UNPKG

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