UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

239 lines (214 loc) 8.73 kB
const NullDependency = require('webpack/lib/dependencies/NullDependency') const makeSerializable = require('webpack/lib/util/makeSerializable') const path = require('path') const addQuery = require('../utils/add-query') const toPosix = require('../utils/to-posix') const async = require('async') const parseRequest = require('../utils/parse-request') const hasOwn = require('../utils/has-own') const { RetryRuntimeGlobal } = require('../retry-runtime-module') class DynamicEntryDependency extends NullDependency { constructor (range, request, entryType, outputPath = '', packageRoot = '', relativePath = '', context = '', extraOptions = {}) { super() this.request = request this.entryType = entryType this.outputPath = outputPath this.packageRoot = packageRoot this.relativePath = relativePath this.context = context this.range = range if (typeof extraOptions === 'string') { extraOptions = JSON.parse(extraOptions) } this.extraOptions = extraOptions } get type () { return 'mpx dynamic entry' } get key () { const { request, entryType, outputPath, packageRoot, relativePath, context, range } = this return toPosix([request, entryType, outputPath, packageRoot, relativePath, context, ...range].join('|')) } addEntry (compilation, callback) { const mpx = compilation.__mpx__ let { request, entryType, outputPath, relativePath, context, originEntryNode, publicPath, resolver, extraOptions } = this async.waterfall([ (callback) => { if (context && resolver) { resolver.resolve({}, context, request, {}, (err, resource) => { callback(err, resource) }) } else { callback(null, request) } }, (resource, callback) => { const { resourcePath } = parseRequest(resource) if (!outputPath) { outputPath = mpx.getOutputPath(resourcePath, entryType) } const { packageName, packageRoot, outputPath: filename, alreadyOutputted } = mpx.getPackageInfo({ resource, outputPath, resourceType: entryType, warn (e) { compilation.warnings.push(e) }, error (e) { compilation.errors.push(e) } }) let resultPath = publicPath + filename if (relativePath) { resultPath = toPosix(path.relative(relativePath, resultPath)) } // export类型的resultPath需要添加.js后缀 if (entryType === 'export') resultPath += '.js' // 对于常规js模块不应添加packageRoot避免冗余 if (packageRoot && entryType !== 'export') { resource = addQuery(resource, { packageRoot }, true) } const key = [resource, filename].join('|') if (alreadyOutputted) { const addEntryPromise = mpx.addEntryPromiseMap.get(key) if (addEntryPromise) { addEntryPromise.then(entryModule => { // 构建entry依赖图,针对alreadyOutputted的entry也需要记录 originEntryNode.addChild(mpx.getEntryNode(entryModule, entryType)) }) } if (mpx.dynamicEntryInfo[packageName] && extraOptions.isAsync) { mpx.dynamicEntryInfo[packageName].entries.forEach(entry => { if (entry.resource === resource && entry.filename === filename && entry.entryType === entryType) { entry.hasAsync = true } return entry }) } // alreadyOutputted时直接返回,避免存在模块循环引用时死循环 return callback(null, { resultPath }) } else { const addEntryPromise = new Promise((resolve, reject) => { mpx.addEntry(resource, filename, (err, entryModule) => { if (err) return reject(err) if (entryType === 'export') { mpx.exportModules.add(entryModule) } resolve(entryModule) }) }) addEntryPromise .then(entryModule => { originEntryNode.addChild(mpx.getEntryNode(entryModule, entryType)) callback(null, { resultPath }) }) .catch(err => callback(err)) mpx.addEntryPromiseMap.set(key, addEntryPromise) mpx.collectDynamicEntryInfo({ resource, packageName, filename, entryType, hasAsync: extraOptions.isAsync || false }) } } ], callback) } mpxAction (module, compilation, callback) { const { __mpx__: mpx, moduleGraph } = compilation let entryModule = module while (true) { const issuer = moduleGraph.getIssuer(entryModule) if (issuer) entryModule = issuer else break } this.originEntryNode = mpx.getEntryNode(entryModule) this.publicPath = compilation.outputOptions.publicPath || '' const { packageRoot, context } = this if (context) this.resolver = compilation.resolverFactory.get('normal', module.resolveOptions) // post 分包队列在 sub 分包队列构建完毕后进行 if (this.extraOptions.postSubpackageEntry) { mpx.postSubpackageEntriesMap[packageRoot] = mpx.postSubpackageEntriesMap[packageRoot] || [] mpx.postSubpackageEntriesMap[packageRoot].push(this) callback() return } // 分包构建在需要在主包构建完成后在finishMake中处理,返回的资源路径先用key来占位,在合成extractedAssets时再进行最终替换 if (packageRoot && mpx.currentPackageRoot !== packageRoot) { mpx.subpackagesEntriesMap[packageRoot] = mpx.subpackagesEntriesMap[packageRoot] || [] mpx.subpackagesEntriesMap[packageRoot].push(this) callback() } else { this.addEntry(compilation, (err, result) => { if (err) return callback(err) this.resultPath = result.resultPath callback() }) } } // hash会影响最终的codeGenerateResult是否走缓存,由于该dep中resultPath是动态变更的,需要将其更新到hash中,避免错误使用缓存 updateHash (hash, context) { const { resultPath, extraOptions } = this if (resultPath) hash.update(resultPath) // 当处理require.async时,插入随机hash使当前module的codeGeneration cache失效,从而执行dep.apply动态获取当前module所属的chunk路径 if (extraOptions.isRequireAsync) hash.update('' + (+new Date()) + Math.random()) super.updateHash(hash, context) } serialize (context) { const { write } = context write(this.request) write(this.entryType) write(this.outputPath) write(this.packageRoot) write(this.relativePath) write(this.context) write(this.range) write(this.extraOptions) super.serialize(context) } deserialize (context) { const { read } = context this.request = read() this.entryType = read() this.outputPath = read() this.packageRoot = read() this.relativePath = read() this.context = read() this.range = read() this.extraOptions = read() super.deserialize(context) } } DynamicEntryDependency.Template = class DynamicEntryDependencyTemplate { apply (dep, source, { module, chunkGraph, runtimeRequirements }) { const { resultPath, key, publicPath, extraOptions } = dep let range = dep.range let replaceContent = '' if (hasOwn(extraOptions, 'replaceContent')) { replaceContent = extraOptions.replaceContent } else if (resultPath) { if (extraOptions.isRequireAsync) { let relativePath = toPosix(path.relative(publicPath + path.dirname(chunkGraph.getModuleChunks(module)[0].name), resultPath)) if (!relativePath.startsWith('.')) relativePath = './' + relativePath replaceContent = JSON.stringify(relativePath) if (extraOptions.retryRequireAsync && extraOptions.retryRequireAsync.times > 0) { range = extraOptions.requireAsyncRange runtimeRequirements.add(RetryRuntimeGlobal) replaceContent = `${RetryRuntimeGlobal}(function() { return require.async(${JSON.stringify(relativePath)}) }, ${extraOptions.retryRequireAsync.times}, ${extraOptions.retryRequireAsync.interval})` } } else { replaceContent = JSON.stringify(resultPath) } } else { replaceContent = JSON.stringify(`mpx_replace_path_${key}`) } if (replaceContent) source.replace(range[0], range[1] - 1, replaceContent) } } makeSerializable(DynamicEntryDependency, '@mpxjs/webpack-plugin/lib/dependencies/DynamicEntryDependency') module.exports = DynamicEntryDependency