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 FancyLogPlugin = require('./webpack/fancy-log-plugin')
|
12 | const webpackUtils = require('./webpack-utils')
|
13 | const {
|
14 | getFileNames,
|
15 | getPublicPath,
|
16 | ownDir,
|
17 | inferProductionValue,
|
18 | stringifyObject,
|
19 | getFullEnvString
|
20 | } = require('./utils')
|
21 | const logger = require('./logger')
|
22 | const cssLoaders = require('./webpack/css-loaders')
|
23 | const transformJS = require('./webpack/transform-js')
|
24 | const transformVue = require('./webpack/transform-vue')
|
25 |
|
26 | module.exports = function ({
|
27 | cwd = process.cwd(),
|
28 | entry = './index.js',
|
29 | dist,
|
30 | homepage,
|
31 | format,
|
32 | filename,
|
33 | mode,
|
34 | templateCompiler,
|
35 | env,
|
36 | define,
|
37 | html,
|
38 | babel = {},
|
39 | postcss = {},
|
40 | minimize,
|
41 | extractCSS,
|
42 | vendor = true,
|
43 | sourceMap,
|
44 | autoprefixer,
|
45 | moduleName,
|
46 | cssModules,
|
47 | copy,
|
48 | hotReload,
|
49 | hotEntry,
|
50 | vue: vueOptions,
|
51 | transformModules,
|
52 | hash,
|
53 | host,
|
54 | port,
|
55 | clear,
|
56 | inlineImageMaxSize = 10000,
|
57 | staticFolder = 'static'
|
58 | } = {}) {
|
59 | const config = new Config()
|
60 |
|
61 | const useHash = typeof hash === 'boolean' ? hash : (mode === 'production' && !format)
|
62 | filename = getFileNames(useHash, filename)
|
63 | minimize = inferProductionValue(minimize, mode)
|
64 | extractCSS = inferProductionValue(extractCSS, mode)
|
65 | env = stringifyObject(Object.assign({
|
66 | NODE_ENV: mode === 'production' ? 'production' : 'development'
|
67 | }, env))
|
68 |
|
69 | if (sourceMap !== false) {
|
70 | if (typeof sourceMap === 'string') {
|
71 | config.devtool(sourceMap)
|
72 | } else {
|
73 | sourceMap = mode === 'production' ?
|
74 | 'source-map' :
|
75 | mode === 'test' ?
|
76 | 'inline-source-map' :
|
77 | 'eval-source-map'
|
78 | config.devtool(sourceMap)
|
79 | }
|
80 | }
|
81 |
|
82 |
|
83 | const handleEntryPath = entry => {
|
84 | return /^[[:].+[\]:]$/.test(entry) ? entry : path.resolve(entry)
|
85 | }
|
86 |
|
87 | if (typeof entry === 'string') {
|
88 | config.entry('client').add(handleEntryPath(entry))
|
89 | } else if (Array.isArray(entry)) {
|
90 | config.entry('client').merge(entry.map(e => handleEntryPath(e)))
|
91 | } else if (typeof entry === 'object') {
|
92 | Object.keys(entry).forEach(k => {
|
93 | const v = entry[k]
|
94 | if (Array.isArray(v)) {
|
95 | config.entry(k).merge(v.map(e => handleEntryPath(e)))
|
96 | } else {
|
97 | config.entry(k).add(handleEntryPath(v))
|
98 | }
|
99 | })
|
100 | }
|
101 |
|
102 | config.output
|
103 | .path(path.resolve(cwd, dist || 'dist'))
|
104 |
|
105 | .pathinfo(true)
|
106 | .filename(filename.js)
|
107 | .chunkFilename(filename.chunk)
|
108 | .publicPath(getPublicPath(mode, homepage))
|
109 |
|
110 | .devtoolModuleFilenameTemplate(info =>
|
111 | path.resolve(info.absoluteResourcePath))
|
112 |
|
113 | config.performance.hints(false)
|
114 |
|
115 | config.resolve
|
116 | .set('symlinks', true)
|
117 | .extensions
|
118 | .add('.js')
|
119 | .add('.jsx')
|
120 | .add('.json')
|
121 | .add('.vue')
|
122 | .end()
|
123 | .modules
|
124 | .add(path.resolve(cwd, 'node_modules'))
|
125 | .add('node_modules')
|
126 | .add(ownDir('node_modules'))
|
127 | .end()
|
128 | .alias
|
129 | .set('@', path.resolve(cwd, 'src'))
|
130 | .set('vue$', templateCompiler ? 'vue/dist/vue.esm.js' : 'vue/dist/vue.runtime.esm.js')
|
131 |
|
132 | config.resolveLoader
|
133 | .set('symlinks', true)
|
134 | .modules
|
135 | .add(path.resolve(cwd, 'node_modules'))
|
136 | .add('node_modules')
|
137 | .add(ownDir('node_modules'))
|
138 |
|
139 | postcss.plugins = postcss.plugins || []
|
140 |
|
141 | if (autoprefixer !== false) {
|
142 | postcss.plugins.unshift(require('autoprefixer')(autoprefixer))
|
143 | }
|
144 |
|
145 | const cssOptions = {
|
146 | minimize,
|
147 | extract: extractCSS,
|
148 | sourceMap: Boolean(sourceMap),
|
149 | postcss,
|
150 | cssModules,
|
151 | fallbackLoader: 'vue-style-loader'
|
152 | }
|
153 |
|
154 |
|
155 | cssLoaders.standalone(config, cssOptions)
|
156 |
|
157 | transformJS(config, { babel, transformModules })
|
158 |
|
159 | transformVue(config, { babel, vueOptions, cssOptions })
|
160 |
|
161 | config.module
|
162 | .rule('image')
|
163 | .test([/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/])
|
164 | .use('url-loader')
|
165 | .loader('url-loader')
|
166 | .options({
|
167 | name: filename.images,
|
168 |
|
169 | limit: inlineImageMaxSize
|
170 | })
|
171 | .end()
|
172 | .end()
|
173 |
|
174 |
|
175 | .rule('svg')
|
176 | .test(/\.(svg)(\?.*)?$/)
|
177 | .use('file-loader')
|
178 | .loader('file-loader')
|
179 | .options({
|
180 | name: filename.images
|
181 | })
|
182 | .end()
|
183 | .end()
|
184 | .rule('font')
|
185 | .test(/\.(eot|otf|webp|ttf|woff|woff2)(\?.*)?$/)
|
186 | .use('file-loader')
|
187 | .loader('file-loader')
|
188 | .options({
|
189 | name: filename.fonts
|
190 | })
|
191 |
|
192 |
|
193 |
|
194 | config.plugin('paths-case-sensitive')
|
195 | .use(PathsCaseSensitivePlugin)
|
196 |
|
197 | config.plugin('constants')
|
198 | .use(webpack.DefinePlugin, [
|
199 | merge(
|
200 |
|
201 | getFullEnvString(env),
|
202 | define && stringifyObject(define)
|
203 | )
|
204 | ])
|
205 |
|
206 | config.plugin('fancy-log')
|
207 | .use(FancyLogPlugin, [
|
208 | {
|
209 | mode,
|
210 | host,
|
211 | port,
|
212 | clear
|
213 | }
|
214 | ])
|
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 && mode !== 'test') {
|
270 | config.plugin('split-vendor-code')
|
271 | .use(webpack.optimize.CommonsChunkPlugin, [{
|
272 | name: 'vendor',
|
273 | minChunks: module => {
|
274 | return module.context && module.context.indexOf('node_modules') >= 0
|
275 | }
|
276 | }])
|
277 | config.plugin('split-manifest')
|
278 | .use(webpack.optimize.CommonsChunkPlugin, [{
|
279 | name: 'manifest'
|
280 | }])
|
281 | }
|
282 |
|
283 | if (mode === 'development' || mode === 'watch') {
|
284 | const WatchMissingNodeModulesPlugin = require('poi-dev-utils/watch-missing-node-modules-plugin')
|
285 |
|
286 | config.plugin('watch-missing-node-modules')
|
287 | .use(WatchMissingNodeModulesPlugin, [
|
288 | path.resolve(cwd, 'node_modules')
|
289 | ])
|
290 | }
|
291 |
|
292 | const supportHMR = hotReload !== false && mode === 'development'
|
293 | const devClient = ownDir('app/dev-client.es6')
|
294 |
|
295 |
|
296 |
|
297 |
|
298 | config.entryPoints.store.forEach(v => {
|
299 | if (v.has('[hot]') || v.has(':hot:')) {
|
300 | logger.warn('[hot] keyword is deprecated, use option "hotEntry" instead.')
|
301 | logger.warn('See https://poi.js.org/#/options?id=hotentry')
|
302 | v.delete('[hot]').delete(':hot:')
|
303 | if (supportHMR) {
|
304 | v.prepend(devClient)
|
305 | }
|
306 | }
|
307 | })
|
308 |
|
309 |
|
310 | if (supportHMR) {
|
311 | config.plugin('hmr')
|
312 | .use(webpack.HotModuleReplacementPlugin)
|
313 |
|
314 | config.plugin('named-modules')
|
315 | .use(webpack.NamedModulesPlugin)
|
316 |
|
317 | const hotEntryPoints = webpackUtils.getHotEntryPoints(hotEntry)
|
318 |
|
319 | config.entryPoints.store.forEach((v, entryPoint) => {
|
320 | if (hotEntryPoints.has(entryPoint)) {
|
321 | v.prepend(devClient)
|
322 | }
|
323 | })
|
324 | }
|
325 |
|
326 | if (copy !== false) {
|
327 | let copyOptions = []
|
328 | if (fs.existsSync(path.resolve(cwd, staticFolder))) {
|
329 | copyOptions.push({
|
330 | from: path.resolve(cwd, staticFolder),
|
331 | to: '.',
|
332 | ignore: ['.DS_Store']
|
333 | })
|
334 | }
|
335 | if (typeof copy === 'object') {
|
336 | if (Array.isArray(copy)) {
|
337 | copyOptions = copyOptions.concat(copy)
|
338 | } else {
|
339 | copyOptions.push(copy)
|
340 | }
|
341 | }
|
342 | if (copyOptions.length > 0) {
|
343 | config.plugin('copy-static-files')
|
344 | .use(CopyPlugin, [copyOptions])
|
345 | }
|
346 | }
|
347 |
|
348 | if (html !== false && mode !== 'test') {
|
349 | html = html || {}
|
350 | const htmls = Array.isArray(html) ? html : [html]
|
351 | const defaultHtml = {
|
352 | title: 'Poi',
|
353 | template: ownDir('lib/index.ejs'),
|
354 | env
|
355 | }
|
356 | htmls.forEach((h, i) => {
|
357 | config.plugin(`html-${i}`)
|
358 | .use(HtmlPlugin, [Object.assign({
|
359 | minify: {
|
360 | collapseWhitespace: minimize,
|
361 | minifyCSS: minimize,
|
362 | minifyJS: minimize
|
363 | }
|
364 | }, defaultHtml, h)])
|
365 | })
|
366 | }
|
367 |
|
368 |
|
369 | if (yarnGlobal.inDirectory(__dirname)) {
|
370 |
|
371 |
|
372 | config.resolve.modules.add(ownDir('..'))
|
373 |
|
374 | config.resolveLoader.modules.add(ownDir('..'))
|
375 | }
|
376 |
|
377 | return config
|
378 | }
|