UNPKG

10.2 kBJavaScriptView Raw
1const fs = require('fs')
2const path = require('path')
3const webpack = require('webpack')
4const Config = require('webpack-chain')
5const merge = require('lodash/merge')
6const ExtractTextPlugin = require('extract-text-webpack-plugin')
7const CopyPlugin = require('copy-webpack-plugin')
8const HtmlPlugin = require('html-webpack-plugin')
9const PathsCaseSensitivePlugin = require('case-sensitive-paths-webpack-plugin')
10const yarnGlobal = require('yarn-global')
11const FancyLogPlugin = require('./webpack/fancy-log-plugin')
12const webpackUtils = require('./webpack-utils')
13const {
14 getFileNames,
15 getPublicPath,
16 ownDir,
17 inferProductionValue,
18 stringifyObject,
19 getFullEnvString
20} = require('./utils')
21const logger = require('./logger')
22const cssLoaders = require('./webpack/css-loaders')
23const transformJS = require('./webpack/transform-js')
24const transformVue = require('./webpack/transform-vue')
25
26module.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 // Do not resolve path like `:hot:` and `[hot]`
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 // Add /* filename */ comments to generated require()s in the output.
105 .pathinfo(true)
106 .filename(filename.js)
107 .chunkFilename(filename.chunk)
108 .publicPath(getPublicPath(mode, homepage))
109 // Point sourcemap entries to original disk location
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 // Rules for CSS/Stylus/Sass...
155 cssLoaders.standalone(config, cssOptions)
156 // Rules for JS/JSX/ES6
157 transformJS(config, { babel, transformModules })
158 // Rules for Vue single-file component
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 // inline the file if < max size
169 limit: inlineImageMaxSize
170 })
171 .end()
172 .end()
173 // SVG files use file-loader directly, why?
174 // See https://github.com/facebookincubator/create-react-app/pull/1180
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 // Enforces the entire path of all required modules match
193 // The exact case of the actual path on disk
194 config.plugin('paths-case-sensitive')
195 .use(PathsCaseSensitivePlugin)
196
197 config.plugin('constants')
198 .use(webpack.DefinePlugin, [
199 merge(
200 // { foo: '"foo"' } => { 'process.env.foo': '"foo"' }
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 /* eslint-disable camelcase */
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 /* eslint-enable camelcase */
265 }])
266 }
267
268 // Do not split vendor code in `cjs` and `umd` mode
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 // Add hmr entry (deprecated)
296 // Replace keywords like `[hot]` `:hot:` with hmr entry
297 // This will be removed in next major version
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 // Add hmr entry using `hotEntry` option
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 // installed by `yarn global add`
369 if (yarnGlobal.inDirectory(__dirname)) {
370 // modules in yarn global node_modules
371 // because of yarn's flat node_modules structure
372 config.resolve.modules.add(ownDir('..'))
373 // loaders in yarn global node_modules
374 config.resolveLoader.modules.add(ownDir('..'))
375 }
376
377 return config
378}