UNPKG

9.68 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 hotEntry,
45 polyfills
46} = {}) {
47 const config = new Config()
48
49 const useHash = mode === 'production' && !format
50 filename = getFileNames(useHash, filename)
51 minimize = inferProductionValue(minimize, mode)
52 extractCSS = inferProductionValue(extractCSS, mode)
53 vendor = inferProductionValue(vendor, mode)
54 env = stringifyObject(Object.assign({
55 NODE_ENV: mode === 'production' ? 'production' : 'development'
56 }, env))
57
58 if (sourceMap !== false) {
59 if (typeof sourceMap === 'string') {
60 config.devtool(sourceMap)
61 } else {
62 sourceMap = mode === 'production' ?
63 'source-map' :
64 mode === 'test' ?
65 'inline-source-map' :
66 'eval-source-map'
67 config.devtool(sourceMap)
68 }
69 }
70
71 // Do not resolve path like `:hot:` and `[hot]`
72 const handleEntryPath = entry => {
73 return /^[[:].+[\]:]$/.test(entry) ? entry : path.resolve(entry)
74 }
75
76 if (polyfills) {
77 config.entry('polyfills')
78 .merge(polyfills === true ? [require.resolve('web-polyfill')] : polyfills)
79 }
80
81 if (typeof entry === 'string') {
82 config.entry('client').add(handleEntryPath(entry))
83 } else if (Array.isArray(entry)) {
84 config.entry('client').merge(entry.map(e => handleEntryPath(e)))
85 } else if (typeof entry === 'object') {
86 Object.keys(entry).forEach(k => {
87 const v = entry[k]
88 if (Array.isArray(v)) {
89 config.entry(k).merge(v.map(e => handleEntryPath(e)))
90 } else {
91 config.entry(k).add(handleEntryPath(v))
92 }
93 })
94 }
95
96 config.output
97 .path(path.resolve(cwd, dist || 'dist'))
98 // Add /* filename */ comments to generated require()s in the output.
99 .pathinfo(true)
100 .filename(filename.js)
101 .chunkFilename(filename.chunk)
102 .publicPath(getPublicPath(mode, homepage))
103 // Point sourcemap entries to original disk location
104 .devtoolModuleFilenameTemplate(info =>
105 path.resolve(info.absoluteResourcePath))
106
107 config.performance.hints(false)
108
109 config.resolve
110 .set('symlinks', true)
111 .extensions
112 .add('.js')
113 .add('.jsx')
114 .add('.json')
115 .add('.vue')
116 .end()
117 .modules
118 .add(path.resolve('node_modules'))
119 .add(path.resolve(cwd, 'node_modules'))
120 .add(ownDir('node_modules'))
121 .end()
122 .alias
123 .set('@', path.resolve(cwd, 'src'))
124 .set('vue$', templateCompiler ? 'vue/dist/vue.esm.js' : 'vue/dist/vue.runtime.esm.js')
125
126 config.resolveLoader
127 .set('symlinks', true)
128 .modules
129 .add(path.resolve('node_modules'))
130 .add(path.resolve(cwd, 'node_modules'))
131 .add(ownDir('node_modules'))
132
133 postcss.plugins = postcss.plugins || []
134
135 if (autoprefixer !== false) {
136 postcss.plugins.unshift(require('autoprefixer')(autoprefixer))
137 }
138
139 const cssOptions = {
140 minimize,
141 extract: extractCSS,
142 sourceMap: Boolean(sourceMap),
143 postcss,
144 cssModules,
145 fallbackLoader: 'vue-style-loader'
146 }
147
148 cssLoaders.standalone(config, cssOptions)
149
150 config.module
151 .rule('js')
152 .test(/\.jsx?$/)
153 .include
154 .add(filepath => {
155 // for anything outside node_modules
156 if (filepath.split(/[/\\]/).indexOf('node_modules') === -1) {
157 return true
158 }
159 return false
160 })
161 .end()
162 .use('babel-loader')
163 .loader('babel-loader')
164 .options(babel)
165 .end()
166 .end()
167 .rule('es')
168 .test(/\.es6?$/)
169 .use('babel-loader')
170 .loader('babel-loader')
171 .options(babel)
172 .end()
173 .end()
174 .rule('vue')
175 .test(/\.vue$/)
176 .use('vue-loader')
177 .loader('vue-loader')
178 .options({
179 postcss,
180 cssModules: {
181 localIdentName: '[name]__[local]___[hash:base64:5]',
182 camelCase: true
183 },
184 loaders: Object.assign(cssLoaders.vue(cssOptions), {
185 js: {
186 loader: 'babel-loader',
187 options: babel
188 }
189 })
190 })
191 .end()
192 .end()
193 .rule('static')
194 .test(/\.(ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/)
195 .use('file-loader')
196 .loader('file-loader')
197 .options({
198 name: filename.static
199 })
200 .end()
201 .end()
202 .rule('svg')
203 .test(/\.(svg)(\?.*)?$/)
204 .use('file-loader')
205 .loader('file-loader')
206 .options({
207 name: filename.static
208 })
209
210 // Enforces the entire path of all required modules match
211 // The exact case of the actual path on disk
212 config.plugin('paths-case-sensitive')
213 .use(PathsCaseSensitivePlugin)
214
215 config.plugin('constants')
216 .use(webpack.DefinePlugin, [merge({
217 'process.env': env
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 (deprecated)
291 // Replace keywords like `[hot]` `:hot:` with hmr entry
292 // This will be removed in next major version
293 config.entryPoints.store.forEach(v => {
294 if (v.has('[hot]') || v.has(':hot:')) {
295 v.delete('[hot]').delete(':hot:')
296 if (supportHMR) {
297 v.prepend(devClient)
298 }
299 }
300 })
301
302 // Add hmr entry using `hotEntry` option
303 if (supportHMR) {
304 config.plugin('hmr')
305 .use(webpack.HotModuleReplacementPlugin)
306
307 const hotEntryPoints = webpackUtils.getHotEntryPoints(hotEntry)
308
309 config.entryPoints.store.forEach((v, entryPoint) => {
310 if (hotEntryPoints.has(entryPoint)) {
311 v.prepend(devClient)
312 }
313 })
314 }
315
316 if (copy !== false) {
317 let copyOptions = []
318 if (fs.existsSync(path.resolve(cwd, 'static'))) {
319 copyOptions.push({
320 from: path.resolve(cwd, 'static'),
321 to: '.',
322 ignore: ['.DS_Store']
323 })
324 }
325 if (typeof copy === 'object') {
326 if (Array.isArray(copy)) {
327 copyOptions = copyOptions.concat(copy)
328 } else {
329 copyOptions.push(copy)
330 }
331 }
332 if (copyOptions.length > 0) {
333 config.plugin('copy-static-files')
334 .use(CopyPlugin, [copyOptions])
335 }
336 }
337
338 if (html !== false && mode !== 'test') {
339 html = html || {}
340 const htmls = Array.isArray(html) ? html : [html]
341 const defaultHtml = {
342 title: 'Poi',
343 template: ownDir('lib/index.ejs'),
344 env
345 }
346 htmls.forEach((h, i) => {
347 config.plugin(`html-${i}`)
348 .use(HtmlPlugin, [Object.assign({
349 minify: {
350 collapseWhitespace: minimize,
351 minifyCSS: minimize,
352 minifyJS: minimize
353 }
354 }, defaultHtml, h)])
355 })
356 }
357
358 // installed by `yarn global add`
359 if (yarnGlobal.inDirectory(__dirname)) {
360 // modules in yarn global node_modules
361 // because of yarn's flat node_modules structure
362 config.resolve.modules.add(ownDir('..'))
363 // loaders in yarn global node_modules
364 config.resolveLoader.modules.add(ownDir('..'))
365 }
366
367 return config
368}