UNPKG

9.83 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 cssLoaders = require('./css-loaders')
12const webpackUtils = require('./webpack-utils')
13const {
14 getFileNames,
15 getPublicPath,
16 ownDir,
17 inferProductionValue,
18 stringifyObject
19} = require('./utils')
20const logger = require('./logger')
21
22module.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 // Do not resolve path like `:hot:` and `[hot]`
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 // Add /* filename */ comments to generated require()s in the output.
95 .pathinfo(true)
96 .filename(filename.js)
97 .chunkFilename(filename.chunk)
98 .publicPath(getPublicPath(mode, homepage))
99 // Point sourcemap entries to original disk location
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 // for anything outside node_modules
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 // Enforces the entire path of all required modules match
207 // The exact case of the actual path on disk
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 /* 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) {
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 // Add hmr entry (deprecated)
287 // Replace keywords like `[hot]` `:hot:` with hmr entry
288 // This will be removed in next major version
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 // Add hmr entry using `hotEntry` option
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 // installed by `yarn global add`
360 if (yarnGlobal.inDirectory(__dirname)) {
361 // modules in yarn global node_modules
362 // because of yarn's flat node_modules structure
363 config.resolve.modules.add(ownDir('..'))
364 // loaders in yarn global node_modules
365 config.resolveLoader.modules.add(ownDir('..'))
366 }
367
368 return config
369}