@tarojs/mini-runner
Version:
Mini app runner for taro
499 lines • 23.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const helper_1 = require("@tarojs/helper");
const shared_1 = require("@tarojs/shared");
const md5 = require("md5");
const path = require("path");
const SplitChunksPlugin = require("webpack/lib/optimize/SplitChunksPlugin");
const webpack_sources_1 = require("webpack-sources");
const PLUGIN_NAME = 'MiniSplitChunkPlugin';
const SUB_ALLINONE = 'allinone';
const FileExtsMap = {
JS: '.js',
JS_MAP: '.js.map',
STYLE: '.wxss'
};
class MiniSplitChunksPlugin extends SplitChunksPlugin {
constructor(options) {
super();
/**
* 自动驱动 tapAsync
*/
this.tryAsync = fn => (arg, callback) => __awaiter(this, void 0, void 0, function* () {
try {
yield fn(arg);
callback();
}
catch (err) {
callback(err);
}
});
this.options = null;
this.subCommonDeps = new Map();
this.chunkSubCommons = new Map();
this.subPackagesVendors = new Map();
this.distPath = '';
const { exclude, fileType, subCommonDir, subVendorsName, isAllInOne } = options;
this.exclude = exclude || [];
this.fileType = fileType || {
style: '.wxss',
config: '.json',
script: '.js',
templ: '.wxml',
xs: '.wxs'
};
FileExtsMap.STYLE = this.fileType.style;
this.subCommonDir = subCommonDir || 'sub-common';
this.subVendorsName = subVendorsName || 'sub-vendors';
this.isAllInOne = isAllInOne || false;
this.chunkMap = {};
this.chunkNo = 0;
}
apply(compiler) {
var _a, _b;
this.context = compiler.context;
this.subPackages = this.getSubpackageConfig(compiler).map((subPackage) => (Object.assign(Object.assign({}, subPackage), { root: this.formatSubRoot(subPackage.root) })));
this.subRoots = this.subPackages.map((subPackage) => subPackage.root);
this.subRootRegExps = this.subRoots.map((subRoot) => new RegExp(`^${subRoot}\\/`));
this.distPath = (_b = (_a = compiler === null || compiler === void 0 ? void 0 : compiler.options) === null || _a === void 0 ? void 0 : _a.output) === null || _b === void 0 ? void 0 : _b.path;
this.isDevMode = compiler.options.mode === 'development';
/**
* 调用父类SplitChunksPlugin的apply方法,注册相关处理事件
*/
super.apply(compiler);
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.optimizeChunks.tap(PLUGIN_NAME, (chunks) => {
var _a, _b;
const splitChunksOriginConfig = Object.assign({}, (_b = (_a = compiler === null || compiler === void 0 ? void 0 : compiler.options) === null || _a === void 0 ? void 0 : _a.optimization) === null || _b === void 0 ? void 0 : _b.splitChunks);
this.subCommonDeps = new Map();
this.chunkSubCommons = new Map();
this.subPackagesVendors = new Map();
/**
* 找出分包入口chunks
*/
const subChunks = chunks.filter(chunk => this.isSubChunk(chunk));
if (subChunks.length === 0) {
this.options = SplitChunksPlugin.normalizeOptions(splitChunksOriginConfig);
return;
}
subChunks.forEach((subChunk) => {
subChunk.modulesIterable.forEach((module) => {
if (this.isExternalModule(module)) {
return;
}
if (!this.hasModuleId(module)) {
return;
}
if (this.hasExclude() && this.isExcludeModule(module)) {
return;
}
const chunks = Array.from(module.chunksIterable);
const chunkNames = chunks.map(chunk => chunk.name);
/**
* 找出没有被主包引用,且被多个分包引用的module,并记录在subCommonDeps中
*/
if (!this.hasMainChunk(chunkNames) && this.isSubsDep(chunkNames)) {
let depPath = '';
let depName = '';
if (module.resource) {
depPath = module.resource;
}
else {
depPath = module._identifier;
}
if (this.isDevMode) {
/**
* 避免开发模式下,清除sub-common源目录后,触发重新编译时,sub-common目录缺失无变化的chunk导致文件copy失败的问题
*/
depName = md5(depPath + new Date().getTime());
}
else {
depName = this.getChunkName(depPath);
}
if (!this.subCommonDeps.has(depName)) {
const subCommonDepChunks = new Set(chunkNames);
this.subCommonDeps.set(depName, {
identifier: module._identifier,
resource: module.resource,
chunks: subCommonDepChunks
});
}
else {
const subCommonDep = this.subCommonDeps.get(depName);
chunks.map(chunk => subCommonDep.chunks.add(chunk.name));
this.subCommonDeps.set(depName, subCommonDep);
}
}
});
});
/**
* 用新的option配置生成新的cacheGroups配置
*/
this.options = SplitChunksPlugin.normalizeOptions(Object.assign(Object.assign({}, splitChunksOriginConfig), { cacheGroups: Object.assign(Object.assign(Object.assign({}, splitChunksOriginConfig === null || splitChunksOriginConfig === void 0 ? void 0 : splitChunksOriginConfig.cacheGroups), this.getSubPackageVendorsCacheGroup()), this.getSubCommonCacheGroup()) }));
});
/**
* 收集分包下的sub-vendors和sub-common下的公共模块信息
*/
compilation.hooks.afterOptimizeChunks.tap(PLUGIN_NAME, (chunks) => {
const existSubCommonDeps = new Map();
chunks.forEach(chunk => {
const chunkName = chunk.name;
if (this.matchSubVendors(chunk)) {
const subRoot = this.subRoots.find(subRoot => new RegExp(`^${subRoot}\\/`).test(chunkName));
this.subPackagesVendors.set(subRoot, chunk);
}
if (this.matchSubCommon(chunk)) {
const depName = chunkName.replace(new RegExp(`^${this.subCommonDir}\\/(.*)`), '$1');
if (this.subCommonDeps.has(depName)) {
existSubCommonDeps.set(depName, this.subCommonDeps.get(depName));
}
}
});
this.setChunkSubCommons(existSubCommonDeps);
this.subCommonDeps = existSubCommonDeps;
});
/**
* 往分包page头部添加require
*/
compilation.chunkTemplate.hooks.renderWithEntry.tap(PLUGIN_NAME, (modules, chunk) => {
if (this.isSubChunk(chunk)) {
const chunkName = chunk.name;
const chunkSubRoot = this.subRoots.find(subRoot => new RegExp(`^${subRoot}\\/`).test(chunkName));
const chunkAbsulutePath = path.resolve(this.distPath, chunkName);
const source = new webpack_sources_1.ConcatSource();
const hasSubVendors = this.subPackagesVendors.has(chunkSubRoot);
const subVendors = this.subPackagesVendors.get(chunkSubRoot);
const subCommon = [...(this.chunkSubCommons.get(chunkName) || [])];
/**
* require该分包下的sub-vendors
*/
if (hasSubVendors) {
const subVendorsAbsolutePath = path.resolve(this.distPath, subVendors.name);
const relativePath = this.getRealRelativePath(chunkAbsulutePath, subVendorsAbsolutePath);
source.add(`require(${JSON.stringify(relativePath)});\n`);
}
// require sub-common下的模块
if (subCommon.length > 0) {
if (this.needAllInOne()) {
const subcommonAllInOnePath = path.resolve(this.distPath, chunkSubRoot, this.subCommonDir, SUB_ALLINONE);
const relativePath = this.getRealRelativePath(chunkAbsulutePath, subcommonAllInOnePath);
source.add(`require(${JSON.stringify(relativePath)});\n`);
}
else {
subCommon.forEach(moduleName => {
const moduleAbsulutePath = path.resolve(this.distPath, chunkSubRoot, this.subCommonDir, moduleName);
const relativePath = this.getRealRelativePath(chunkAbsulutePath, moduleAbsulutePath);
source.add(`require(${JSON.stringify(relativePath)});\n`);
});
}
}
source.add(modules);
source.add(';');
return source;
}
});
});
compiler.hooks.emit.tapAsync(PLUGIN_NAME, this.tryAsync((compilation) => {
const assets = compilation.assets;
const subChunks = compilation.entries.filter(entry => this.isSubChunk(entry));
const needAllInOne = this.needAllInOne();
subChunks.forEach(subChunk => {
const subChunkName = subChunk.name;
const subRoot = this.subRoots.find(subRoot => new RegExp(`^${subRoot}\\/`).test(subChunkName));
const chunkWxssName = `${subChunkName}${FileExtsMap.STYLE}`;
const subCommon = [...(this.chunkSubCommons.get(subChunkName) || [])];
const wxssAbsulutePath = path.resolve(this.distPath, chunkWxssName);
const subVendorsWxssPath = path.join(subRoot, `${this.subVendorsName}${FileExtsMap.STYLE}`);
const source = new webpack_sources_1.ConcatSource();
if (subCommon.length > 0) {
let hasSubCssCommon = false;
subCommon.forEach(moduleName => {
const wxssFileName = `${moduleName}${FileExtsMap.STYLE}`;
const wxssFilePath = path.join(this.subCommonDir, wxssFileName);
if (assets[(0, helper_1.normalizePath)(wxssFilePath)]) {
hasSubCssCommon = true;
if (!needAllInOne) {
const moduleAbsulutePath = path.resolve(this.distPath, subRoot, this.subCommonDir, wxssFileName);
const relativePath = this.getRealRelativePath(wxssAbsulutePath, moduleAbsulutePath);
source.add(` ${JSON.stringify(`${relativePath}`)};\n`);
}
}
// 复制sub-common下的资源到分包下
for (const key in FileExtsMap) {
const ext = FileExtsMap[key];
const assetName = path.join(this.subCommonDir, `${moduleName}${ext}`);
const subAssetName = path.join(subRoot, assetName);
const assetSource = assets[(0, helper_1.normalizePath)(assetName)];
if (assetSource) {
assets[(0, helper_1.normalizePath)(subAssetName)] = {
size: () => assetSource.source().length,
source: () => assetSource.source()
};
}
}
});
if (needAllInOne && hasSubCssCommon) {
const subCommonAllinOnePath = path.resolve(this.distPath, subRoot, this.subCommonDir, `${SUB_ALLINONE}${FileExtsMap.STYLE}`);
const relativePath = this.getRealRelativePath(wxssAbsulutePath, subCommonAllinOnePath);
source.add(` ${JSON.stringify(`${relativePath}`)};\n`);
}
}
if (assets[(0, helper_1.normalizePath)(subVendorsWxssPath)]) {
const subVendorsAbsolutePath = path.resolve(this.distPath, subVendorsWxssPath);
const relativePath = this.getRealRelativePath(wxssAbsulutePath, subVendorsAbsolutePath);
source.add(` ${JSON.stringify(relativePath)};\n`);
}
if (assets[chunkWxssName]) {
const originSource = assets[chunkWxssName].source();
source.add(originSource);
}
assets[chunkWxssName] = {
size: () => source.source().length,
source: () => source.source()
};
});
/**
* 根目录下的sub-common资源删掉不输出
*/
for (const assetPath in assets) {
if (new RegExp(`^${this.subCommonDir}\\/.*`).test(assetPath)) {
delete assets[assetPath];
}
}
/**
* 将 subcommon 下的 chunk 合成一个
*/
if (this.needAllInOne()) {
this.subRoots.forEach(subRoot => {
let allInOneJsContent = '';
let allInOneCssContent = '';
// const allInOneJsAssetPath = normalizePath(`${subRoot}/${SUB_COMMON_DIR}/${SUB_ALLINONE}${FileExtsMap.JS}`) // 不需要做路径处理
const allInOneJsAssetPath = `${subRoot}/${this.subCommonDir}/${SUB_ALLINONE}${FileExtsMap.JS}`;
const allInOneCssAssetPath = `${subRoot}/${this.subCommonDir}/${SUB_ALLINONE}${FileExtsMap.STYLE}`;
for (const assetPath in assets) {
const isSubCommon = assetPath.replace(/\\/g, '/').startsWith(`${subRoot}/${this.subCommonDir}/`);
if (!isSubCommon)
continue;
const assetsContent = assets[assetPath].source() + '\n';
if (path.extname(assetPath) === FileExtsMap.JS) {
allInOneJsContent += assetsContent;
delete assets[assetPath];
}
else if (path.extname(assetPath) === FileExtsMap.STYLE) {
allInOneCssContent += assetsContent;
delete assets[assetPath];
}
}
if (allInOneJsContent) {
assets[allInOneJsAssetPath] = {
size: () => allInOneJsContent.length,
source: () => allInOneJsContent
};
}
if (allInOneCssContent) {
assets[allInOneCssAssetPath] = {
size: () => allInOneCssContent.length,
source: () => allInOneCssContent
};
}
});
}
}));
}
/**
* 根据 webpack entry 配置获取入口文件路径
*/
getAppEntry(compiler) {
const originalEntry = compiler.options.entry;
return path.resolve(this.context, originalEntry.app[0]);
}
/**
* 获取分包配置
*/
getSubpackageConfig(compiler) {
const appEntry = this.getAppEntry(compiler);
const appConfigPath = this.getConfigFilePath(appEntry);
const appConfig = (0, helper_1.readConfig)(appConfigPath);
return appConfig.subPackages || appConfig.subpackages || [];
}
/**
* 根据 app、页面、组件的路径获取对应的 config 配置文件的路径
*/
getConfigFilePath(filePath) {
return (0, helper_1.resolveMainFilePath)(`${filePath.replace(path.extname(filePath), '')}.config`);
}
/**
* 去掉尾部的/
*/
formatSubRoot(subRoot) {
const lastApl = subRoot[subRoot.length - 1];
if (lastApl === '/') {
subRoot = subRoot.slice(0, subRoot.length - 1);
}
return subRoot;
}
isSubChunk(chunk) {
const isSubChunk = this.subRootRegExps.find(subRootRegExp => subRootRegExp.test(chunk.name));
return !!isSubChunk;
}
/**
* match *\/sub-vendors
*/
matchSubVendors(chunk) {
const subVendorsRegExps = this.subRoots.map(subRoot => new RegExp(`^${(0, helper_1.normalizePath)(path.join(subRoot, this.subVendorsName))}$`));
const isSubVendors = subVendorsRegExps.find(subVendorsRegExp => subVendorsRegExp.test(chunk.name));
return !!isSubVendors;
}
/**
* match sub-common\/*
*/
matchSubCommon(chunk) {
return new RegExp(`^${this.subCommonDir}\\/`).test(chunk.name);
}
/**
* 判断module有没被主包引用
*/
hasMainChunk(chunkNames) {
/**
* 遍历chunk,如果其中有一个chunk,无法匹配分包root,则视为非分包的chunk
*/
return !!chunkNames.find((chunkName) => !(this.subRootRegExps.find(subRootRegExp => subRootRegExp.test(chunkName))));
}
/**
* 判断该module有没被多个分包引用
*/
isSubsDep(chunkNames) {
const chunkSubRoots = new Set();
chunkNames.forEach((chunkName) => {
this.subRoots.forEach((subRoot) => {
if (new RegExp(`^${subRoot}\\/`).test(chunkName)) {
chunkSubRoots.add(subRoot);
}
});
});
return chunkSubRoots.size > 1;
}
/**
* 仅分包有引用的module抽取到分包下的sub-vendors
*/
getSubPackageVendorsCacheGroup() {
const subPackageVendorsCacheGroup = {};
this.subRoots.forEach(subRoot => {
subPackageVendorsCacheGroup[subRoot] = {
test: (module, chunks) => {
if (this.hasExclude() && this.isExcludeModule(module)) {
return false;
}
return chunks.every(chunk => new RegExp(`^${subRoot}\\/`).test(chunk.name));
},
name: (0, helper_1.normalizePath)(path.join(subRoot, this.subVendorsName)),
minChunks: 2,
priority: 10000
};
});
return subPackageVendorsCacheGroup;
}
/**
* 没有被主包引用, 且被多个分包引用, 提取成单个模块,输出到sub-common下
*/
getSubCommonCacheGroup() {
const subCommonCacheGroup = {};
this.subCommonDeps.forEach((depInfo, depName) => {
const cacheGroupName = (0, helper_1.normalizePath)(path.join(this.subCommonDir, depName));
subCommonCacheGroup[cacheGroupName] = {
name: cacheGroupName,
test: module => {
if (!module.resource) {
return !!module._identifier && module._identifier === depInfo.identifier;
}
return module.resource === depInfo.resource;
},
priority: 1000
};
});
return subCommonCacheGroup;
}
hasExclude() {
return (0, shared_1.isArray)(this.exclude) && this.exclude.length > 0;
}
isExcludeModule(module) {
const moduleResource = module.resource;
for (let i = 0; i < this.exclude.length; i++) {
const excludeItem = this.exclude[i];
if ((0, shared_1.isString)(excludeItem) && excludeItem === moduleResource) {
return true;
}
if ((0, shared_1.isFunction)(excludeItem) && excludeItem(module)) {
return true;
}
}
return false;
}
setChunkSubCommons(subCommonDeps) {
const chunkSubCommons = new Map();
subCommonDeps.forEach((depInfo, depName) => {
const chunks = [...depInfo.chunks];
chunks.forEach(chunk => {
if (chunkSubCommons.has(chunk)) {
const chunkSubCommon = chunkSubCommons.get(chunk);
chunkSubCommon.add(depName);
chunkSubCommons.set(chunk, chunkSubCommon);
}
else {
chunkSubCommons.set(chunk, new Set([depName]));
}
});
});
this.chunkSubCommons = chunkSubCommons;
}
/**
* 获取page相对于公共模块的路径
*/
getRealRelativePath(from, to) {
return (0, helper_1.promoteRelativePath)(path.relative(from, to));
}
/**
* 判断module为external module
*/
isExternalModule(module) {
return !!module.external;
}
/**
* 判断是否存在resource和_identifier
*/
hasModuleId(module) {
if (module.resource) {
return true;
}
if (module._identifier) {
return true;
}
return false;
}
/**
* 用序号代替md5,减少chunName的体积消耗
*/
getChunkName(id) {
if (this.chunkMap[id])
return this.chunkMap[id];
this.chunkMap[id] = String(this.chunkNo++);
return this.chunkMap[id];
}
/**
* 是否需要做合并处理
*/
needAllInOne() {
return this.isAllInOne && !this.isDevMode;
}
}
exports.default = MiniSplitChunksPlugin;
//# sourceMappingURL=MiniSplitChunksPlugin.js.map