UNPKG

9.74 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 vue: vueOptions
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 (typeof entry === 'string') {
77 config.entry('client').add(handleEntryPath(entry))
78 } else if (Array.isArray(entry)) {
79 config.entry('client').merge(entry.map(e => handleEntryPath(e)))
80 } else if (typeof entry === 'object') {
81 Object.keys(entry).forEach(k => {
82 const v = entry[k]
83 if (Array.isArray(v)) {
84 config.entry(k).merge(v.map(e => handleEntryPath(e)))
85 } else {
86 config.entry(k).add(handleEntryPath(v))
87 }
88 })
89 }
90
91 config.output
92 .path(path.resolve(cwd, dist || 'dist'))
93 // Add /* filename */ comments to generated require()s in the output.
94 .pathinfo(true)
95 .filename(filename.js)
96 .chunkFilename(filename.chunk)
97 .publicPath(getPublicPath(mode, homepage))
98 // Point sourcemap entries to original disk location
99 .devtoolModuleFilenameTemplate(info =>
100 path.resolve(info.absoluteResourcePath))
101
102 config.performance.hints(false)
103
104 config.resolve
105 .set('symlinks', true)
106 .extensions
107 .add('.js')
108 .add('.jsx')
109 .add('.json')
110 .add('.vue')
111 .end()
112 .modules
113 .add(path.resolve('node_modules'))
114 .add(path.resolve(cwd, 'node_modules'))
115 .add(ownDir('node_modules'))
116 .end()
117 .alias
118 .set('@', path.resolve(cwd, 'src'))
119 .set('vue$', templateCompiler ? 'vue/dist/vue.esm.js' : 'vue/dist/vue.runtime.esm.js')
120
121 config.resolveLoader
122 .set('symlinks', true)
123 .modules
124 .add(path.resolve('node_modules'))
125 .add(path.resolve(cwd, 'node_modules'))
126 .add(ownDir('node_modules'))
127
128 postcss.plugins = postcss.plugins || []
129
130 if (autoprefixer !== false) {
131 postcss.plugins.unshift(require('autoprefixer')(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-loader')
158 .loader('babel-loader')
159 .options(babel)
160 .end()
161 .end()
162 .rule('es')
163 .test(/\.es6?$/)
164 .use('babel-loader')
165 .loader('babel-loader')
166 .options(babel)
167 .end()
168 .end()
169 .rule('vue')
170 .test(/\.vue$/)
171 .use('vue-loader')
172 .loader('vue-loader')
173 .options(Object.assign({
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 }, vueOptions))
186 .end()
187 .end()
188 .rule('static')
189 .test(/\.(ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/)
190 .use('file-loader')
191 .loader('file-loader')
192 .options({
193 name: filename.static
194 })
195 .end()
196 .end()
197 .rule('svg')
198 .test(/\.(svg)(\?.*)?$/)
199 .use('file-loader')
200 .loader('file-loader')
201 .options({
202 name: filename.static
203 })
204
205 config.plugin('module-concatenation')
206 .use(webpack.optimize.ModuleConcatenationPlugin)
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.env': env
216 }, define && stringifyObject(define))])
217
218 if (format === 'cjs') {
219 config.output.libraryTarget('commonjs2')
220 webpackUtils.externalize(config)
221 } else if (format === 'umd') {
222 config.output.libraryTarget('umd').library(moduleName)
223 }
224
225 if (extractCSS) {
226 config.plugin('extract-css')
227 .use(ExtractTextPlugin, [{
228 filename: filename.css,
229 allChunks: true
230 }])
231 }
232
233 if (mode === 'production') {
234 const ProgressPlugin = require('webpack/lib/ProgressPlugin')
235 const NoEmitOnErrorsPlugin = require('webpack/lib/NoEmitOnErrorsPlugin')
236
237 config
238 .plugin('no-emit-on-errors')
239 .use(NoEmitOnErrorsPlugin)
240 .end()
241 .plugin('progress-bar')
242 .use(ProgressPlugin)
243 .end()
244 }
245
246 if (minimize) {
247 config.plugin('minimize')
248 .use(webpack.optimize.UglifyJsPlugin, [{
249 sourceMap: Boolean(sourceMap),
250 /* eslint-disable camelcase */
251 compressor: {
252 warnings: false,
253 conditionals: true,
254 unused: true,
255 comparisons: true,
256 sequences: true,
257 dead_code: true,
258 evaluate: true,
259 if_return: true,
260 join_vars: true,
261 negate_iife: false
262 },
263 output: {
264 comments: false
265 }
266 /* eslint-enable camelcase */
267 }])
268 }
269
270 // Do not split vendor code in `cjs` and `umd` mode
271 if (vendor && !format) {
272 config.plugin('split-vendor-code')
273 .use(webpack.optimize.CommonsChunkPlugin, [{
274 name: 'vendor',
275 minChunks: module => {
276 return module.resource && /\.(js|css|es|es6)$/.test(module.resource) && module.resource.indexOf('node_modules') !== -1
277 }
278 }])
279 config.plugin('split-manifest')
280 .use(webpack.optimize.CommonsChunkPlugin, [{
281 name: 'manifest'
282 }])
283 }
284
285 const supportHMR = hotReload !== false && mode === 'development'
286 const devClient = ownDir('app/dev-client.es6')
287
288 // Add hmr entry (deprecated)
289 // Replace keywords like `[hot]` `:hot:` with hmr entry
290 // This will be removed in next major version
291 config.entryPoints.store.forEach(v => {
292 if (v.has('[hot]') || v.has(':hot:')) {
293 v.delete('[hot]').delete(':hot:')
294 if (supportHMR) {
295 v.prepend(devClient)
296 }
297 }
298 })
299
300 // Add hmr entry using `hotEntry` option
301 if (supportHMR) {
302 config.plugin('hmr')
303 .use(webpack.HotModuleReplacementPlugin)
304
305 config.plugin('named-modules')
306 .use(webpack.NamedModulesPlugin)
307
308 const hotEntryPoints = webpackUtils.getHotEntryPoints(hotEntry)
309
310 config.entryPoints.store.forEach((v, entryPoint) => {
311 if (hotEntryPoints.has(entryPoint)) {
312 v.prepend(devClient)
313 }
314 })
315 }
316
317 if (copy !== false) {
318 let copyOptions = []
319 if (fs.existsSync(path.resolve(cwd, 'static'))) {
320 copyOptions.push({
321 from: path.resolve(cwd, 'static'),
322 to: '.',
323 ignore: ['.DS_Store']
324 })
325 }
326 if (typeof copy === 'object') {
327 if (Array.isArray(copy)) {
328 copyOptions = copyOptions.concat(copy)
329 } else {
330 copyOptions.push(copy)
331 }
332 }
333 if (copyOptions.length > 0) {
334 config.plugin('copy-static-files')
335 .use(CopyPlugin, [copyOptions])
336 }
337 }
338
339 if (html !== false && mode !== 'test') {
340 html = html || {}
341 const htmls = Array.isArray(html) ? html : [html]
342 const defaultHtml = {
343 title: 'Poi',
344 template: ownDir('lib/index.ejs'),
345 env
346 }
347 htmls.forEach((h, i) => {
348 config.plugin(`html-${i}`)
349 .use(HtmlPlugin, [Object.assign({
350 minify: {
351 collapseWhitespace: minimize,
352 minifyCSS: minimize,
353 minifyJS: minimize
354 }
355 }, defaultHtml, h)])
356 })
357 }
358
359 // installed by `yarn global add`
360 if (yarnGlobal.inDirectory(__dirname)) {
361 // modules in yarn global node_modules
362 // because of yarn's flat node_modules structure
363 config.resolve.modules.add(ownDir('..'))
364 // loaders in yarn global node_modules
365 config.resolveLoader.modules.add(ownDir('..'))
366 }
367
368 return config
369}