UNPKG

12.2 kBJavaScriptView Raw
1'use strict'
2
3const path = require('path')
4const fs = require('fs')
5const webpack = require('webpack')
6const merge = require('webpack-merge')
7const chalk = require('chalk')
8const CopyWebpackPlugin = require('copy-webpack-plugin-hash')
9const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
10const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
11const HtmlWebpackPlugin = require('sina-html-webpack-plugin')
12const ExtractTextPlugin = require('extract-text-webpack-plugin')
13const moduleDependency = require('sinamfe-webpack-module_dependency')
14const { HybridCommonPlugin } = require('../libs/hybrid')
15const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
16const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin')
17const ZenJsPlugin = require('../libs/ZenJsPlugin')
18
19const config = require('../config')
20const { banner, rootPath, getChunks, isObject } = require('../libs/utils')
21
22const maraConf = require(config.paths.marauder)
23const shouldUseSourceMap = !!maraConf.sourceMap
24const isHybridMode = config.build.env.raw['jsbridgeBuildType'] === 'app'
25
26/**
27 * 生成生产配置
28 * @param {String} options.entry 页面名称
29 * @param {String} options.cmd 当前命令
30 * @return {Object} webpack 配置对象
31 */
32module.exports = function({ entry, cmd }) {
33 const distPageDir = `${config.paths.dist}/${entry}`
34 const baseWebpackConfig = require('./webpack.base.conf')(entry, 'build')
35 const hasHtml = fs.existsSync(`${config.paths.page}/${entry}/index.html`)
36 const chunksEntry = getChunks(`src/view/${entry}/index.*.js`)
37 const debugLabel = config.debug ? '.debug' : ''
38 const shouldUseZenJs = config.compiler.zenJs && !isHybridMode
39
40 // https://github.com/survivejs/webpack-merge
41 const webpackConfig = merge(baseWebpackConfig, {
42 // 在第一个错误出错时抛出,而不是无视错误
43 bail: true,
44 devtool: shouldUseSourceMap ? 'source-map' : false,
45 entry: chunksEntry,
46 output: {
47 path: distPageDir,
48 publicPath: config.build.assetsPublicPath,
49 filename: maraConf.hash
50 ? `static/js/[name].[chunkhash:8]${debugLabel}.js`
51 : `static/js/[name]${debugLabel || '.min'}.js`,
52 chunkFilename: maraConf.chunkHash
53 ? `static/js/[name].[chunkhash:8].async${debugLabel}.js`
54 : `static/js/[name].async${debugLabel}.js`
55 },
56 plugins: [
57 new InterpolateHtmlPlugin(config.build.env.raw),
58 new webpack.DefinePlugin(config.build.env.stringified),
59 // 使作作用域提升(scope hoisting)
60 // https://medium.com/webpack/brief-introduction-to-scope-hoisting-in-webpack-8435084c171f
61 new webpack.optimize.ModuleConcatenationPlugin(),
62 config.debug && new webpack.NamedModulesPlugin(),
63 // Minify the code.
64 new UglifyJsPlugin({
65 uglifyOptions: {
66 // 强制使用 es5 压缩输出,避免 es6 优化导致兼容性问题
67 ecma: 5,
68 compress: {
69 warnings: false,
70 // Disabled because of an issue with Uglify breaking seemingly valid code:
71 // https://github.com/facebook/create-react-app/issues/2376
72 // Pending further investigation:
73 // https://github.com/mishoo/UglifyJS2/issues/2011
74 comparisons: false,
75 drop_console: !config.debug && config.compiler.dropConsole,
76 join_vars: !config.debug
77 },
78 mangle: config.debug
79 ? false
80 : {
81 safari10: true
82 },
83 keep_fnames: config.debug,
84 output: {
85 comments: false,
86 // Turned on because emoji and regex is not minified properly using default
87 // https://github.com/facebook/create-react-app/issues/2488
88 ascii_only: true,
89 comments: config.debug && /(\sMODULE)|(^\s\d+\s$)/,
90 beautify: config.debug
91 }
92 },
93 // Use multi-process parallel running to improve the build speed
94 // Default number of concurrent runs: os.cpus().length - 1
95 parallel: true,
96 // Enable file caching
97 cache: true,
98 sourceMap: shouldUseSourceMap
99 }),
100 new ExtractTextPlugin({
101 filename: maraConf.hash
102 ? 'static/css/[name].[contenthash:8].css'
103 : 'static/css/[name].min.css'
104 }),
105 // hybrid 共享包
106 // 创建 maraContext
107 new HybridCommonPlugin(),
108 new OptimizeCssAssetsPlugin({
109 // cssnano 中自带 autoprefixer,在压缩时会根据配置去除无用前缀
110 // 为保持统一,将其禁用,在 4.0 版本后将会默认禁用
111 // safe: true 禁止计算 z-index
112 cssProcessor: require('cssnano'),
113 cssProcessorOptions: Object.assign(
114 // { autoprefixer: false, safe: true },
115 shouldUseSourceMap
116 ? {
117 map: { inline: false }
118 }
119 : {}
120 ),
121 canPrint: false // 不显示通知
122 }),
123 // Moment.js is an extremely popular library that bundles large locale files
124 // by default due to how Webpack interprets its code. This is a practical
125 // solution that requires the user to opt into importing specific locales.
126 // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
127 // You can remove this if you don't use Moment.js:
128 new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
129 hasHtml &&
130 new HtmlWebpackPlugin({
131 // 生成出来的html文件名
132 filename: rootPath(`dist/${entry}/index.html`),
133 // 每个html的模版,这里多个页面使用同一个模版
134 template: `${config.paths.page}/${entry}/index.html`,
135 minify: false,
136 // 自动将引用插入html
137 inject: true,
138 // 模块排序,common > entry > servant
139 chunksSortMode(a, b) {
140 const chunkNames = Object.keys(chunksEntry).sort()
141 const order = ['common', entry].concat(chunkNames)
142
143 return order.indexOf(a.names[0]) - order.indexOf(b.names[0])
144 },
145 collapseWhitespace: true,
146 removeRedundantAttributes: true,
147 useShortDoctype: true,
148 removeEmptyAttributes: true,
149 removeStyleLinkTypeAttributes: true,
150 keepClosingSlash: true
151 }),
152 hasHtml && shouldUseZenJs && new ZenJsPlugin(),
153 // 【争议】:lib 模式禁用依赖分析?
154 new moduleDependency({
155 emitError: config.compiler.checkDuplicatePackage !== false
156 }),
157 new DuplicatePackageCheckerPlugin({
158 // show details
159 verbose: true,
160 showHelp: false,
161 // throwt error
162 emitError: config.compiler.checkDuplicatePackage,
163 // check major version
164 strict: true
165 }),
166 new webpack.BannerPlugin({
167 banner: banner(), // 其值为字符串,将作为注释存在
168 entryOnly: false // 如果值为 true,将只在入口 chunks 文件中添加
169 }),
170 ...copyPublicFiles(entry, distPageDir)
171 ].filter(Boolean)
172 })
173
174 //预加载
175 if (maraConf.prerender) {
176 const PrerenderSPAPlugin = require('prerender-html-plugin')
177 const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
178
179 new PrerenderSPAPlugin({
180 // 生成文件的路径,也可以与webpakc打包的一致。
181 // 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
182 entry: `${entry}`,
183
184 staticDir: path.join(rootPath(`dist`), `${entry}`),
185
186 outputDir: path.join(rootPath(`dist`), `${entry}`),
187
188 // 对应自己的路由文件,比如index有参数,就需要写成 /index/param1。
189 routes: ['/'],
190
191 // 这个很重要,如果没有配置这段,也不会进行预编译
192 renderer: new Renderer({
193 inject: {
194 foo: 'bar'
195 },
196 headless: false,
197 // 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
198 renderAfterDocumentEvent: 'render-event'
199 })
200 })
201 }
202
203 if (maraConf.ensurels) {
204 const ensure_ls = require('sinamfe-marauder-ensure-ls')
205 webpackConfig.plugins.push(new ensure_ls())
206 }
207
208 const vendorConf = maraConf.vendor || []
209 if (Object.keys(vendorConf).length) {
210 if (isObject(vendorConf) && !vendorConf.libs) {
211 console.log(
212 chalk.yellow(
213 'Build skip, vendor.libs is undefined. Please check marauder.config.js'
214 )
215 )
216 process.exit(0)
217 }
218
219 let manifest = ''
220 // 为多页面准备,生成 xxx_vender 文件夹
221 const namespace = maraConf.vendor.name ? `${maraConf.vendor.name}_` : ''
222
223 try {
224 manifest = require(`${config.paths.dll}/${namespace}manifest.json`)
225 } catch (err) {
226 console.log(
227 chalk.yellow(
228 `dll/${namespace}manifest.json 未生成,请执行 npm run dll\n`
229 )
230 )
231 process.exit(1)
232 }
233
234 webpackConfig.plugins.push(
235 new webpack.DllReferencePlugin({
236 manifest: manifest
237 })
238 )
239 }
240
241 // bundle 大小分析
242 if (config.build.bundleAnalyzerReport) {
243 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
244 .BundleAnalyzerPlugin
245 webpackConfig.plugins.push(new BundleAnalyzerPlugin())
246 }
247
248 // @TODO publish npm module
249 // 生成serviceworker
250 // if (maraConf.sw) {
251 // const webpackWS = require('@mfelibs/webpack-create-serviceworker')
252 // const swConfig = maraConf.sw_config || {}
253 // webpackConfig.plugins.push(new webpackWS(swConfig))
254 // }
255
256 // 重要:确保 zip plugin 在插件列表末尾
257 if (maraConf.zip === true || maraConf.hybrid) {
258 const ZipPlugin = require('zip-webpack-plugin')
259 webpackConfig.plugins.push(
260 new ZipPlugin({
261 // OPTIONAL: defaults to the Webpack output filename (above) or,
262 // if not present, the basename of the path
263 filename: entry,
264 // OPTIONAL: defaults to 'zip'
265 // the file extension to use instead of 'zip'
266 // 对 hybrid 项目使用 php 后缀,防止 CDN 劫持(?)
267 extension: maraConf.hybrid ? 'php' : 'zip',
268 // OPTIONAL: defaults to including everything
269 // can be a string, a RegExp, or an array of strings and RegExps
270 // include: [/\.js$/],
271 // OPTIONAL: defaults to excluding nothing
272 // can be a string, a RegExp, or an array of strings and RegExps
273 // if a file matches both include and exclude, exclude takes precedence
274 exclude: maraConf.debug
275 ? [
276 /__MACOSX$/,
277 /.DS_Store$/,
278 /dependencyGraph.json$/,
279 /debug.css$/,
280 /build.json$/,
281 /js.map$/,
282 /css.map$/
283 ]
284 : [
285 /__MACOSX$/,
286 /.DS_Store$/,
287 /dependencyGraph.json$/,
288 /debug.js$/,
289 /debug.css$/,
290 /build.json$/,
291 /js.map$/,
292 /css.map$/
293 ],
294
295 // yazl Options
296 // OPTIONAL: see https://github.com/thejoshwolfe/yazl#addfilerealpath-metadatapath-options
297 fileOptions: {
298 mtime: new Date(),
299 mode: 0o100664,
300 compress: true,
301 forceZip64Format: false
302 },
303 // OPTIONAL: see https://github.com/thejoshwolfe/yazl#endoptions-finalsizecallback
304 zipOptions: {
305 forceZip64Format: false
306 }
307 })
308 )
309 }
310
311 return webpackConfig
312}
313
314function copyPublicFiles(entry, distPageDir) {
315 const pagePublicDir = rootPath(`${config.paths.page}/${entry}/public`)
316 const plugins = []
317
318 function getCopyOption(src) {
319 return {
320 from: src,
321 // 放置于根路径
322 to: distPageDir,
323 ignore: ['.*']
324 }
325 }
326
327 // 全局 public
328 if (fs.existsSync(config.paths.public)) {
329 plugins.push(new CopyWebpackPlugin([getCopyOption(config.paths.public)]))
330 }
331
332 // 页面级 public,能够覆盖全局 public
333 if (fs.existsSync(pagePublicDir)) {
334 plugins.push(new CopyWebpackPlugin([getCopyOption(pagePublicDir)]))
335 }
336
337 return plugins
338}