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 PathsCaseSensitivePlugin = require('case-sensitive-paths-webpack-plugin')
|
10 | const yarnGlobal = require('yarn-global')
|
11 | const cssLoaders = require('./css-loaders')
|
12 | const webpackUtils = require('./webpack-utils')
|
13 | const {
|
14 | getFileNames,
|
15 | getPublicPath,
|
16 | ownDir,
|
17 | inferProductionValue,
|
18 | stringifyObject
|
19 | } = require('./utils')
|
20 |
|
21 | module.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 |
|
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 |
|
98 | .pathinfo(true)
|
99 | .filename(filename.js)
|
100 | .chunkFilename(filename.chunk)
|
101 | .publicPath(getPublicPath(mode, homepage))
|
102 |
|
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 |
|
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 |
|
209 |
|
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 |
|
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 |
|
269 | }])
|
270 | }
|
271 |
|
272 |
|
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 |
|
291 |
|
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 |
|
349 | if (yarnGlobal.inDirectory(__dirname)) {
|
350 |
|
351 |
|
352 | config.resolve.modules.add(ownDir('..'))
|
353 |
|
354 | config.resolveLoader.modules.add(ownDir('..'))
|
355 | }
|
356 |
|
357 | return config
|
358 | }
|