@tarojs/mini-runner
Version:
Mini app runner for taro
1,026 lines • 55.2 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 });
exports.createTarget = void 0;
const helper_1 = require("@tarojs/helper");
const html_minifier_1 = require("html-minifier");
const loader_utils_1 = require("loader-utils");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");
const webpack = require("webpack");
const SingleEntryDependency = require("webpack/lib/dependencies/SingleEntryDependency");
const FunctionModulePlugin = require("webpack/lib/FunctionModulePlugin");
const LoaderTargetPlugin = require("webpack/lib/LoaderTargetPlugin");
const NodeSourcePlugin = require("webpack/lib/node/NodeSourcePlugin");
const NaturalChunkOrderPlugin = require("webpack/lib/optimize/NaturalChunkOrderPlugin");
const RuntimeChunkPlugin = require("webpack/lib/optimize/RuntimeChunkPlugin");
const SplitChunksPlugin = require("webpack/lib/optimize/SplitChunksPlugin");
const JsonpTemplatePlugin = require("webpack/lib/web/JsonpTemplatePlugin");
const webpack_sources_1 = require("webpack-sources");
const TaroSingleEntryDependency_1 = require("../dependencies/TaroSingleEntryDependency");
const prerender_1 = require("../prerender/prerender");
const component_1 = require("../template/component");
const TaroLoadChunksPlugin_1 = require("./TaroLoadChunksPlugin");
const TaroNormalModulesPlugin_1 = require("./TaroNormalModulesPlugin");
const TaroSingleEntryPlugin_1 = require("./TaroSingleEntryPlugin");
const baseCompName = 'comp';
const customWrapperName = 'custom-wrapper';
const PLUGIN_NAME = 'TaroMiniPlugin';
const createTarget = function createTarget({ framework }) {
return (compiler) => {
const { options } = compiler;
new JsonpTemplatePlugin().apply(compiler);
new FunctionModulePlugin(options.output).apply(compiler);
new NodeSourcePlugin(options.node).apply(compiler);
if (process.env.NODE_ENV !== 'jest') {
// 暂时性修复 vue3 兼容问题,后续再改进写法
if (framework === helper_1.FRAMEWORK_MAP.VUE3) {
new LoaderTargetPlugin('web').apply(compiler);
}
else {
new LoaderTargetPlugin('node').apply(compiler);
}
}
};
};
exports.createTarget = createTarget;
function isLoaderExist(loaders, loaderName) {
return loaders.some(item => item.loader === loaderName);
}
class TaroMiniPlugin {
constructor(options = {}) {
/** app、页面、组件的配置集合 */
this.filesConfig = {};
this.isWatch = false;
/** 页面列表 */
this.pages = new Set();
this.components = new Set();
/** tabbar icon 图片路径列表 */
this.tabBarIcons = new Set();
this.dependencies = new Map();
this.pageLoaderName = '@tarojs/taro-loader/lib/page';
this.independentPackages = new Map();
/**
* 自动驱动 tapAsync
*/
this.tryAsync = fn => (arg, callback) => __awaiter(this, void 0, void 0, function* () {
try {
yield fn(arg);
callback();
}
catch (err) {
callback(err);
}
});
this.options = Object.assign({
sourceDir: '',
framework: 'nerv',
commonChunks: ['runtime', 'vendors'],
isBuildQuickapp: false,
isBuildPlugin: false,
fileType: {
style: '.wxss',
config: '.json',
script: '.js',
templ: '.wxml',
xs: '.wxs'
},
minifyXML: {},
hot: false
}, options);
const { template, baseLevel } = this.options;
if (template.isSupportRecursive === false && baseLevel > 0) {
template.baseLevel = baseLevel;
}
this.prerenderPages = new Set();
}
/**
* 插件入口
*/
apply(compiler) {
this.context = compiler.context;
this.appEntry = this.getAppEntry(compiler);
const { commonChunks, addChunkPages, framework, isBuildQuickapp, isBuildPlugin, fileType } = this.options;
/** build mode */
compiler.hooks.run.tapAsync(PLUGIN_NAME, this.tryAsync((compiler) => __awaiter(this, void 0, void 0, function* () {
yield this.run(compiler);
new TaroLoadChunksPlugin_1.default({
commonChunks: commonChunks,
isBuildPlugin,
addChunkPages: addChunkPages,
pages: this.pages,
framework: framework,
isBuildQuickapp
}).apply(compiler);
})));
/** watch mode */
compiler.hooks.watchRun.tapAsync(PLUGIN_NAME, this.tryAsync((compiler) => __awaiter(this, void 0, void 0, function* () {
const changedFiles = this.getChangedFiles(compiler);
if (changedFiles.length) {
this.isWatch = true;
}
yield this.run(compiler);
if (!this.loadChunksPlugin) {
this.loadChunksPlugin = new TaroLoadChunksPlugin_1.default({
commonChunks: commonChunks,
isBuildPlugin,
addChunkPages: addChunkPages,
pages: this.pages,
framework: framework,
isBuildQuickapp
});
this.loadChunksPlugin.apply(compiler);
}
})));
/** compilation.addEntry */
compiler.hooks.make.tapAsync(PLUGIN_NAME, this.tryAsync((compilation) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const dependencies = this.dependencies;
const promises = [];
this.compileIndependentPages(compiler, compilation, dependencies, promises);
dependencies.forEach(dep => {
promises.push(new Promise((resolve, reject) => {
compilation.addEntry(this.options.sourceDir, dep, dep.name, err => err ? reject(err) : resolve(null));
}));
});
yield Promise.all(promises);
yield ((_b = (_a = this.options).onCompilerMake) === null || _b === void 0 ? void 0 : _b.call(_a, compilation, compiler, this));
})));
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => {
/** For Webpack compilation get factory from compilation.dependencyFactories by denpendence's constructor */
compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory);
compilation.dependencyFactories.set(TaroSingleEntryDependency_1.default, normalModuleFactory);
/**
* webpack NormalModule 在 runLoaders 真正解析资源的前一刻,
* 往 NormalModule.loaders 中插入对应的 Taro Loader
*/
compilation.hooks.normalModuleLoader.tap(PLUGIN_NAME, (_loaderContext, module) => {
const { framework, loaderMeta, designWidth, deviceRatio } = this.options;
if (module.miniType === helper_1.META_TYPE.ENTRY) {
const loaderName = '@tarojs/taro-loader';
if (!isLoaderExist(module.loaders, loaderName)) {
module.loaders.unshift({
loader: loaderName,
options: {
framework,
loaderMeta,
prerender: this.prerenderPages.size > 0,
config: this.appConfig,
runtimePath: this.options.runtimePath,
blended: this.options.blended,
pxTransformConfig: {
designWidth: designWidth || 750,
deviceRatio: deviceRatio || {
750: 1
}
}
}
});
}
}
else if (module.miniType === helper_1.META_TYPE.PAGE) {
let isIndependent = false;
this.independentPackages.forEach(pages => {
if (pages.includes(module.resource)) {
isIndependent = true;
}
});
const loaderName = isBuildPlugin ? '@tarojs/taro-loader/lib/native-page' : (isIndependent ? '@tarojs/taro-loader/lib/independentPage' : this.pageLoaderName);
if (!isLoaderExist(module.loaders, loaderName)) {
module.loaders.unshift({
loader: loaderName,
options: {
framework,
loaderMeta,
name: module.name,
prerender: this.prerenderPages.has(module.name),
config: this.filesConfig,
appConfig: this.appConfig,
runtimePath: this.options.runtimePath,
hot: this.options.hot
}
});
}
}
else if (module.miniType === helper_1.META_TYPE.COMPONENT) {
const loaderName = isBuildPlugin ? '@tarojs/taro-loader/lib/native-component' : '@tarojs/taro-loader/lib/component';
if (!isLoaderExist(module.loaders, loaderName)) {
module.loaders.unshift({
loader: loaderName,
options: {
framework,
loaderMeta,
name: module.name,
prerender: this.prerenderPages.has(module.name),
runtimePath: this.options.runtimePath
}
});
}
}
});
/**
* 与原生小程序混写时解析模板与样式
*/
compilation.hooks.afterOptimizeAssets.tap(PLUGIN_NAME, assets => {
Object.keys(assets).forEach(assetPath => {
const styleExt = fileType.style;
const templExt = fileType.templ;
if (new RegExp(`(\\${styleExt}|\\${templExt})\\.js(\\.map){0,1}$`).test(assetPath)) {
delete assets[assetPath];
}
else if (new RegExp(`${styleExt}${styleExt}$`).test(assetPath)) {
const assetObj = assets[assetPath];
const newAssetPath = assetPath.replace(styleExt, '');
assets[newAssetPath] = assetObj;
delete assets[assetPath];
}
});
});
});
compiler.hooks.emit.tapAsync(PLUGIN_NAME, this.tryAsync((compilation) => __awaiter(this, void 0, void 0, function* () {
yield this.generateMiniFiles(compilation);
})));
compiler.hooks.afterEmit.tapAsync(PLUGIN_NAME, this.tryAsync((compilation) => __awaiter(this, void 0, void 0, function* () {
yield this.addTarBarFilesToDependencies(compilation);
})));
new TaroNormalModulesPlugin_1.default(this.options.onParseCreateElement).apply(compiler);
}
/**
* 根据 webpack entry 配置获取入口文件路径
* @returns app 入口文件路径
*/
getAppEntry(compiler) {
// const originalEntry = compiler.options.entry as webpack.Entry
// compiler.options.entry = {}
// return path.resolve(this.context, originalEntry.app[0])
const { entry } = compiler.options;
if (this.options.isBuildPlugin) {
const entryCopy = Object.assign({}, entry);
compiler.options.entry = {};
return entryCopy;
}
if (this.options.appEntry) {
compiler.options.entry = {};
return this.options.appEntry;
}
function getEntryPath(entry) {
const app = entry.app;
if (Array.isArray(app)) {
return app[0];
}
return app;
}
const appEntryPath = getEntryPath(entry);
compiler.options.entry = {};
return appEntryPath;
}
getChangedFiles(compiler) {
const { watchFileSystem } = compiler;
const watcher = watchFileSystem.watcher || watchFileSystem.wfs.watcher;
return Object.keys(watcher.mtimes);
}
/**
* 分析 app 入口文件,搜集页面、组件信息,
* 往 this.dependencies 中添加资源模块
*/
run(compiler) {
if (this.options.isBuildPlugin) {
this.getPluginFiles();
this.getConfigFiles(compiler);
}
else {
this.appConfig = this.getAppConfig();
this.getPages();
this.getPagesConfig();
this.getDarkMode();
this.getConfigFiles(compiler);
this.addEntries();
}
}
getPluginFiles() {
const fileList = new Set();
const { pluginConfig, template } = this.options;
const normalFiles = new Set();
Object.keys(this.appEntry).forEach(key => {
const filePath = this.appEntry[key][0];
if (key === this.options.pluginMainEntry) {
this.addEntry(filePath, key, helper_1.META_TYPE.EXPORTS);
}
if (pluginConfig) {
fileList.add({
name: key,
path: filePath,
isNative: false
});
let isPage = false;
let isComponent = false;
Object.keys(pluginConfig).forEach(pluginKey => {
if (pluginKey === 'pages') {
Object.keys(pluginConfig[pluginKey]).forEach(pageKey => {
if (`plugin/${pluginConfig[pluginKey][pageKey]}` === key) {
isPage = true;
}
});
}
if (pluginKey === 'publicComponents') {
Object.keys(pluginConfig[pluginKey]).forEach(pageKey => {
if (`plugin/${pluginConfig[pluginKey][pageKey]}` === key) {
isComponent = true;
}
});
}
});
if (isPage) {
this.pages.add({
name: key,
path: filePath,
isNative: false
});
}
else if (isComponent) {
this.components.add({
name: key,
path: filePath,
isNative: false
});
}
else {
normalFiles.add({
name: key,
path: filePath,
isNative: true
});
}
}
});
if (!template.isSupportRecursive) {
this.addEntry(path.resolve(__dirname, '..', 'template/comp'), this.getIsBuildPluginPath('comp', true), helper_1.META_TYPE.STATIC);
}
this.addEntry(path.resolve(__dirname, '..', 'template/custom-wrapper'), this.getIsBuildPluginPath('custom-wrapper', true), helper_1.META_TYPE.STATIC);
normalFiles.forEach(item => {
this.addEntry(item.path, item.name, helper_1.META_TYPE.NORMAL);
});
this.pages.forEach(item => {
if (!this.isWatch) {
(0, helper_1.printLog)("compile" /* processTypeEnum.COMPILE */, '发现页面', this.getShowPath(item.path));
}
this.compileFile(item);
if (item.isNative) {
this.addEntry(item.path, item.name, helper_1.META_TYPE.NORMAL);
if (item.stylePath && helper_1.fs.existsSync(item.stylePath)) {
this.addEntry(item.stylePath, this.getStylePath(item.name), helper_1.META_TYPE.NORMAL);
}
if (item.templatePath && helper_1.fs.existsSync(item.templatePath)) {
this.addEntry(item.templatePath, this.getTemplatePath(item.name), helper_1.META_TYPE.NORMAL);
}
}
else {
this.addEntry(item.path, item.name, helper_1.META_TYPE.PAGE);
}
});
this.components.forEach(item => {
this.compileFile(item);
if (item.isNative) {
this.addEntry(item.path, item.name, helper_1.META_TYPE.NORMAL);
if (item.stylePath && helper_1.fs.existsSync(item.stylePath)) {
this.addEntry(item.stylePath, this.getStylePath(item.name), helper_1.META_TYPE.NORMAL);
}
if (item.templatePath && helper_1.fs.existsSync(item.templatePath)) {
this.addEntry(item.templatePath, this.getTemplatePath(item.name), helper_1.META_TYPE.NORMAL);
}
}
else {
this.addEntry(item.path, item.name, helper_1.META_TYPE.COMPONENT);
}
});
}
modifyPluginJSON(pluginJSON) {
const { main, publicComponents } = pluginJSON;
const isUsingCustomWrapper = component_1.componentConfig.thirdPartyComponents.has('custom-wrapper');
if (main) {
pluginJSON.main = this.getTargetFilePath(main, '.js');
}
if (!this.options.template.isSupportRecursive) {
pluginJSON.publicComponents = Object.assign({}, publicComponents, {
[baseCompName]: baseCompName
});
}
if (isUsingCustomWrapper) {
pluginJSON.publicComponents = Object.assign({}, publicComponents, {
[customWrapperName]: customWrapperName
});
}
}
/**
* 获取 app config 配置内容
* @returns app config 配置内容
*/
getAppConfig() {
const appName = path.basename(this.appEntry).replace(path.extname(this.appEntry), '');
this.compileFile({
name: appName,
path: this.appEntry,
isNative: false
});
const fileConfig = this.filesConfig[this.getConfigFilePath(appName)];
const appConfig = fileConfig ? fileConfig.content || {} : {};
if ((0, helper_1.isEmptyObject)(appConfig)) {
throw new Error('缺少 app 全局配置文件,请检查!');
}
return appConfig;
}
/**
* 根据 app config 的 pages 配置项,收集所有页面信息,
* 包括处理分包和 tabbar
*/
getPages() {
if ((0, helper_1.isEmptyObject)(this.appConfig)) {
throw new Error('缺少 app 全局配置文件,请检查!');
}
const appPages = this.appConfig.pages;
if (!appPages || !appPages.length) {
throw new Error('全局配置缺少 pages 字段,请检查!');
}
if (!this.isWatch) {
(0, helper_1.printLog)("compile" /* processTypeEnum.COMPILE */, '发现入口', this.getShowPath(this.appEntry));
}
const { frameworkExts, prerender } = this.options;
this.prerenderPages = new Set((0, prerender_1.validatePrerenderPages)(appPages, prerender).map(p => p.path));
this.getTabBarFiles(this.appConfig);
this.pages = new Set([
...appPages.map(item => {
const pagePath = (0, helper_1.resolveMainFilePath)(path.join(this.options.sourceDir, item), frameworkExts);
const pageTemplatePath = this.getTemplatePath(pagePath);
const isNative = this.isNativePageORComponent(pageTemplatePath);
return {
name: item,
path: pagePath,
isNative,
stylePath: isNative ? this.getStylePath(pagePath) : undefined,
templatePath: isNative ? this.getTemplatePath(pagePath) : undefined
};
})
]);
this.getSubPackages(this.appConfig);
}
/**
* 读取页面及其依赖的组件的配置
*/
getPagesConfig() {
this.pages.forEach(page => {
if (!this.isWatch) {
(0, helper_1.printLog)("compile" /* processTypeEnum.COMPILE */, '发现页面', this.getShowPath(page.path));
}
this.compileFile(page);
});
}
/**
* 往 this.dependencies 中新增或修改所有 config 配置模块
*/
getConfigFiles(compiler) {
const filesConfig = this.filesConfig;
Object.keys(filesConfig).forEach(item => {
if (helper_1.fs.existsSync(filesConfig[item].path)) {
this.addEntry(filesConfig[item].path, item, helper_1.META_TYPE.CONFIG);
}
});
// webpack createChunkAssets 前一刻,去除所有 config chunks
compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
compilation.hooks.beforeChunkAssets.tap(PLUGIN_NAME, () => {
Object.keys(filesConfig).forEach(item => {
const assetsChunkIndex = compilation.chunks.findIndex(({ name }) => name === item);
if (assetsChunkIndex > -1) {
compilation.chunks.splice(assetsChunkIndex, 1);
}
});
});
});
}
/**
* 在 this.dependencies 中新增或修改模块
*/
addEntry(entryPath, entryName, entryType) {
let dep;
if (this.dependencies.has(entryPath)) {
dep = this.dependencies.get(entryPath);
dep.name = entryName;
dep.loc = { name: entryName };
dep.entryPath = entryPath;
dep.entryType = entryType;
}
else {
dep = new TaroSingleEntryDependency_1.default(entryPath, entryName, { name: entryName }, entryType);
}
this.dependencies.set(entryPath, dep);
}
/**
* 在 this.dependencies 中新增或修改 app、模板组件、页面、组件等资源模块
*/
addEntries() {
const { template } = this.options;
this.addEntry(this.appEntry, 'app', helper_1.META_TYPE.ENTRY);
if (!template.isSupportRecursive) {
this.addEntry(path.resolve(__dirname, '..', 'template/comp'), 'comp', helper_1.META_TYPE.STATIC);
}
this.addEntry(path.resolve(__dirname, '..', 'template/custom-wrapper'), 'custom-wrapper', helper_1.META_TYPE.STATIC);
this.pages.forEach(item => {
if (item.isNative) {
this.addEntry(item.path, item.name, helper_1.META_TYPE.NORMAL);
if (item.stylePath && helper_1.fs.existsSync(item.stylePath)) {
this.addEntry(item.stylePath, this.getStylePath(item.name), helper_1.META_TYPE.NORMAL);
}
if (item.templatePath && helper_1.fs.existsSync(item.templatePath)) {
this.addEntry(item.templatePath, this.getTemplatePath(item.name), helper_1.META_TYPE.NORMAL);
}
}
else {
this.addEntry(item.path, item.name, helper_1.META_TYPE.PAGE);
}
});
this.components.forEach(item => {
if (item.isNative) {
this.addEntry(item.path, item.name, helper_1.META_TYPE.NORMAL);
if (item.stylePath && helper_1.fs.existsSync(item.stylePath)) {
this.addEntry(item.stylePath, this.getStylePath(item.name), helper_1.META_TYPE.NORMAL);
}
if (item.templatePath && helper_1.fs.existsSync(item.templatePath)) {
this.addEntry(item.templatePath, this.getTemplatePath(item.name), helper_1.META_TYPE.NORMAL);
}
}
else {
this.addEntry(item.path, item.name, helper_1.META_TYPE.COMPONENT);
}
});
}
replaceExt(file, ext) {
return path.join(path.dirname(file), path.basename(file, path.extname(file)) + `${ext}`);
}
/**
* 读取页面、组件的配置,并递归读取依赖的组件的配置
*/
compileFile(file) {
const filePath = file.path;
const fileConfigPath = file.isNative ? this.replaceExt(filePath, '.json') : this.getConfigFilePath(filePath);
const fileConfig = (0, helper_1.readConfig)(fileConfigPath);
const { componentGenerics } = fileConfig;
// 修复百度小程序内容服务组件使用新的引入方式"usingSwanComponents"导致的无法编译到页面配置json的问题
// 获取 fileConfig 里面的匹配 "/^using[A-Za-z]*Components$/"的字段,之后合并到 usingComponents 中
const usingArray = Object.keys(fileConfig).filter(item => /^using[A-Za-z]*Components$/.test(item)).map(item => fileConfig[item]);
const usingComponents = usingArray.length < 1 ? undefined : Object.assign({}, ...usingArray);
if (this.options.isBuildPlugin && componentGenerics) {
Object.keys(componentGenerics).forEach(component => {
if (componentGenerics[component]) {
if (!component_1.componentConfig.thirdPartyComponents.has(component)) {
component_1.componentConfig.thirdPartyComponents.set(component, new Set());
}
}
});
}
// 递归收集依赖的第三方组件
if (usingComponents) {
const componentNames = Object.keys(usingComponents);
const depComponents = [];
const alias = this.options.alias;
for (const compName of componentNames) {
let compPath = usingComponents[compName];
if ((0, helper_1.isAliasPath)(compPath, alias)) {
compPath = (0, helper_1.replaceAliasPath)(filePath, compPath, alias);
fileConfig.usingComponents[compName] = compPath;
}
// 判断是否为第三方依赖的正则,如果 test 为 false 则为第三方依赖
const notNpmPkgReg = /^[.\\/]/;
if (!this.options.skipProcessUsingComponents &&
!compPath.startsWith('plugin://') &&
!notNpmPkgReg.test(compPath)) {
const tempCompPath = (0, helper_1.getNpmPackageAbsolutePath)(compPath);
if (tempCompPath) {
compPath = tempCompPath;
fileConfig.usingComponents[compName] = compPath;
}
}
depComponents.push({
name: compName,
path: compPath
});
if (!component_1.componentConfig.thirdPartyComponents.has(compName) && !file.isNative) {
component_1.componentConfig.thirdPartyComponents.set(compName, new Set());
}
}
depComponents.forEach(item => {
const componentPath = (0, helper_1.resolveMainFilePath)(path.resolve(path.dirname(file.path), item.path));
if (helper_1.fs.existsSync(componentPath) && !Array.from(this.components).some(item => item.path === componentPath)) {
const componentName = this.getComponentName(componentPath);
const componentTempPath = this.getTemplatePath(componentPath);
const isNative = this.isNativePageORComponent(componentTempPath);
const componentObj = {
name: componentName,
path: componentPath,
isNative,
stylePath: isNative ? this.getStylePath(componentPath) : undefined,
templatePath: isNative ? this.getTemplatePath(componentPath) : undefined
};
this.components.add(componentObj);
this.compileFile(componentObj);
}
});
}
this.filesConfig[this.getConfigFilePath(file.name)] = {
content: fileConfig,
path: fileConfigPath
};
}
/**
* 收集分包配置中的页面
*/
getSubPackages(appConfig) {
const subPackages = appConfig.subPackages || appConfig.subpackages;
const { frameworkExts } = this.options;
if (subPackages && subPackages.length) {
subPackages.forEach(item => {
if (item.pages && item.pages.length) {
const root = item.root;
const isIndependent = !!item.independent;
if (isIndependent) {
this.independentPackages.set(root, []);
}
item.pages.forEach(page => {
let pageItem = `${root}/${page}`;
pageItem = pageItem.replace(/\/{2,}/g, '/');
let hasPageIn = false;
this.pages.forEach(({ name }) => {
if (name === pageItem) {
hasPageIn = true;
}
});
if (!hasPageIn) {
const pagePath = (0, helper_1.resolveMainFilePath)(path.join(this.options.sourceDir, pageItem), frameworkExts);
const templatePath = this.getTemplatePath(pagePath);
const isNative = this.isNativePageORComponent(templatePath);
if (isIndependent) {
const independentPages = this.independentPackages.get(root);
independentPages === null || independentPages === void 0 ? void 0 : independentPages.push(pagePath);
}
this.pages.add({
name: pageItem,
path: pagePath,
isNative,
stylePath: isNative ? this.getStylePath(pagePath) : undefined,
templatePath: isNative ? this.getTemplatePath(pagePath) : undefined
});
}
});
}
});
}
}
/**
* 收集 dark mode 配置中的文件
*/
getDarkMode() {
const themeLocation = this.appConfig.themeLocation;
const darkMode = this.appConfig.darkmode;
if (darkMode && themeLocation && typeof themeLocation === 'string') {
this.themeLocation = themeLocation;
}
}
compileIndependentPages(compiler, compilation, dependencies, promises) {
const independentPackages = this.independentPackages;
if (independentPackages.size) {
independentPackages.forEach((pages, name) => {
const childCompiler = compilation.createChildCompiler(PLUGIN_NAME, {
path: `${compiler.options.output.path}/${name}`,
jsonpFunction: `${compiler.options.output.jsonpFunction}/${name}`
});
const compPath = path.resolve(__dirname, '..', 'template/comp');
childCompiler.inputFileSystem = compiler.inputFileSystem;
childCompiler.outputFileSystem = compiler.outputFileSystem;
childCompiler.context = compiler.context;
new JsonpTemplatePlugin().apply(childCompiler);
new NaturalChunkOrderPlugin().apply(childCompiler);
new MiniCssExtractPlugin({
filename: `[name]${this.options.fileType.style}`,
chunkFilename: `[name]${this.options.fileType.style}`
}).apply(childCompiler);
new webpack.DefinePlugin(this.options.constantsReplaceList).apply(childCompiler);
if (compiler.options.optimization) {
new SplitChunksPlugin({
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
common: {
name: `${name}/common`,
minChunks: 2,
priority: 1
},
vendors: {
name: `${name}/vendors`,
minChunks: 1,
test: module => {
return (/[\\/]node_modules[\\/]/.test(module.resource) && module.resource.indexOf(compPath) < 0);
},
priority: 10
}
}
}).apply(childCompiler);
new RuntimeChunkPlugin({
name: `${name}/runtime`
}).apply(childCompiler);
}
const childPages = new Set();
pages.forEach(pagePath => {
if (dependencies.has(pagePath)) {
const dep = dependencies.get(pagePath);
new TaroSingleEntryPlugin_1.default(compiler.context, dep === null || dep === void 0 ? void 0 : dep.request, dep === null || dep === void 0 ? void 0 : dep.name, dep === null || dep === void 0 ? void 0 : dep.miniType).apply(childCompiler);
}
this.pages.forEach(item => {
if (item.path === pagePath) {
childPages.add(item);
}
});
dependencies.delete(pagePath);
});
new TaroLoadChunksPlugin_1.default({
commonChunks: [`${name}/runtime`, `${name}/vendors`, `${name}/common`],
isBuildPlugin: false,
addChunkPages: this.options.addChunkPages,
pages: childPages,
framework: this.options.framework,
isBuildQuickapp: true,
needAddCommon: [`${name}/comp`]
}).apply(childCompiler);
// 添加 comp 和 custom-wrapper 组件
new TaroSingleEntryPlugin_1.default(compiler.context, path.resolve(__dirname, '..', 'template/comp'), `${name}/comp`, helper_1.META_TYPE.STATIC).apply(childCompiler);
new TaroSingleEntryPlugin_1.default(compiler.context, path.resolve(__dirname, '..', 'template/custom-wrapper'), `${name}/custom-wrapper`, helper_1.META_TYPE.STATIC).apply(childCompiler);
promises.push(new Promise((resolve, reject) => {
childCompiler.runAsChild(err => {
if (err) {
return reject(err);
}
resolve(null);
});
}).catch(err => console.log(err)));
});
}
}
/**
* 搜集 tabbar icon 图标路径
* 收集自定义 tabbar 组件
*/
getTabBarFiles(appConfig) {
const tabBar = appConfig.tabBar;
const { sourceDir, frameworkExts } = this.options;
if (tabBar && typeof tabBar === 'object' && !(0, helper_1.isEmptyObject)(tabBar)) {
// eslint-disable-next-line dot-notation
const list = tabBar['list'] || [];
list.forEach(item => {
// eslint-disable-next-line dot-notation
item['iconPath'] && this.tabBarIcons.add(item['iconPath']);
// eslint-disable-next-line dot-notation
item['selectedIconPath'] && this.tabBarIcons.add(item['selectedIconPath']);
});
if (tabBar.custom) {
const isAlipay = process.env.TARO_ENV === 'alipay';
const customTabBarPath = path.join(sourceDir, isAlipay ? 'customize-tab-bar' : 'custom-tab-bar');
const customTabBarComponentPath = (0, helper_1.resolveMainFilePath)(customTabBarPath, [...frameworkExts, ...helper_1.SCRIPT_EXT]);
if (helper_1.fs.existsSync(customTabBarComponentPath)) {
const customTabBarComponentTemplPath = this.getTemplatePath(customTabBarComponentPath);
const isNative = this.isNativePageORComponent(customTabBarComponentTemplPath);
if (!this.isWatch) {
(0, helper_1.printLog)("compile" /* processTypeEnum.COMPILE */, '自定义 tabBar', this.getShowPath(customTabBarComponentPath));
}
const componentObj = {
name: isAlipay ? 'customize-tab-bar/index' : 'custom-tab-bar/index',
path: customTabBarComponentPath,
isNative,
stylePath: isNative ? this.getStylePath(customTabBarComponentPath) : undefined,
templatePath: isNative ? this.getTemplatePath(customTabBarComponentPath) : undefined
};
this.compileFile(componentObj);
this.components.add(componentObj);
}
}
}
}
/** 是否为小程序原生页面或组件 */
isNativePageORComponent(templatePath) {
return helper_1.fs.existsSync(templatePath);
}
getShowPath(filePath) {
return filePath.replace(this.context, '').replace(/\\/g, '/').replace(/^\//, '');
}
// 调整 config 文件中 usingComponents 的路径
// 1. 将 node_modules 调整为 npm
// 2. 将 ../../../node_modules/xxx 调整为 /npm/xxx
adjustConfigContent(config) {
const { usingComponents } = config;
if (!usingComponents || this.options.skipProcessUsingComponents)
return;
for (const [key, value] of Object.entries(usingComponents)) {
if (!value.includes(helper_1.NODE_MODULES))
return;
const match = value.replace(helper_1.NODE_MODULES, 'npm').match(/npm.*/);
usingComponents[key] = match ? `${path.sep}${match[0]}` : value;
}
}
/** 生成小程序相关文件 */
generateMiniFiles(compilation) {
return __awaiter(this, void 0, void 0, function* () {
const { template, modifyBuildAssets, modifyMiniConfigs, isBuildPlugin, sourceDir, blended } = this.options;
const baseTemplateName = this.getIsBuildPluginPath('base', isBuildPlugin);
const isUsingCustomWrapper = component_1.componentConfig.thirdPartyComponents.has('custom-wrapper');
if (typeof modifyMiniConfigs === 'function') {
yield modifyMiniConfigs(this.filesConfig);
}
if (!blended && !isBuildPlugin) {
const appConfigPath = this.getConfigFilePath(this.appEntry);
const appConfigName = path.basename(appConfigPath).replace(path.extname(appConfigPath), '');
this.generateConfigFile(compilation, this.appEntry, this.filesConfig[appConfigName].content);
}
if (!template.isSupportRecursive) {
// 如微信、QQ 不支持递归模版的小程序,需要使用自定义组件协助递归
this.generateTemplateFile(compilation, this.getIsBuildPluginPath(baseCompName, isBuildPlugin), template.buildBaseComponentTemplate, this.options.fileType.templ);
const baseCompConfig = {
component: true,
usingComponents: {
[baseCompName]: `./${baseCompName}`
}
};
if (isUsingCustomWrapper) {
baseCompConfig.usingComponents[customWrapperName] = `./${customWrapperName}`;
this.generateConfigFile(compilation, this.getIsBuildPluginPath(customWrapperName, isBuildPlugin), {
component: true,
styleIsolation: 'apply-shared',
usingComponents: {
[baseCompName]: `./${baseCompName}`,
[customWrapperName]: `./${customWrapperName}`
}
});
}
this.generateConfigFile(compilation, this.getIsBuildPluginPath(baseCompName, isBuildPlugin), baseCompConfig);
}
else {
if (isUsingCustomWrapper) {
this.generateConfigFile(compilation, this.getIsBuildPluginPath(customWrapperName, isBuildPlugin), {
component: true,
usingComponents: {
[customWrapperName]: `./${customWrapperName}`
}
});
}
}
this.generateTemplateFile(compilation, baseTemplateName, template.buildTemplate, component_1.componentConfig);
if (isUsingCustomWrapper) {
this.generateTemplateFile(compilation, this.getIsBuildPluginPath(customWrapperName, isBuildPlugin), template.buildCustomComponentTemplate, this.options.fileType.templ);
}
else {
delete compilation.assets['custom-wrapper.js'];
}
this.generateXSFile(compilation, 'utils', isBuildPlugin);
// 为独立分包生成 base/comp/custom-wrapper
this.independentPackages.forEach((_pages, name) => {
this.generateTemplateFile(compilation, `${name}/${baseTemplateName}`, template.buildTemplate, component_1.componentConfig);
if (!template.isSupportRecursive) {
// 如微信、QQ 不支持递归模版的小程序,需要使用自定义组件协助递归
this.generateConfigFile(compilation, `${name}/${baseCompName}`, {
component: true,
usingComponents: {
[baseCompName]: `./${baseCompName}`,
[customWrapperName]: `./${customWrapperName}`
}
});
this.generateTemplateFile(compilation, `${name}/${baseCompName}`, template.buildBaseComponentTemplate, this.options.fileType.templ);
}
this.generateConfigFile(compilation, `${name}/${customWrapperName}`, {
component: true,
usingComponents: {
[customWrapperName]: `./${customWrapperName}`
}
});
this.generateTemplateFile(compilation, `${name}/${customWrapperName}`, template.buildCustomComponentTemplate, this.options.fileType.templ);
this.generateXSFile(compilation, `${name}/utils`, isBuildPlugin);
});
this.components.forEach(component => {
const importBaseTemplatePath = (0, helper_1.promoteRelativePath)(path.relative(component.path, path.join(sourceDir, this.getTemplatePath(baseTemplateName))));
const config = this.filesConfig[this.getConfigFilePath(component.name)];
if (config) {
this.generateConfigFile(compilation, component.path, config.content);
}
if (!component.isNative) {
this.generateTemplateFile(compilation, component.path, template.buildPageTemplate, importBaseTemplatePath);
}
});
this.pages.forEach(page => {
let importBaseTemplatePath = (0, helper_1.promoteRelativePath)(path.relative(page.path, path.join(sourceDir, this.getTemplatePath(baseTemplateName))));
const config = this.filesConfig[this.getConfigFilePath(page.name)];
let isIndependent = false;
let independentName = '';
this.independentPackages.forEach((pages, name) => {
if (pages.includes(page.path)) {
isIndependent = true;
independentName = name;
importBaseTemplatePath = (0, helper_1.promoteRelativePath)(path.relative(page.path, path.join(sourceDir, name, this.getTemplatePath(baseTemplateName))));
}
});
if (config) {
let importBaseCompPath = (0, helper_1.promoteRelativePath)(path.relative(page.path, path.join(sourceDir, this.getTargetFilePath(this.getIsBuildPluginPath(baseCompName, isBuildPlugin), ''))));
let importCustomWrapperPath = (0, helper_1.promoteRelativePath)(path.relative(page.path, path.join(sourceDir, this.getTargetFilePath(this.getIsBuildPluginPath(customWrapperName, isBuildPlugin), ''))));
if (isIndependent) {
importBaseCompPath = (0, helper_1.promoteRelativePath)(path.relative(page.path, path.join(sourceDir, independentName, this.getTargetFilePath(this.getIsBuildPluginPath(baseCompName, isBuildPlugin), ''))));
importCustomWrapperPath = (0, helper_1.promoteRelativePath)(path.relative(page.path, path.join(sourceDir, independentName, this.getTargetFilePath(this.getIsBuildPluginPath(customWrapperName, isBuildPlugin), ''))));
}
config.content.usingComponents = Object.assign({}, config.content.usingComponents);
if (isUsingCustomWrapper) {
config.content.usingComponents[customWrapperName] = importCustomWrapperPath;
}
if (!template.isSupportRecursive && !page.isNative) {
config.content.usingComponents[baseCompName] = importBaseCompPath;
}
this.generateConfigFile(compilation, page.path, config.content);
}
if (!page.isNative) {
this.generateTemplateFile(compilation, page.path, template.buildPageTemplate, importBaseTemplatePath);
}
});
this.generateTabBarFiles(compilation);
this.injectCommonStyles(compilation);
if (this.themeLocation) {
this.generateDarkModeFile(compilation);
}
if (isBuildPlugin) {
const pluginJSONPath = path.join(sourceDir, 'plugin', 'plugin.json');
if (helper_1.fs.existsSync(pluginJSONPath)) {
const pluginJSON = helper_1.fs.readJSONSync(pluginJSONPath);
this.modifyPluginJSON(pluginJSON);
const relativePath = pluginJSONPath.replace(sourceDir, '').replace(/\\/g, '/');
compilation.assets[relativePath] = {
size: () => JSON.stringify(pluginJSON).length,
source: () => JSON.stringify(pluginJSON, null, 2)
};
}
}
if (typeof modifyBuildAssets === 'function') {
yield modifyBuildAssets(compilation.assets, this);
}
});
}
generateConfigFile(compilation, filePath, config) {
const fileConfigName = this.getConfigPath(this.getComponentName(filePath));
const unOfficalConfigs = ['enableShareAppMessage', 'enableShareTimeline', 'components'];
unOfficalConfigs.forEach(item => {
delete config[item];
});
this.adjustConfigContent(config);
const fileConfigStr = JSON.stringify(config);
compilation.assets[fileConfigName] = {
size: () => fileConfigStr.length,
source: () => fileConfigStr
};
}
generateTemplateFile(compilation, filePath, templateFn, ...options) {
var _a;
let templStr = templateFn(...options);
const fileTemplName = this.getTemplatePath(this.getComponentName(filePath));
if ((_a = this.options.minifyXML) === null || _a === void 0 ? void 0 : _a.collapseWhitespace) {
templStr = (0, html_minifier_1.minify)(templStr, {
collapseWhitespace: true,
keepClosingSlash: true
});
}
compilation.assets[fileTemplName] = {
size: () => templStr.length,
source: () => templStr
};
}
generateXSFile(compilation, xsPath, isBuildPlugin) {
const ext = this.options.fileType.xs;
const isSupportXS = this.options.template.supportXS;
if (ext == null || !isSupportXS) {
return;
}
const xs = this.options.template.buildXScript();
const fileXsName = this.getTargetFilePath(xsPath, ext);
const filePath = this.getIsBuildPluginPath(fileXsName, isBuildPlugin);
compilation.assets[filePath] = {
size: () => xs.length,
source: () => xs
};
}
getComponentName(componentPath) {
let componentName;
if (helper_1.NODE_MODULES_REG.test(componentPath)) {
componentName = componentPath.replace(this.context, '').replace(/\\/g, '/').replace(path.extname(componentPath), '');
componentName = componentName.replace(/node_modules/gi, 'npm');
}
else {
componentName = componentPath.replace(this.options.sourceDir, '').replace(/\\/g, '/').replace(path.extname(componentPath), '');
}
return componentName.replace(/^(\/|\\)/, '