UNPKG

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