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 | const logger = require('./logger')
|
21 |
|
22 | module.exports = function ({
|
23 | cwd = process.cwd(),
|
24 | entry = './index.js',
|
25 | dist,
|
26 | homepage,
|
27 | format,
|
28 | filename,
|
29 | mode,
|
30 | templateCompiler,
|
31 | env,
|
32 | define,
|
33 | html,
|
34 | babel = {},
|
35 | postcss = {},
|
36 | minimize,
|
37 | extractCSS,
|
38 | vendor,
|
39 | sourceMap,
|
40 | autoprefixer,
|
41 | moduleName,
|
42 | cssModules,
|
43 | copy,
|
44 | hotReload,
|
45 | hotEntry,
|
46 | vue: vueOptions
|
47 | } = {}) {
|
48 | const config = new Config()
|
49 |
|
50 | const useHash = mode === 'production' && !format
|
51 | filename = getFileNames(useHash, filename)
|
52 | minimize = inferProductionValue(minimize, mode)
|
53 | extractCSS = inferProductionValue(extractCSS, mode)
|
54 | vendor = inferProductionValue(vendor, mode)
|
55 | env = stringifyObject(Object.assign({
|
56 | NODE_ENV: mode === 'production' ? 'production' : 'development'
|
57 | }, env))
|
58 |
|
59 | if (sourceMap !== false) {
|
60 | if (typeof sourceMap === 'string') {
|
61 | config.devtool(sourceMap)
|
62 | } else {
|
63 | sourceMap = mode === 'production' ?
|
64 | 'source-map' :
|
65 | mode === 'test' ?
|
66 | 'inline-source-map' :
|
67 | 'eval-source-map'
|
68 | config.devtool(sourceMap)
|
69 | }
|
70 | }
|
71 |
|
72 |
|
73 | const handleEntryPath = entry => {
|
74 | return /^[[:].+[\]:]$/.test(entry) ? entry : path.resolve(entry)
|
75 | }
|
76 |
|
77 | if (typeof entry === 'string') {
|
78 | config.entry('client').add(handleEntryPath(entry))
|
79 | } else if (Array.isArray(entry)) {
|
80 | config.entry('client').merge(entry.map(e => handleEntryPath(e)))
|
81 | } else if (typeof entry === 'object') {
|
82 | Object.keys(entry).forEach(k => {
|
83 | const v = entry[k]
|
84 | if (Array.isArray(v)) {
|
85 | config.entry(k).merge(v.map(e => handleEntryPath(e)))
|
86 | } else {
|
87 | config.entry(k).add(handleEntryPath(v))
|
88 | }
|
89 | })
|
90 | }
|
91 |
|
92 | config.output
|
93 | .path(path.resolve(cwd, dist || 'dist'))
|
94 |
|
95 | .pathinfo(true)
|
96 | .filename(filename.js)
|
97 | .chunkFilename(filename.chunk)
|
98 | .publicPath(getPublicPath(mode, homepage))
|
99 |
|
100 | .devtoolModuleFilenameTemplate(info =>
|
101 | path.resolve(info.absoluteResourcePath))
|
102 |
|
103 | config.performance.hints(false)
|
104 |
|
105 | config.resolve
|
106 | .set('symlinks', true)
|
107 | .extensions
|
108 | .add('.js')
|
109 | .add('.jsx')
|
110 | .add('.json')
|
111 | .add('.vue')
|
112 | .end()
|
113 | .modules
|
114 | .add(path.resolve('node_modules'))
|
115 | .add(path.resolve(cwd, 'node_modules'))
|
116 | .add(ownDir('node_modules'))
|
117 | .end()
|
118 | .alias
|
119 | .set('@', path.resolve(cwd, 'src'))
|
120 | .set('vue$', templateCompiler ? 'vue/dist/vue.esm.js' : 'vue/dist/vue.runtime.esm.js')
|
121 |
|
122 | config.resolveLoader
|
123 | .set('symlinks', true)
|
124 | .modules
|
125 | .add(path.resolve('node_modules'))
|
126 | .add(path.resolve(cwd, 'node_modules'))
|
127 | .add(ownDir('node_modules'))
|
128 |
|
129 | postcss.plugins = postcss.plugins || []
|
130 |
|
131 | if (autoprefixer !== false) {
|
132 | postcss.plugins.unshift(require('autoprefixer')(autoprefixer))
|
133 | }
|
134 |
|
135 | const cssOptions = {
|
136 | minimize,
|
137 | extract: extractCSS,
|
138 | sourceMap: Boolean(sourceMap),
|
139 | postcss,
|
140 | cssModules,
|
141 | fallbackLoader: 'vue-style-loader'
|
142 | }
|
143 |
|
144 | cssLoaders.standalone(config, cssOptions)
|
145 |
|
146 | config.module
|
147 | .rule('js')
|
148 | .test(/\.jsx?$/)
|
149 | .include
|
150 | .add(filepath => {
|
151 |
|
152 | if (filepath.split(/[/\\]/).indexOf('node_modules') === -1) {
|
153 | return true
|
154 | }
|
155 | return false
|
156 | })
|
157 | .end()
|
158 | .use('babel-loader')
|
159 | .loader('babel-loader')
|
160 | .options(babel)
|
161 | .end()
|
162 | .end()
|
163 | .rule('es')
|
164 | .test(/\.es6?$/)
|
165 | .use('babel-loader')
|
166 | .loader('babel-loader')
|
167 | .options(babel)
|
168 | .end()
|
169 | .end()
|
170 | .rule('vue')
|
171 | .test(/\.vue$/)
|
172 | .use('vue-loader')
|
173 | .loader('vue-loader')
|
174 | .options(Object.assign({
|
175 | postcss,
|
176 | cssModules: {
|
177 | localIdentName: '[name]__[local]___[hash:base64:5]',
|
178 | camelCase: true
|
179 | },
|
180 | loaders: Object.assign(cssLoaders.vue(cssOptions), {
|
181 | js: {
|
182 | loader: 'babel-loader',
|
183 | options: babel
|
184 | }
|
185 | })
|
186 | }, vueOptions))
|
187 | .end()
|
188 | .end()
|
189 | .rule('static')
|
190 | .test(/\.(ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/)
|
191 | .use('file-loader')
|
192 | .loader('file-loader')
|
193 | .options({
|
194 | name: filename.static
|
195 | })
|
196 | .end()
|
197 | .end()
|
198 | .rule('svg')
|
199 | .test(/\.(svg)(\?.*)?$/)
|
200 | .use('file-loader')
|
201 | .loader('file-loader')
|
202 | .options({
|
203 | name: filename.static
|
204 | })
|
205 |
|
206 |
|
207 |
|
208 | config.plugin('paths-case-sensitive')
|
209 | .use(PathsCaseSensitivePlugin)
|
210 |
|
211 | config.plugin('constants')
|
212 | .use(webpack.DefinePlugin, [merge({
|
213 | 'process.env': env
|
214 | }, define && stringifyObject(define))])
|
215 |
|
216 | if (format === 'cjs') {
|
217 | config.output.libraryTarget('commonjs2')
|
218 | webpackUtils.externalize(config)
|
219 | } else if (format === 'umd') {
|
220 | config.output.libraryTarget('umd').library(moduleName)
|
221 | }
|
222 |
|
223 | if (extractCSS) {
|
224 | config.plugin('extract-css')
|
225 | .use(ExtractTextPlugin, [{
|
226 | filename: filename.css,
|
227 | allChunks: true
|
228 | }])
|
229 | }
|
230 |
|
231 | if (mode === 'production') {
|
232 | const ProgressPlugin = require('webpack/lib/ProgressPlugin')
|
233 | const NoEmitOnErrorsPlugin = require('webpack/lib/NoEmitOnErrorsPlugin')
|
234 |
|
235 | config
|
236 | .plugin('no-emit-on-errors')
|
237 | .use(NoEmitOnErrorsPlugin)
|
238 | .end()
|
239 | .plugin('progress-bar')
|
240 | .use(ProgressPlugin)
|
241 | .end()
|
242 | }
|
243 |
|
244 | if (minimize) {
|
245 | config.plugin('minimize')
|
246 | .use(webpack.optimize.UglifyJsPlugin, [{
|
247 | sourceMap: Boolean(sourceMap),
|
248 |
|
249 | compressor: {
|
250 | warnings: false,
|
251 | conditionals: true,
|
252 | unused: true,
|
253 | comparisons: true,
|
254 | sequences: true,
|
255 | dead_code: true,
|
256 | evaluate: true,
|
257 | if_return: true,
|
258 | join_vars: true,
|
259 | negate_iife: false
|
260 | },
|
261 | output: {
|
262 | comments: false
|
263 | }
|
264 |
|
265 | }])
|
266 | }
|
267 |
|
268 |
|
269 | if (vendor && !format) {
|
270 | config.plugin('split-vendor-code')
|
271 | .use(webpack.optimize.CommonsChunkPlugin, [{
|
272 | name: 'vendor',
|
273 | minChunks: module => {
|
274 | return module.resource && /\.(js|css|es|es6)$/.test(module.resource) && module.resource.indexOf('node_modules') !== -1
|
275 | }
|
276 | }])
|
277 | config.plugin('split-manifest')
|
278 | .use(webpack.optimize.CommonsChunkPlugin, [{
|
279 | name: 'manifest'
|
280 | }])
|
281 | }
|
282 |
|
283 | const supportHMR = hotReload !== false && mode === 'development'
|
284 | const devClient = ownDir('app/dev-client.es6')
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | config.entryPoints.store.forEach(v => {
|
290 | if (v.has('[hot]') || v.has(':hot:')) {
|
291 | logger.warn('[hot] keyword is deprecated, use option "hotEntry" instead.')
|
292 | logger.warn('See https://poi.js.org/#/options?id=hotentry')
|
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 | }
|