UNPKG

8.71 kBJavaScriptView Raw
1const fs = require('fs')
2const path = require('path')
3const webpack = require('webpack')
4const Config = require('webpack-chain')
5const merge = require('lodash.merge')
6const ExtractTextPlugin = require('extract-text-webpack-plugin')
7const CopyPlugin = require('copy-webpack-plugin')
8const HtmlPlugin = require('html-webpack-plugin')
9const isYarn = require('installed-by-yarn-globally')
10const cssLoaders = require('./css-loaders')
11const webpackUtils = require('./webpack-utils')
12const {
13 getFileNames,
14 getPublicPath,
15 ownDir,
16 inferProductionValue,
17 stringifyObject
18} = require('./utils')
19
20module.exports = function ({
21 cwd = process.cwd(),
22 entry = './index.js',
23 dist,
24 homepage,
25 format,
26 filename,
27 mode,
28 templateCompiler,
29 env,
30 define,
31 html,
32 babel = {},
33 postcss = {},
34 minimize,
35 extractCSS,
36 vendor,
37 sourceMap,
38 autoprefixer,
39 moduleName,
40 cssModules,
41 copy,
42 hotReload,
43 polyfills
44} = {}) {
45 const config = new Config()
46
47 const useHash = mode === 'production' && !format
48 filename = getFileNames(useHash, filename)
49 minimize = inferProductionValue(minimize, mode)
50 extractCSS = inferProductionValue(extractCSS, mode)
51 vendor = inferProductionValue(vendor, mode)
52 env = stringifyObject(Object.assign({
53 NODE_ENV: mode === 'production' ? 'production' : 'development'
54 }, env))
55 babel = babel && (babel.babelrc !== false) ? babel : {
56 babelrc: false,
57 presets: [
58 [require.resolve('babel-preset-vue-app'), {
59 useBuiltIns: true
60 }]
61 ]
62 }
63
64 if (sourceMap === true || sourceMap === undefined) {
65 sourceMap = mode === 'production' ?
66 'source-map' :
67 mode === 'test' ?
68 'inline-source-map' :
69 'eval-source-map'
70 config.devtool(sourceMap)
71 }
72
73 const handleEntryPath = entry => {
74 return /^\[.+\]$/.test(entry) ? entry : path.resolve(entry)
75 }
76
77 if (polyfills) {
78 config.entry('polyfills')
79 .merge(polyfills === true ? [require.resolve('web-polyfill')] : polyfills)
80 }
81
82 if (typeof entry === 'string') {
83 config.entry('client').add(handleEntryPath(entry))
84 } else if (Array.isArray(entry)) {
85 config.entry('client').merge(entry.map(e => handleEntryPath(e)))
86 } else if (typeof entry === 'object') {
87 Object.keys(entry).forEach(k => {
88 const v = entry[k]
89 if (Array.isArray(v)) {
90 config.entry(k).merge(v.map(e => handleEntryPath(e)))
91 } else {
92 config.entry(k).add(handleEntryPath(v))
93 }
94 })
95 }
96
97 config.output
98 .path(path.resolve(cwd, dist || 'dist'))
99 .filename(filename.js)
100 .chunkFilename(filename.chunk)
101 .publicPath(getPublicPath(mode, homepage))
102
103 config.performance.hints(false)
104
105 config.resolve
106 .extensions
107 .add('.js')
108 .add('.json')
109 .add('.vue')
110 .end()
111 .modules
112 .add(path.resolve('node_modules'))
113 .add(path.resolve(cwd, 'node_modules'))
114 .add(ownDir('node_modules'))
115 .end()
116 .alias
117 .set('@', path.resolve(cwd, 'src'))
118 .set('vue$', templateCompiler ? 'vue/dist/vue.esm.js' : 'vue/dist/vue.runtime.esm.js')
119
120 config.resolveLoader
121 .modules
122 .add(path.resolve('node_modules'))
123 .add(path.resolve(cwd, 'node_modules'))
124 .add(ownDir('node_modules'))
125
126 postcss.plugins = postcss.plugins || []
127
128 if (autoprefixer !== false) {
129 postcss.plugins.unshift(require('autoprefixer')(Object.assign({
130 browsers: ['ie > 8', 'last 3 versions']
131 }, autoprefixer)))
132 }
133
134 const cssOptions = {
135 minimize,
136 extract: extractCSS,
137 sourceMap: Boolean(sourceMap),
138 postcss,
139 cssModules,
140 fallbackLoader: 'vue-style-loader'
141 }
142
143 cssLoaders.standalone(config, cssOptions)
144
145 config.module
146 .rule('js')
147 .test(/\.jsx?$/)
148 .include
149 .add(filepath => {
150 // for anything outside node_modules
151 if (filepath.split(/[/\\]/).indexOf('node_modules') === -1) {
152 return true
153 }
154 return false
155 })
156 .end()
157 .use('babel')
158 .loader('babel-loader')
159 .options(babel)
160 .end()
161 .end()
162 .rule('es')
163 .test(/\.es6?$/)
164 .use('babel')
165 .loader('babel-loader')
166 .options(babel)
167 .end()
168 .end()
169 .rule('vue')
170 .test(/\.vue$/)
171 .use('vue')
172 .loader('vue-loader')
173 .options({
174 postcss,
175 cssModules: {
176 localIdentName: '[name]__[local]___[hash:base64:5]',
177 camelCase: true
178 },
179 loaders: Object.assign(cssLoaders.vue(cssOptions), {
180 js: {
181 loader: 'babel-loader',
182 options: babel
183 }
184 })
185 })
186 .end()
187 .end()
188 .rule('static')
189 .test(/\.(ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/)
190 .use('file')
191 .loader('file-loader')
192 .options({
193 name: filename.static
194 })
195 .end()
196 .end()
197 .rule('svg')
198 .test(/\.(svg)(\?.*)?$/)
199 .use('file')
200 .loader('file-loader')
201 .options({
202 name: filename.static
203 })
204
205 config.plugin('constants')
206 .use(webpack.DefinePlugin, [merge({
207 process: {
208 env
209 }
210 }, define && stringifyObject(define))])
211
212 if (format === 'cjs') {
213 config.output.libraryTarget('commonjs2')
214 webpackUtils.externalize(config)
215 } else if (format === 'umd') {
216 config.output.libraryTarget('umd').library(moduleName)
217 }
218
219 if (extractCSS) {
220 config.plugin('extract-css')
221 .use(ExtractTextPlugin, [{
222 filename: filename.css,
223 allChunks: true
224 }])
225 }
226
227 if (mode === 'production') {
228 const ProgressPlugin = require('webpack/lib/ProgressPlugin')
229 const NoEmitOnErrorsPlugin = require('webpack/lib/NoEmitOnErrorsPlugin')
230
231 config
232 .plugin('no-emit-on-errors')
233 .use(NoEmitOnErrorsPlugin)
234 .end()
235 .plugin('progress-bar')
236 .use(ProgressPlugin)
237 .end()
238 }
239
240 if (minimize) {
241 config.plugin('minimize')
242 .use(webpack.optimize.UglifyJsPlugin, [{
243 sourceMap: Boolean(sourceMap),
244 /* eslint-disable camelcase */
245 compressor: {
246 warnings: false,
247 conditionals: true,
248 unused: true,
249 comparisons: true,
250 sequences: true,
251 dead_code: true,
252 evaluate: true,
253 if_return: true,
254 join_vars: true,
255 negate_iife: false
256 },
257 output: {
258 comments: false
259 }
260 /* eslint-enable camelcase */
261 }])
262 }
263
264 // Do not split vendor code in `cjs` and `umd` mode
265 if (vendor && !format) {
266 config.plugin('split-vendor-code')
267 .use(webpack.optimize.CommonsChunkPlugin, [{
268 name: 'vendor',
269 minChunks: module => {
270 return module.resource && /\.(js|css|es|es6)$/.test(module.resource) && module.resource.indexOf('node_modules') !== -1
271 }
272 }])
273 config.plugin('split-manifest')
274 .use(webpack.optimize.CommonsChunkPlugin, [{
275 name: 'manifest'
276 }])
277 }
278
279 if (hotReload !== false && mode === 'development') {
280 const devClient = ownDir('app/dev-client.es6')
281
282 config.entryPoints.store.forEach((v, k) => {
283 if (k === 'client' || v.has('[hot]')) {
284 v.delete('[hot]').prepend(devClient)
285 }
286 })
287
288 config.plugin('hmr')
289 .use(webpack.HotModuleReplacementPlugin)
290 }
291
292 if (copy !== false) {
293 let copyOptions = []
294 if (fs.existsSync(path.resolve(cwd, 'static'))) {
295 copyOptions.push({
296 from: path.resolve(cwd, 'static'),
297 to: '.',
298 ignore: ['.DS_Store']
299 })
300 }
301 if (typeof copy === 'object') {
302 if (Array.isArray(copy)) {
303 copyOptions = copyOptions.concat(copy)
304 } else {
305 copyOptions.push(copy)
306 }
307 }
308 if (copyOptions.length > 0) {
309 config.plugin('copy-static-files')
310 .use(CopyPlugin, [copyOptions])
311 }
312 }
313
314 if (html !== false && mode !== 'test') {
315 html = html || {}
316 const htmls = Array.isArray(html) ? html : [html]
317 const defaultHtml = {
318 title: 'Poi',
319 template: ownDir('lib/index.ejs'),
320 env
321 }
322 htmls.forEach((h, i) => {
323 config.plugin(`html-${i}`)
324 .use(HtmlPlugin, [Object.assign({
325 minify: {
326 collapseWhitespace: minimize,
327 minifyCSS: minimize,
328 minifyJS: minimize
329 }
330 }, defaultHtml, h)])
331 })
332 }
333
334 // installed by `yarn global add`
335 if (isYarn(__dirname)) {
336 // modules in yarn global node_modules
337 // because of yarn's flat node_modules structure
338 config.resolve.modules.add(ownDir('..'))
339 // loaders in yarn global node_modules
340 config.resolveLoader.modules.add(ownDir('..'))
341 }
342
343 return config
344}