1 | 'use strict'
|
2 |
|
3 | const fs = require('fs')
|
4 | const path = require('path')
|
5 | const webpack = require('webpack')
|
6 | const merge = require('webpack-merge')
|
7 | const chalk = require('chalk')
|
8 | const { localIp, isObject } = require('@mara/devkit')
|
9 | const TerserPlugin = require('terser-webpack-plugin')
|
10 | const CopyWebpackPlugin = require('copy-webpack-plugin')
|
11 | const HtmlWebpackPlugin = require('html-webpack-plugin')
|
12 | const safePostCssParser = require('postcss-safe-parser')
|
13 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
14 | const moduleDependency = require('sinamfe-webpack-module_dependency')
|
15 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
16 | const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
|
17 | const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
|
18 |
|
19 |
|
20 | const { banner, rootPath, getEntryPoints } = require('../lib/utils')
|
21 | const BuildProgressPlugin = require('../lib/BuildProgressPlugin')
|
22 | const InlineUmdHtmlPlugin = require('../lib/InlineUmdHtmlPlugin')
|
23 | const { GLOB, VIEWS_DIR, DLL_DIR, TARGET } = require('../config/const')
|
24 | const ManifestPlugin = require('../lib/hybrid/ManifestPlugin')
|
25 | const BuildJsonPlugin = require('../lib/BuildJsonPlugin')
|
26 | const ZenJsPlugin = require('../lib/ZenJsPlugin')
|
27 | const config = require('../config')
|
28 |
|
29 | const shouldUseSourceMap = !!config.build.sourceMap
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | module.exports = function(context, spinner) {
|
38 | const entry = context.entry
|
39 | const distPageDir = `${config.paths.dist}/${entry}`
|
40 | const baseWebpackConfig = require('./webpack.base.conf')(context, 'build')
|
41 | const htmlTemplatePath = `${config.paths.views}/${entry}/index.html`
|
42 | const hasHtml = fs.existsSync(htmlTemplatePath)
|
43 | const servantEntry = getEntryPoints(
|
44 | `${VIEWS_DIR}/${entry}/${GLOB.SERVANT_ENTRY}`
|
45 | )
|
46 | const debugLabel = config.debug ? '.debug' : ''
|
47 | const isHybridMode = context.target === TARGET.APP
|
48 | const shouldUseZenJs = config.compiler.zenJs && context.target != TARGET.APP
|
49 |
|
50 |
|
51 | const buildVersion =
|
52 | context.version || require(config.paths.packageJson).version
|
53 |
|
54 |
|
55 | const webpackConfig = merge(baseWebpackConfig, {
|
56 | mode: 'production',
|
57 |
|
58 | bail: true,
|
59 | devtool: shouldUseSourceMap ? 'source-map' : false,
|
60 |
|
61 | entry: servantEntry,
|
62 | output: {
|
63 | path: distPageDir,
|
64 | publicPath: context.publicPath,
|
65 |
|
66 | filename: config.hash.main
|
67 | ? `static/js/[name].[contenthash:8]${debugLabel}.js`
|
68 | : `static/js/[name]${debugLabel || '.min'}.js`,
|
69 | chunkFilename: config.hash.chunk
|
70 | ? `static/js/[name].[contenthash:8].chunk${debugLabel}.js`
|
71 | : `static/js/[name].chunk${debugLabel}.js`,
|
72 |
|
73 | devtoolModuleFilenameTemplate: info =>
|
74 | path
|
75 | .relative(config.paths.src, info.absoluteResourcePath)
|
76 | .replace(/\\/g, '/')
|
77 | },
|
78 |
|
79 | optimization: {
|
80 | minimize: config.debug !== true,
|
81 | minimizer: [
|
82 | new TerserPlugin({
|
83 | terserOptions: {
|
84 | parse: {
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | ecma: 8
|
91 | },
|
92 | compress: {
|
93 | ecma: 5,
|
94 | warnings: false,
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | comparisons: false,
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | inline: 2
|
105 | },
|
106 | mangle: {
|
107 | safari10: true
|
108 | },
|
109 | output: {
|
110 | ecma: 5,
|
111 | comments: false,
|
112 |
|
113 |
|
114 | ascii_only: true
|
115 | }
|
116 | },
|
117 |
|
118 |
|
119 | parallel: true,
|
120 |
|
121 | cache: true,
|
122 | sourceMap: shouldUseSourceMap
|
123 | }),
|
124 | new OptimizeCSSAssetsPlugin({
|
125 | cssProcessorOptions: {
|
126 | parser: safePostCssParser,
|
127 | map: shouldUseSourceMap
|
128 | ? {
|
129 |
|
130 |
|
131 | inline: false,
|
132 |
|
133 |
|
134 | annotation: true
|
135 | }
|
136 | : false
|
137 | },
|
138 | canPrint: false
|
139 | })
|
140 | ],
|
141 |
|
142 |
|
143 |
|
144 | runtimeChunk: false
|
145 | },
|
146 | plugins: [
|
147 |
|
148 |
|
149 | spinner &&
|
150 | new BuildProgressPlugin({
|
151 | spinner,
|
152 | name: 'Building',
|
153 | type: config.marax.progress
|
154 | }),
|
155 | hasHtml &&
|
156 | new HtmlWebpackPlugin({
|
157 |
|
158 | filename: 'index.html',
|
159 |
|
160 | template: htmlTemplatePath,
|
161 |
|
162 | inject: true,
|
163 | minify: {
|
164 | removeRedundantAttributes: true,
|
165 | useShortDoctype: true,
|
166 | removeEmptyAttributes: true,
|
167 | removeStyleLinkTypeAttributes: true,
|
168 | keepClosingSlash: true,
|
169 | minifyJS: true,
|
170 | minifyCSS: true,
|
171 | minifyURLs: true
|
172 | }
|
173 | }),
|
174 | hasHtml && new InlineUmdHtmlPlugin(HtmlWebpackPlugin),
|
175 | hasHtml && shouldUseZenJs && new ZenJsPlugin(HtmlWebpackPlugin),
|
176 | hasHtml &&
|
177 | new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
|
178 | hasHtml &&
|
179 | new InterpolateHtmlPlugin(HtmlWebpackPlugin, context.buildEnv.raw),
|
180 | new MiniCssExtractPlugin({
|
181 |
|
182 | filename: config.hash.main
|
183 | ? `static/css/[name].[contenthash:8]${debugLabel}.css`
|
184 | : `static/css/[name]${debugLabel || '.min'}.css`,
|
185 | chunkFilename: config.hash.chunk
|
186 | ? `static/css/[name].[contenthash:8].chunk${debugLabel}.css`
|
187 | : `static/css/[name].chunk${debugLabel}.css`
|
188 | }),
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 | new moduleDependency({
|
195 | emitError: config.compiler.checkDuplicatePackage
|
196 | }),
|
197 | new webpack.BannerPlugin({
|
198 | banner: banner(buildVersion, context.target),
|
199 | entryOnly: false
|
200 | }),
|
201 | new BuildJsonPlugin({
|
202 | debug: config.debug,
|
203 | target: context.target,
|
204 | env: config.deployEnv,
|
205 | version: buildVersion,
|
206 | marax: require(config.paths.maraxPackageJson).version
|
207 | }),
|
208 | new ManifestPlugin({
|
209 | entry,
|
210 | version: context.version,
|
211 | target: context.target
|
212 | }),
|
213 | ...copyPublicFiles(entry, distPageDir)
|
214 | ].filter(Boolean)
|
215 | })
|
216 |
|
217 |
|
218 | if (config.prerender) {
|
219 | const PrerenderSPAPlugin = require('prerender-html-plugin')
|
220 | const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
|
221 |
|
222 | new PrerenderSPAPlugin({
|
223 |
|
224 |
|
225 | entry: `${entry}`,
|
226 |
|
227 | staticDir: path.join(rootPath(`dist`), `${entry}`),
|
228 |
|
229 | outputDir: path.join(rootPath(`dist`), `${entry}`),
|
230 |
|
231 |
|
232 | routes: ['/'],
|
233 |
|
234 |
|
235 | renderer: new Renderer({
|
236 | inject: {
|
237 | foo: 'bar'
|
238 | },
|
239 | headless: false,
|
240 |
|
241 | renderAfterDocumentEvent: 'render-event'
|
242 | })
|
243 | })
|
244 | }
|
245 |
|
246 | const vendorConf = config.vendor || []
|
247 | if (Object.keys(vendorConf).length) {
|
248 | if (isObject(vendorConf) && !vendorConf.libs) {
|
249 | console.log(
|
250 | chalk.yellow(
|
251 | 'Build skip, vendor.libs is undefined. Please check marauder.config.js'
|
252 | )
|
253 | )
|
254 | process.exit(0)
|
255 | }
|
256 |
|
257 | let manifest = ''
|
258 |
|
259 | const namespace = config.vendor.name ? `${config.vendor.name}_` : ''
|
260 |
|
261 | try {
|
262 | manifest = require(`${config.paths.dll}/${namespace}manifest.json`)
|
263 | } catch (err) {
|
264 | console.log(
|
265 | chalk.yellow(
|
266 | `${DLL_DIR}/${namespace}manifest.json 未生成,请执行 npm run dll\n`
|
267 | )
|
268 | )
|
269 | process.exit(1)
|
270 | }
|
271 |
|
272 | webpackConfig.plugins.push(
|
273 | new webpack.DllReferencePlugin({
|
274 | manifest: manifest
|
275 | })
|
276 | )
|
277 | }
|
278 |
|
279 |
|
280 | if (config.build.report || config.build.writeStatsJson) {
|
281 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
|
282 |
|
283 | webpackConfig.plugins.push(
|
284 | new BundleAnalyzerPlugin({
|
285 | logLevel: 'warn',
|
286 | analyzerHost: localIp(),
|
287 | defaultSizes: 'gzip',
|
288 | analyzerMode: config.build.report ? 'server' : 'disabled',
|
289 | statsFilename: 'build-stats.json',
|
290 | generateStatsFile: config.build.writeStatsJson
|
291 | })
|
292 | )
|
293 | }
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 | if (isHybridMode) {
|
305 | const ZipPlugin = require('zip-webpack-plugin')
|
306 | webpackConfig.plugins.push(
|
307 | new ZipPlugin({
|
308 |
|
309 |
|
310 | filename: entry,
|
311 | extension: 'php',
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | exclude: [
|
319 | /__MACOSX$/,
|
320 | /.DS_Store$/,
|
321 | /dependencyGraph.json$/,
|
322 | /build.json$/,
|
323 | /js.map$/,
|
324 | /css.map$/
|
325 | ],
|
326 |
|
327 |
|
328 | fileOptions: {
|
329 | mtime: new Date(),
|
330 | mode: 0o100664,
|
331 | compress: true,
|
332 | forceZip64Format: false
|
333 | },
|
334 |
|
335 | zipOptions: {
|
336 | forceZip64Format: false
|
337 | }
|
338 | })
|
339 | )
|
340 | }
|
341 |
|
342 | return webpackConfig
|
343 | }
|
344 |
|
345 | function copyPublicFiles(entry, distPageDir) {
|
346 | const localPublicDir = rootPath(`${config.paths.views}/${entry}/public`)
|
347 | const plugins = []
|
348 |
|
349 | function getCopyOption(src) {
|
350 | return {
|
351 | from: src,
|
352 |
|
353 | to: distPageDir,
|
354 |
|
355 |
|
356 | ignore: ['.*', 'manifest.json']
|
357 | }
|
358 | }
|
359 |
|
360 |
|
361 | if (fs.existsSync(config.paths.public)) {
|
362 | plugins.push(new CopyWebpackPlugin([getCopyOption(config.paths.public)]))
|
363 | }
|
364 |
|
365 |
|
366 | if (fs.existsSync(localPublicDir)) {
|
367 | plugins.push(new CopyWebpackPlugin([getCopyOption(localPublicDir)]))
|
368 | }
|
369 |
|
370 | return plugins
|
371 | }
|