const GraphHelpers = require('webpack/lib/GraphHelpers'); const { normalizePath } = require('@dcloudio/uni-cli-shared'); const getSplitChunks = require('@dcloudio/vue-cli-plugin-uni/lib/split-chunks'); const path = require('path'); const mainPath = normalizePath(path.resolve(process.env.UNI_INPUT_DIR, 'main.')); const mainPkgName = 'mainPkg'; function getAllEntryPointOfChunkGroup (chunkGroup, entryPointSet) { if (chunkGroup.isInitial()) { return entryPointSet.add(chunkGroup); } const parentChunkGroups = [...chunkGroup.parentsIterable]; parentChunkGroups.forEach(parentChunkGroup => getAllEntryPointOfChunkGroup(parentChunkGroup, entryPointSet)); } function getChunkToEntryPointsMap (allChunks) { const chunkToEntryPointsMap = new Map(); allChunks.forEach(chunkItem => { const chunkGroups = [...chunkItem.groupsIterable]; const tmpEntryPointSet = new Set(); chunkToEntryPointsMap.set(chunkItem, tmpEntryPointSet); chunkGroups.forEach(chunkGroup => { getAllEntryPointOfChunkGroup(chunkGroup, tmpEntryPointSet); }); }); return chunkToEntryPointsMap; } function baseTest (module) { if (module.type === 'css/mini-extract') { return false; } if (module.resource) { const resource = normalizePath(module.resource); if ( resource.indexOf('.vue') !== -1 || resource.indexOf('.nvue') !== -1 || resource.indexOf(mainPath) === 0 // main.js ) { return false; } } return true; } class SplitHandler { constructor (chunks = [], compilation, cacheGroups, chunkFilter, removeModuleFromChunkFilter = () => true) { this.chunks = chunks || []; this.chunkFilter = chunkFilter; this.cacheGroups = cacheGroups; this.compilation = compilation; this.removeModuleFromChunkFilter = removeModuleFromChunkFilter; this.chunksInfoMap = new Map(); } addModuleToChunksInfoMap (module, chunks, newChunkName) { let info = this.chunksInfoMap.get(newChunkName); if (!info) { info = { modules: new Set(), chunks: new Set(), }; this.chunksInfoMap.set(newChunkName, info); } info.modules.add(module); chunks.forEach(chunk => info.chunks.add(chunk)); } checkTest (module, test) { if (typeof test === 'function') { if (test(module, module.getChunks())) { return true; } } else if (test instanceof RegExp) { if (module.nameForCondition && test.test(module.nameForCondition())) { return true; } for (const chunk of module.getChunks()) { if (chunk.name && test.test(chunk.name)) { return true; } } } return false; } getHitCacheGroups (module) { const hitCacheGroups = []; const cacheGroups = this.cacheGroups; for (const key of Object.keys(cacheGroups)) { const cacheInfo = cacheGroups[key]; if (!cacheInfo) { continue; } if (this.checkTest(module, cacheInfo.test)) { hitCacheGroups.push({ newChunkName: cacheInfo.name, priority: cacheInfo.priority || 0 }); } } if (hitCacheGroups.length) { return hitCacheGroups.sort((b, a) => a.priority - b.priority)[0]; } return null; } start () { const allModulesSet = new Set(); this.chunks.forEach(chunk => { chunk.getModules().forEach(module => allModulesSet.add(module)); }); this.splitHandler([...allModulesSet]); } filter (module) { // 获取chunks和this.chunks的交集部分 const filterOne = this.chunks.filter(targetChunk => module.chunksIterable.has(targetChunk)) || []; // 处理uniapp提供的过滤,见split-chunks文件 return filterOne.filter(this.chunkFilter); } splitHandler (allModulesUsedByIndependent) { // 遍历独立分包中用到的模块,测试其所在的cacheGroup // 每个模块在这里至多会生成或加入到一个newChunk中 for (const module of allModulesUsedByIndependent) { const hitGroup = this.getHitCacheGroups(module); if (!hitGroup) { continue; } this.addModuleToChunksInfoMap(module, this.filter(module), hitGroup.newChunkName); } // 遍历 chunksInfoMap for (const [chunkName, newChunkInfo] of this.chunksInfoMap) { const newChunk = this.compilation.addChunk(chunkName); newChunk.chunkReason = 'split chunk for independent'; for (const module of newChunkInfo.modules) { GraphHelpers.connectChunkAndModule(newChunk, module); [...newChunkInfo.chunks].forEach(chunk => { if (this.removeModuleFromChunkFilter(chunk)) { chunk.removeModule(module); chunk.split(newChunk); } }); } } } } class SplitIndependentChunksPlugin { generateCacheGroups () { const cacheGroups = {}; Object.keys(process.UNI_SUBPACKAGES).forEach(root => { const pkgInfo = process.UNI_SUBPACKAGES[root]; if (pkgInfo.independent) { cacheGroups[root] = { [root + '/commonsVendor']: { test: /[\\/]node_modules[\\/]/, minSize: 0, minChunks: 1, name: normalizePath(path.join(root, 'common/library')), priority: 2, chunks: 'all', }, [root + '/commons']: { priority: 1, name: normalizePath(path.join(root, 'common/vendor')), test: (module) => { if (!baseTest(module)) { return false; } return true; }, }, }; } }); const splitChunkConfig = getSplitChunks(); cacheGroups[mainPkgName] = splitChunkConfig.cacheGroups; return { cacheGroups, chunkFilter: splitChunkConfig.chunks }; } apply (compiler) { compiler.hooks.thisCompilation.tap('SplitIndependentChunksPlugin', compilation => { compilation.hooks.optimizeChunksAdvanced.tap('SplitIndependentChunksPlugin', chunks => { try { const independentPkgRoot = Object.values(process.UNI_SUBPACKAGES).filter(rootInfo => rootInfo.independent).map(rootInfo => rootInfo.root); const allPkgRootMap = {}; const mainPkgChunks = []; for (const chunk of chunks) { const chunkName = chunk.name; if (!chunkName) { continue; } const root = independentPkgRoot.find(root => chunkName.startsWith(root)); if (!root) { mainPkgChunks.push(chunk); continue; } if (!allPkgRootMap[root]) { allPkgRootMap[root] = []; } allPkgRootMap[root].push(chunk); } const { cacheGroups, chunkFilter } = this.generateCacheGroups(compiler); // 找出chunk所有的entryPoint const chunkToEntryPointsMap = getChunkToEntryPointsMap(compilation.chunks); const allChunksUsedByIndependentMap = {}; for (const pkgRoot in allPkgRootMap) { if (!allChunksUsedByIndependentMap[pkgRoot]) { allChunksUsedByIndependentMap[pkgRoot] = new Set(); } for (const [chunkItem, entryPointSet] of chunkToEntryPointsMap) { const filter = entryPoint => entryPoint.name.startsWith(pkgRoot); const referenceByPkgRoot = [...entryPointSet].find(filter); // 当前chunk中存在模块被该独立分包下面的页面引用 // uniapp的entry: main.js + page.vue if (referenceByPkgRoot) { allChunksUsedByIndependentMap[pkgRoot].add(chunkItem); } } } // 先分离独立分包 for (const pkgRoot in allPkgRootMap) { const chunksOfIndependentPkg = allPkgRootMap[pkgRoot]; // 需要将独立分包中用到外部js模块拆分一份到 pkgRoot/vendor.js 中 const allChunksUsedByIndependent = [...allChunksUsedByIndependentMap[pkgRoot]]; // 收集独立分包下面的所有chunks:包内chunks + 包外chunks(主要引用的包外组件) const outSideChunks = []; allChunksUsedByIndependent.forEach(chunkItem => { if (!chunksOfIndependentPkg.includes(chunkItem)) { outSideChunks.push(chunkItem); } }); new SplitHandler([...allChunksUsedByIndependent], compilation, cacheGroups[pkgRoot], chunkFilter, (chunk) => { if (outSideChunks.includes(chunk)) { return false; } return true; }).start(); } // 再分离普通分包和主包 new SplitHandler(mainPkgChunks, compilation, cacheGroups[mainPkgName], chunkFilter).start(); } catch (e) { console.error('independent.error', 'SplitIndependentChunksPlugin', e); } }); }); } } module.exports = SplitIndependentChunksPlugin;