UNPKG

34.9 kBJavaScriptView Raw
1'use strict'
2
3const path = require('path')
4const ConcatSource = require('webpack-sources').ConcatSource
5const RawSource = require('webpack-sources').RawSource
6const ResolveDependency = require('./dependency/ResolveDependency')
7const InjectDependency = require('./dependency/InjectDependency')
8const ReplaceDependency = require('./dependency/ReplaceDependency')
9const NullFactory = require('webpack/lib/NullFactory')
10const normalize = require('./utils/normalize')
11const toPosix = require('./utils/to-posix')
12const addQuery = require('./utils/add-query')
13const DefinePlugin = require('webpack/lib/DefinePlugin')
14const ExternalsPlugin = require('webpack/lib/ExternalsPlugin')
15const AddModePlugin = require('./resolver/AddModePlugin')
16const CommonJsRequireDependency = require('webpack/lib/dependencies/CommonJsRequireDependency')
17const HarmonyImportSideEffectDependency = require('webpack/lib/dependencies/HarmonyImportSideEffectDependency')
18const RequireHeaderDependency = require('webpack/lib/dependencies/RequireHeaderDependency')
19const RemovedModuleDependency = require('./dependency/RemovedModuleDependency')
20const SplitChunksPlugin = require('webpack/lib/optimize/SplitChunksPlugin')
21const fixRelative = require('./utils/fix-relative')
22const parseRequest = require('./utils/parse-request')
23const matchCondition = require('./utils/match-condition')
24
25const isProductionLikeMode = options => {
26 return options.mode === 'production' || !options.mode
27}
28
29const outputFilename = '[name].js'
30const publicPath = '/'
31
32function isChunkInPackage (chunkName, packageName) {
33 return (new RegExp(`^${packageName}\\/`)).test(chunkName)
34}
35
36function getPackageCacheGroup (packageName) {
37 if (packageName === 'main') {
38 return {
39 name: 'bundle',
40 minChunks: 2,
41 chunks: 'all'
42 }
43 } else {
44 return {
45 test: (module, chunks) => {
46 return chunks.every((chunk) => {
47 return isChunkInPackage(chunk.name, packageName)
48 })
49 },
50 name: `${packageName}/bundle`,
51 minChunks: 2,
52 minSize: 1000,
53 priority: 100,
54 chunks: 'all'
55 }
56 }
57}
58
59let loaderOptions
60
61const externalsMap = {
62 weui: /^weui-miniprogram/
63}
64
65const warnings = []
66const errors = []
67
68class MpxWebpackPlugin {
69 constructor (options = {}) {
70 options.mode = options.mode || 'wx'
71
72 options.srcMode = options.srcMode || options.mode
73 if (options.mode !== options.srcMode && options.srcMode !== 'wx') {
74 errors.push('MpxWebpackPlugin supports srcMode to be "wx" only temporarily!')
75 }
76 if (options.mode === 'web' && options.srcMode !== 'wx') {
77 errors.push('MpxWebpackPlugin supports mode to be "web" only when srcMode is set to "wx"!')
78 }
79 if (!Array.isArray(options.externalClasses)) {
80 options.externalClasses = ['custom-class', 'i-class']
81 }
82
83 options.externalClasses = options.externalClasses.map((className) => {
84 return {
85 className,
86 replacement: className.replace(/-(.)/g, (matched, $1) => {
87 return $1.toUpperCase()
88 })
89 }
90 })
91 options.resolveMode = options.resolveMode || 'webpack'
92 options.writeMode = options.writeMode || 'changed'
93 options.autoScopeRules = options.autoScopeRules || {}
94 options.forceDisableInject = options.forceDisableInject || false
95 options.forceDisableProxyCtor = options.forceDisableProxyCtor || false
96 options.transMpxRules = options.transMpxRules || {
97 include: () => true
98 }
99 if (options.autoSplit === undefined) {
100 // web模式下默认不开启autoSplit
101 options.autoSplit = options.mode !== 'web'
102 }
103 // 通过默认defs配置实现mode及srcMode的注入,简化内部处理逻辑
104 options.defs = Object.assign({}, options.defs, {
105 '__mpx_mode__': options.mode,
106 '__mpx_src_mode__': options.srcMode
107 })
108 // 批量指定源码mode
109 options.modeRules = options.modeRules || {}
110 options.generateBuildMap = options.generateBuildMap || false
111 options.attributes = options.attributes || []
112 options.externals = (options.externals || []).map((external) => {
113 return externalsMap[external] || external
114 })
115 options.forceUsePageCtor = options.forceUsePageCtor || false
116 options.postcssInlineConfig = options.postcssInlineConfig || {}
117 options.transRpxRules = options.transRpxRules || null
118 options.auditResource = options.auditResource || false
119 this.options = options
120 }
121
122 static loader (options = {}) {
123 loaderOptions = options
124 if (loaderOptions.transRpx) {
125 warnings.push('Mpx loader option [transRpx] is deprecated now, please use mpx webpack plugin config [transRpxRules] instead!')
126 }
127 return { loader: normalize.lib('loader'), options }
128 }
129
130 static pluginLoader (options = {}) {
131 return { loader: normalize.lib('plugin-loader'), options }
132 }
133
134 static wxsPreLoader (options = {}) {
135 return { loader: normalize.lib('wxs/wxs-pre-loader'), options }
136 }
137
138 static urlLoader (options = {}) {
139 return { loader: normalize.lib('url-loader'), options }
140 }
141
142 static fileLoader (options = {}) {
143 return { loader: normalize.lib('file-loader'), options }
144 }
145
146 runModeRules (request) {
147 const { resourcePath, queryObj } = parseRequest(request)
148 if (queryObj.mode) {
149 return request
150 }
151 const mode = this.options.mode
152 const modeRule = this.options.modeRules[mode]
153 if (!modeRule) {
154 return request
155 }
156 if (matchCondition(resourcePath, modeRule)) {
157 return addQuery(request, { mode })
158 }
159 return request
160 }
161
162 apply (compiler) {
163 if (!compiler.__mpx__) {
164 compiler.__mpx__ = true
165 } else {
166 errors.push('Multiple MpxWebpackPlugin instances exist in webpack compiler, please check webpack plugins config!')
167 }
168
169 if (this.options.mode !== 'web') {
170 // 强制设置publicPath为'/'
171 if (compiler.options.output.publicPath && compiler.options.output.publicPath !== publicPath) {
172 warnings.push(`webpack options: MpxWebpackPlugin accept options.output.publicPath to be ${publicPath} only, custom options.output.publicPath will be ignored!`)
173 }
174 compiler.options.output.publicPath = publicPath
175 if (compiler.options.output.filename && compiler.options.output.filename !== outputFilename) {
176 warnings.push(`webpack options: MpxWebpackPlugin accept options.output.filename to be ${outputFilename} only, custom options.output.filename will be ignored!`)
177 }
178 compiler.options.output.filename = compiler.options.output.chunkFilename = outputFilename
179 }
180
181 if (!compiler.options.node || !compiler.options.node.global) {
182 compiler.options.node = compiler.options.node || {}
183 compiler.options.node.global = true
184 warnings.push(`webpack options: MpxWebpackPlugin strongly depends options.node.globel to be true, custom options.node will be ignored!`)
185 }
186
187 const resolvePlugin = new AddModePlugin('before-resolve', this.options.mode, 'resolve')
188
189 if (Array.isArray(compiler.options.resolve.plugins)) {
190 compiler.options.resolve.plugins.push(resolvePlugin)
191 } else {
192 compiler.options.resolve.plugins = [resolvePlugin]
193 }
194
195 let splitChunksPlugin
196 let splitChunksOptions
197
198 if (this.options.autoSplit) {
199 compiler.options.optimization.runtimeChunk = {
200 name: 'bundle'
201 }
202 splitChunksOptions = compiler.options.optimization.splitChunks
203 delete compiler.options.optimization.splitChunks
204 splitChunksPlugin = new SplitChunksPlugin(splitChunksOptions)
205 splitChunksPlugin.apply(compiler)
206 }
207
208 // 代理writeFile
209 if (this.options.writeMode === 'changed') {
210 const writedFileContentMap = new Map()
211 const originalWriteFile = compiler.outputFileSystem.writeFile
212 compiler.outputFileSystem.writeFile = (filePath, content, callback) => {
213 if (writedFileContentMap.has(filePath) && writedFileContentMap.get(filePath).equals(content)) {
214 return callback()
215 }
216 writedFileContentMap.set(filePath, content)
217 originalWriteFile(filePath, content, callback)
218 }
219 }
220 const defs = this.options.defs
221
222 const defsOpt = {
223 '__mpx_wxs__': DefinePlugin.runtimeValue(({ module }) => {
224 return JSON.stringify(!!module.wxs)
225 })
226 }
227
228 Object.keys(defs).forEach((key) => {
229 defsOpt[key] = JSON.stringify(defs[key])
230 })
231
232 // define mode & defs
233 new DefinePlugin(defsOpt).apply(compiler)
234
235 new ExternalsPlugin('commonjs2', this.options.externals).apply(compiler)
236
237 compiler.hooks.compilation.tap('MpxWebpackPlugin ', (compilation) => {
238 compilation.hooks.normalModuleLoader.tap('MpxWebpackPlugin', (loaderContext, module) => {
239 // 设置loaderContext的minimize
240 if (isProductionLikeMode(compiler.options)) {
241 loaderContext.minimize = true
242 }
243 })
244 })
245
246 let mpx
247
248 // staticResourceHit需要长效保持记录哪些资源是静态资源,避免后续误用缓存
249 const staticResourceHit = {}
250
251 compiler.hooks.thisCompilation.tap('MpxWebpackPlugin', (compilation, { normalModuleFactory }) => {
252 compilation.warnings = compilation.warnings.concat(warnings)
253 compilation.errors = compilation.errors.concat(errors)
254 // additionalAssets和mpx由于包含缓存机制,必须在每次compilation时重新初始化
255 const additionalAssets = {}
256 if (!compilation.__mpx__) {
257 // init mpx
258 mpx = compilation.__mpx__ = {
259 // pages全局记录,无需区分主包分包
260 pagesMap: {},
261 // 记录pages对应的entry,处理多appEntry输出web多页项目时可能出现的pagePath冲突的问题,多appEntry输出目前仅web模式支持
262 pagesEntryMap: {},
263 // 组件资源记录,依照所属包进行记录,冗余存储,只要某个包有引用会添加对应记录,不管其会不会在当前包输出,这样设计主要是为了在resolve时能够以较低成本找到特定资源的输出路径
264 componentsMap: {
265 main: {}
266 },
267 // 静态资源(图片,字体,独立样式)等,依照所属包进行记录,冗余存储,同上
268 staticResourceMap: {
269 main: {}
270 },
271 // 记录静态资源首次命中的分包,当有其他分包再次引用了同样的静态资源时,对其request添加packageName query以避免模块缓存导致loader不再执行
272 staticResourceHit,
273 loaderOptions,
274 extractedMap: {},
275 extractSeenFile: {},
276 usingComponents: [],
277 hasApp: false,
278 // todo es6 map读写性能高于object,之后会逐步替换
279 vueContentCache: new Map(),
280 currentPackageRoot: '',
281 wxsMap: {},
282 wxsConentMap: {},
283 forceDisableInject: this.options.forceDisableInject,
284 forceUsePageCtor: this.options.forceUsePageCtor,
285 resolveMode: this.options.resolveMode,
286 mode: this.options.mode,
287 srcMode: this.options.srcMode,
288 globalMpxAttrsFilter: this.options.globalMpxAttrsFilter,
289 externalClasses: this.options.externalClasses,
290 projectRoot: this.options.projectRoot,
291 autoScopeRules: this.options.autoScopeRules,
292 transRpxRules: this.options.transRpxRules,
293 postcssInlineConfig: this.options.postcssInlineConfig,
294 // native文件专用相关配置
295 nativeOptions: Object.assign({
296 cssLangs: ['css', 'less', 'stylus', 'scss', 'sass']
297 }, this.options.nativeOptions),
298 defs: this.options.defs,
299 i18n: this.options.i18n,
300 appTitle: 'Mpx homepage',
301 attributes: this.options.attributes,
302 externals: this.options.externals,
303 extract: (content, file, index, sideEffects) => {
304 additionalAssets[file] = additionalAssets[file] || []
305 if (!additionalAssets[file][index]) {
306 additionalAssets[file][index] = content
307 }
308 sideEffects && sideEffects(additionalAssets)
309 },
310 // 组件和静态资源的输出规则如下:
311 // 1. 主包引用的资源输出至主包
312 // 2. 分包引用且主包引用过的资源输出至主包,不在当前分包重复输出
313 // 3. 分包引用且无其他包引用的资源输出至当前分包
314 // 4. 分包引用且其他分包也引用过的资源,重复输出至当前分包
315 // 5. 当用户通过packageName query显式指定了资源的所属包时,输出至指定的包
316 getPackageInfo: (resource, { outputPath, isStatic, error, warn }) => {
317 let packageRoot = ''
318 let packageName = 'main'
319 const currentPackageRoot = mpx.currentPackageRoot
320 const currentPackageName = currentPackageRoot || 'main'
321 const { resourcePath, queryObj } = parseRequest(resource)
322 const resourceMap = isStatic ? mpx.staticResourceMap : mpx.componentsMap
323 // 主包中有引用一律使用主包中资源,不再额外输出
324 if (!resourceMap.main[resourcePath]) {
325 if (queryObj.packageName) {
326 packageName = queryObj.packageName
327 packageRoot = packageName === 'main' ? '' : packageName
328 if (packageName !== currentPackageName && packageName !== 'main') {
329 error && error(new Error(`根据小程序分包资源引用规则,资源只支持声明为当前分包或者主包,否则可能会导致资源无法引用的问题,当前资源的当前分包为${currentPackageName},资源查询字符串声明的分包为${packageName},请检查!`))
330 }
331 } else if (currentPackageRoot) {
332 packageName = packageRoot = currentPackageRoot
333 }
334
335 if (this.options.auditResource) {
336 if (this.options.auditResource !== 'component' || !isStatic) {
337 Object.keys(resourceMap).filter(key => key !== 'main').forEach((key) => {
338 if (resourceMap[key][resourcePath] && key !== packageName) {
339 warn && warn(new Error(`当前${isStatic ? '静态' : '组件'}资源${resourcePath}在分包${key}和分包${packageName}中都有引用,会分别输出到两个分包中,为了总体积最优,可以在主包中建立引用声明以消除资源输出冗余!`))
340 }
341 })
342 }
343 }
344 }
345
346 outputPath = toPosix(path.join(packageRoot, outputPath))
347
348 const currentResourceMap = resourceMap[currentPackageName]
349 const actualResourceMap = resourceMap[packageName]
350
351 let alreadyOutputed = false
352 // 如果之前已经进行过输出,则不需要重复进行
353 if (actualResourceMap[resourcePath]) {
354 outputPath = actualResourceMap[resourcePath]
355 alreadyOutputed = true
356 }
357 // 将当前的currentResourceMap和实际进行输出的actualResourceMap都填充上,便于resolve时使用
358 currentResourceMap[resourcePath] = actualResourceMap[resourcePath] = outputPath
359
360 if (isStatic && packageName !== 'main' && !mpx.staticResourceHit[resourcePath]) {
361 mpx.staticResourceHit[resourcePath] = packageName
362 }
363
364 return {
365 packageName,
366 packageRoot,
367 resourcePath,
368 queryObj,
369 outputPath,
370 alreadyOutputed
371 }
372 }
373 }
374 }
375
376 compilation.hooks.finishModules.tap('MpxWebpackPlugin', (modules) => {
377 // 自动跟进分包配置修改splitChunksPlugin配置
378 if (splitChunksPlugin) {
379 let needInit = false
380 Object.keys(mpx.componentsMap).forEach((packageName) => {
381 if (!splitChunksOptions.cacheGroups.hasOwnProperty(packageName)) {
382 needInit = true
383 splitChunksOptions.cacheGroups[packageName] = getPackageCacheGroup(packageName)
384 }
385 })
386 if (needInit) {
387 splitChunksPlugin.options = SplitChunksPlugin.normalizeOptions(splitChunksOptions)
388 }
389 }
390 })
391
392 compilation.hooks.optimizeModules.tap('MpxWebpackPlugin', (modules) => {
393 modules.forEach((module) => {
394 if (module.needRemove) {
395 let removed = false
396 module.reasons.forEach((reason) => {
397 if (reason.module) {
398 if (reason.dependency instanceof HarmonyImportSideEffectDependency) {
399 reason.module.removeDependency(reason.dependency)
400 reason.module.addDependency(new RemovedModuleDependency(reason.dependency.request))
401 removed = true
402 } else if (reason.dependency instanceof CommonJsRequireDependency && reason.dependency.loc.range) {
403 let index = reason.module.dependencies.indexOf(reason.dependency)
404 if (index > -1 && reason.module.dependencies[index + 1] instanceof RequireHeaderDependency) {
405 reason.module.dependencies.splice(index, 2)
406 reason.module.addDependency(new RemovedModuleDependency(reason.dependency.request, reason.dependency.loc.range))
407 removed = true
408 }
409 }
410 }
411 })
412 if (removed) {
413 module.chunksIterable.forEach((chunk) => {
414 module.removeChunk(chunk)
415 })
416 module.disconnect()
417 }
418 }
419 })
420 })
421
422 compilation.moduleTemplates.javascript.hooks.content.tap('MpxWebpackPlugin', (source, module, options) => {
423 // 处理dll产生的external模块
424 if (module.external && module.userRequest.startsWith('dll-reference ') && mpx.mode !== 'web') {
425 const chunk = options.chunk
426 const request = module.request
427 let relativePath = path.relative(path.dirname(chunk.name), request)
428 if (!/^\.\.?\//.test(relativePath)) relativePath = './' + relativePath
429 if (chunk) {
430 return new RawSource(`module.exports = require("${relativePath}");\n`)
431 }
432 }
433 return source
434 })
435
436 compilation.hooks.additionalAssets.tapAsync('MpxWebpackPlugin', (callback) => {
437 for (let file in additionalAssets) {
438 let content = new ConcatSource()
439 if (additionalAssets[file].prefix) {
440 additionalAssets[file].prefix.forEach((item) => {
441 content.add(item)
442 })
443 }
444 additionalAssets[file].forEach((item) => {
445 content.add(item)
446 })
447 compilation.assets[file] = content
448 }
449 callback()
450 })
451
452 compilation.dependencyFactories.set(ResolveDependency, new NullFactory())
453 compilation.dependencyTemplates.set(ResolveDependency, new ResolveDependency.Template())
454
455 compilation.dependencyFactories.set(InjectDependency, new NullFactory())
456 compilation.dependencyTemplates.set(InjectDependency, new InjectDependency.Template())
457
458 compilation.dependencyFactories.set(ReplaceDependency, new NullFactory())
459 compilation.dependencyTemplates.set(ReplaceDependency, new ReplaceDependency.Template())
460
461 compilation.dependencyFactories.set(RemovedModuleDependency, normalModuleFactory)
462 compilation.dependencyTemplates.set(RemovedModuleDependency, new RemovedModuleDependency.Template())
463
464 normalModuleFactory.hooks.parser.for('javascript/auto').tap('MpxWebpackPlugin', (parser) => {
465 // hack预处理,将expr.range写入loc中便于在CommonJsRequireDependency中获取,移除无效require
466 parser.hooks.call.for('require').tap({ name: 'MpxWebpackPlugin', stage: -100 }, (expr) => {
467 expr.loc.range = expr.range
468 })
469
470 parser.hooks.call.for('__mpx_resolve_path__').tap('MpxWebpackPlugin', (expr) => {
471 if (expr.arguments[0]) {
472 const resource = expr.arguments[0].value
473 const { queryObj } = parseRequest(resource)
474 const packageName = queryObj.packageName
475 const pagesMap = mpx.pagesMap
476 const componentsMap = mpx.componentsMap
477 const staticResourceMap = mpx.staticResourceMap
478 const publicPath = mpx.mode === 'web' ? '' : compilation.outputOptions.publicPath
479 const range = expr.range
480 const issuerResource = parser.state.module.issuer.resource
481 const dep = new ResolveDependency(resource, packageName, pagesMap, componentsMap, staticResourceMap, publicPath, range, issuerResource)
482 parser.state.current.addDependency(dep)
483 return true
484 }
485 })
486
487 const transHandler = (expr) => {
488 const module = parser.state.module
489 const current = parser.state.current
490 const { queryObj, resourcePath } = parseRequest(module.resource)
491 const localSrcMode = queryObj.mode
492 const globalSrcMode = this.options.srcMode
493 const srcMode = localSrcMode || globalSrcMode
494 const mode = this.options.mode
495
496 let target
497
498 if (expr.type === 'Identifier') {
499 target = expr
500 } else if (expr.type === 'MemberExpression') {
501 target = expr.object
502 }
503 if (!matchCondition(resourcePath, this.options.transMpxRules) || resourcePath.indexOf('@mpxjs') !== -1 || !target || mode === srcMode) {
504 return
505 }
506
507 const type = target.name
508
509 const name = type === 'wx' ? 'mpx' : 'createFactory'
510 const replaceContent = type === 'wx' ? 'mpx' : `${name}(${JSON.stringify(type)})`
511
512 const dep = new ReplaceDependency(replaceContent, target.range)
513 current.addDependency(dep)
514
515 let needInject = true
516 for (let v of module.variables) {
517 if (v.name === name) {
518 needInject = false
519 break
520 }
521 }
522 if (needInject) {
523 const expression = `require(${JSON.stringify(`@mpxjs/core/src/runtime/${name}`)})`
524 const deps = []
525 parser.parse(expression, {
526 current: {
527 addDependency: dep => {
528 dep.userRequest = name
529 deps.push(dep)
530 }
531 },
532 module
533 })
534 module.addVariable(name, expression, deps)
535 }
536 }
537
538 // hack babel polyfill global
539 parser.hooks.evaluate.for('CallExpression').tap('MpxWebpackPlugin', (expr) => {
540 if (/core-js/.test(parser.state.module.resource)) {
541 const current = parser.state.current
542 const arg0 = expr.arguments[0]
543 const callee = expr.callee
544 if (arg0 && arg0.value === 'return this' && callee.name === 'Function') {
545 current.addDependency(new InjectDependency({
546 content: '(function() { return this })() || ',
547 index: expr.range[0]
548 }))
549 }
550 }
551 })
552
553 if (this.options.srcMode !== this.options.mode) {
554 // 全量替换未声明的wx identifier
555 parser.hooks.expression.for('wx').tap('MpxWebpackPlugin', transHandler)
556
557 // parser.hooks.evaluate.for('MemberExpression').tap('MpxWebpackPlugin', (expr) => {
558 // // Undeclared varible for wx[identifier]()
559 // // TODO Unable to handle wx[identifier]
560 // if (expr.object.name === 'wx' && !parser.scope.definitions.has('wx')) {
561 // transHandler(expr)
562 // }
563 // })
564 // // Trans for wx.xx, wx['xx'], wx.xx(), wx['xx']()
565 // parser.hooks.expressionAnyMember.for('wx').tap('MpxWebpackPlugin', transHandler)
566 // Proxy ctor for transMode
567 if (!this.options.forceDisableProxyCtor) {
568 parser.hooks.call.for('Page').tap('MpxWebpackPlugin', (expr) => {
569 transHandler(expr.callee)
570 })
571 parser.hooks.call.for('Component').tap('MpxWebpackPlugin', (expr) => {
572 transHandler(expr.callee)
573 })
574 parser.hooks.call.for('App').tap('MpxWebpackPlugin', (expr) => {
575 transHandler(expr.callee)
576 })
577 if (this.options.mode === 'ali') {
578 // 支付宝不支持Behaviors
579 parser.hooks.call.for('Behavior').tap('MpxWebpackPlugin', (expr) => {
580 transHandler(expr.callee)
581 })
582 }
583 }
584 }
585
586 const apiBlackListMap = [
587 'createApp',
588 'createPage',
589 'createComponent',
590 'createStore',
591 'createStoreWithThis',
592 'mixin',
593 'injectMixins',
594 'toPureObject',
595 'observable',
596 'watch',
597 'use',
598 'set',
599 'remove',
600 'delete: del',
601 'setConvertRule',
602 'getMixin',
603 'getComputed',
604 'implement'
605 ].reduce((map, api) => {
606 map[api] = true
607 return map
608 }, {})
609
610 const handler = (expr) => {
611 const callee = expr.callee
612 const args = expr.arguments
613 const name = callee.object.name
614 const { queryObj, resourcePath } = parseRequest(parser.state.module.resource)
615
616 if (apiBlackListMap[callee.property.name || callee.property.value] || (name !== 'mpx' && name !== 'wx') || (name === 'wx' && !matchCondition(resourcePath, this.options.transMpxRules))) {
617 return
618 }
619
620 const localSrcMode = queryObj.mode
621 const globalSrcMode = this.options.srcMode
622 const srcMode = localSrcMode || globalSrcMode
623 const srcModeString = `__mpx_src_mode_${srcMode}__`
624 const dep = new InjectDependency({
625 content: args.length
626 ? `, ${JSON.stringify(srcModeString)}`
627 : JSON.stringify(srcModeString),
628 index: expr.end - 1
629 })
630 parser.state.current.addDependency(dep)
631 }
632
633 if (this.options.srcMode !== this.options.mode) {
634 parser.hooks.callAnyMember.for('imported var').tap('MpxWebpackPlugin', handler)
635 parser.hooks.callAnyMember.for('mpx').tap('MpxWebpackPlugin', handler)
636 parser.hooks.callAnyMember.for('wx').tap('MpxWebpackPlugin', handler)
637 }
638 })
639 })
640
641 compiler.hooks.normalModuleFactory.tap('MpxWebpackPlugin', (normalModuleFactory) => {
642 // resolve前修改原始request
643 normalModuleFactory.hooks.beforeResolve.tapAsync('MpxWebpackPlugin', (data, callback) => {
644 let request = data.request
645 let { queryObj, resource } = parseRequest(request)
646 if (queryObj.resolve) {
647 // 此处的query用于将资源引用的当前包信息传递给resolveDependency
648 const pathLoader = normalize.lib('path-loader')
649 const packageName = mpx.currentPackageRoot || 'main'
650 resource = addQuery(resource, {
651 packageName
652 })
653 data.request = `!!${pathLoader}!${resource}`
654 } else if (queryObj.wxsModule) {
655 const wxsPreLoader = normalize.lib('wxs/wxs-pre-loader')
656 if (!/wxs-loader/.test(request)) {
657 data.request = `!!${wxsPreLoader}!${resource}`
658 }
659 }
660 callback(null, data)
661 })
662
663 // resolve完成后修改loaders或者resource/request
664 normalModuleFactory.hooks.afterResolve.tapAsync('MpxWebpackPlugin', (data, callback) => {
665 const isFromMpx = /\.(mpx|vue)/.test(data.resource)
666 if (data.loaders && isFromMpx) {
667 data.loaders.forEach((loader) => {
668 if (/ts-loader/.test(loader.loader)) {
669 loader.options = Object.assign({}, { appendTsSuffixTo: [/\.(mpx|vue)$/] })
670 }
671 })
672 }
673 // 根据用户传入的modeRules对特定资源添加mode query
674 data.resource = this.runModeRules(data.resource)
675
676 if (mpx.currentPackageRoot) {
677 const resourcePath = parseRequest(data.resource).resourcePath
678
679 const staticResourceHit = mpx.staticResourceHit
680 const packageName = mpx.currentPackageRoot || 'main'
681
682 let needAddQuery = false
683
684 if (staticResourceHit[resourcePath] && staticResourceHit[resourcePath] !== packageName) {
685 needAddQuery = true
686 }
687
688 if (needAddQuery) {
689 // 此处的query用于避免静态资源模块缓存,确保不同分包中引用的静态资源为不同模块
690 data.request = addQuery(data.request, {
691 packageName
692 })
693 }
694 }
695 callback(null, data)
696 })
697 })
698
699 compiler.hooks.emit.tapAsync('MpxWebpackPlugin', (compilation, callback) => {
700 if (this.options.mode === 'web') return callback()
701 const jsonpFunction = compilation.outputOptions.jsonpFunction
702
703 function getTargetFile (file) {
704 let targetFile = file
705 const queryStringIdx = targetFile.indexOf('?')
706 if (queryStringIdx >= 0) {
707 targetFile = targetFile.substr(0, queryStringIdx)
708 }
709 return targetFile
710 }
711
712 const processedChunk = new Set()
713 const rootName = compilation._preparedEntrypoints[0].name
714
715 function processChunk (chunk, isRuntime, relativeChunks) {
716 if (!chunk.files[0] || processedChunk.has(chunk)) {
717 return
718 }
719
720 let originalSource = compilation.assets[chunk.files[0]]
721 const source = new ConcatSource()
722 source.add('\nvar window = window || {};\n\n')
723
724 relativeChunks.forEach((relativeChunk, index) => {
725 if (!relativeChunk.files[0]) return
726 let chunkPath = getTargetFile(chunk.files[0])
727 let relativePath = getTargetFile(relativeChunk.files[0])
728 relativePath = path.relative(path.dirname(chunkPath), relativePath)
729 relativePath = fixRelative(relativePath, mpx.mode)
730 relativePath = toPosix(relativePath)
731 if (index === 0) {
732 // 引用runtime
733 // 支付宝分包独立打包,通过全局context获取webpackJSONP
734 if (mpx.mode === 'ali') {
735 if (chunk.name === rootName) {
736 // 在rootChunk中挂载jsonpFunction
737 source.add('// process ali subpackages runtime in root chunk\n' +
738 'var context = (function() { return this })() || Function("return this")();\n\n')
739 source.add(`context[${JSON.stringify(jsonpFunction)}] = window[${JSON.stringify(jsonpFunction)}] = require("${relativePath}");\n`)
740 } else {
741 // 其余chunk中通过context全局传递runtime
742 source.add('// process ali subpackages runtime in other chunk\n' +
743 'var context = (function() { return this })() || Function("return this")();\n\n')
744 source.add(`window[${JSON.stringify(jsonpFunction)}] = context[${JSON.stringify(jsonpFunction)}];\n`)
745 }
746 } else {
747 source.add(`window[${JSON.stringify(jsonpFunction)}] = require("${relativePath}");\n`)
748 }
749 } else {
750 source.add(`require("${relativePath}");\n`)
751 }
752 })
753
754 if (isRuntime) {
755 source.add('var context = (function() { return this })() || Function("return this")();\n')
756 source.add(`
757// Fix babel runtime in some quirky environment like ali & qq dev.
758if(!context.console) {
759 try {
760 context.console = console;
761 context.setInterval = setInterval;
762 context.setTimeout = setTimeout;
763 context.JSON = JSON;
764 context.Math = Math;
765 context.RegExp = RegExp;
766 context.Infinity = Infinity;
767 context.isFinite = isFinite;
768 context.parseFloat = parseFloat;
769 context.parseInt = parseInt;
770 context.Promise = Promise;
771 context.WeakMap = WeakMap;
772 context.Reflect = Reflect;
773 context.RangeError = RangeError;
774 context.TypeError = TypeError;
775 context.Uint8Array = Uint8Array;
776 context.DataView = DataView;
777 context.ArrayBuffer = ArrayBuffer;
778 context.Symbol = Symbol;
779 } catch(e){
780 }
781}
782\n`)
783 if (mpx.mode === 'swan') {
784 source.add('// swan runtime fix\n' +
785 'if (!context.navigator) {\n' +
786 ' context.navigator = {};\n' +
787 '}\n' +
788 'Object.defineProperty(context.navigator, "standalone",{\n' +
789 ' configurable: true,' +
790 ' enumerable: true,' +
791 ' get () {\n' +
792 ' return true;\n' +
793 ' }\n' +
794 '});\n\n')
795 }
796 source.add(originalSource)
797 source.add(`\nmodule.exports = window[${JSON.stringify(jsonpFunction)}];\n`)
798 } else {
799 if (mpx.pluginMain === chunk.name) {
800 source.add('module.exports =\n')
801 }
802 source.add(originalSource)
803 }
804
805 compilation.assets[chunk.files[0]] = source
806 processedChunk.add(chunk)
807 }
808
809 compilation.chunkGroups.forEach((chunkGroup) => {
810 if (!chunkGroup.isInitial()) {
811 return
812 }
813
814 let runtimeChunk, entryChunk
815 let middleChunks = []
816
817 let chunksLength = chunkGroup.chunks.length
818
819 chunkGroup.chunks.forEach((chunk, index) => {
820 if (index === 0) {
821 runtimeChunk = chunk
822 } else if (index === chunksLength - 1) {
823 entryChunk = chunk
824 } else {
825 middleChunks.push(chunk)
826 }
827 })
828
829 if (runtimeChunk) {
830 processChunk(runtimeChunk, true, [])
831 if (middleChunks.length) {
832 middleChunks.forEach((middleChunk) => {
833 processChunk(middleChunk, false, [runtimeChunk])
834 })
835 }
836 if (entryChunk) {
837 middleChunks.unshift(runtimeChunk)
838 processChunk(entryChunk, false, middleChunks)
839 }
840 }
841 })
842
843 if (this.options.generateBuildMap) {
844 const pagesMap = compilation.__mpx__.pagesMap
845 const componentsPackageMap = compilation.__mpx__.componentsMap
846 const componentsMap = Object.keys(componentsPackageMap).map(item => componentsPackageMap[item]).reduce((pre, cur) => {
847 return { ...pre, ...cur }
848 }, {})
849 const outputMap = JSON.stringify({ ...pagesMap, ...componentsMap })
850 compilation.assets['../outputMap.json'] = {
851 source: () => {
852 return outputMap
853 },
854 size: () => {
855 return Buffer.byteLength(outputMap, 'utf8')
856 }
857 }
858 }
859
860 callback()
861 })
862 }
863}
864
865module.exports = MpxWebpackPlugin