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 | 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 |
|
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 |
|
94 | .pathinfo(true)
|
95 | .filename(filename.js)
|
96 | .chunkFilename(filename.chunk)
|
97 | .publicPath(getPublicPath(mode, homepage))
|
98 |
|
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 |
|
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 |
|
209 |
|
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 |
|
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 |
|
267 | }])
|
268 | }
|
269 |
|
270 |
|
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 |
|
289 |
|
290 |
|
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 |
|
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 |
|
360 | if (yarnGlobal.inDirectory(__dirname)) {
|
361 |
|
362 |
|
363 | config.resolve.modules.add(ownDir('..'))
|
364 |
|
365 | config.resolveLoader.modules.add(ownDir('..'))
|
366 | }
|
367 |
|
368 | return config
|
369 | }
|