1 | const fs = require('fs')
|
2 | const path = require('path')
|
3 | const webpack = require('webpack')
|
4 | const Config = require('webpack-chain')
|
5 | const merge = require('lodash.merge')
|
6 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
7 | const CopyPlugin = require('copy-webpack-plugin')
|
8 | const HtmlPlugin = require('html-webpack-plugin')
|
9 | const isYarn = require('installed-by-yarn-globally')
|
10 | const cssLoaders = require('./css-loaders')
|
11 | const webpackUtils = require('./webpack-utils')
|
12 | const {
|
13 | getFileNames,
|
14 | getPublicPath,
|
15 | ownDir,
|
16 | inferProductionValue,
|
17 | stringifyObject
|
18 | } = require('./utils')
|
19 |
|
20 | module.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 |
|
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 |
|
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 |
|
261 | }])
|
262 | }
|
263 |
|
264 |
|
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 |
|
335 | if (isYarn(__dirname)) {
|
336 |
|
337 |
|
338 | config.resolve.modules.add(ownDir('..'))
|
339 |
|
340 | config.resolveLoader.modules.add(ownDir('..'))
|
341 | }
|
342 |
|
343 | return config
|
344 | }
|