UNPKG

@tarojs/mini-runner

Version:

Mini app runner for taro

1,026 lines 55.2 kB
"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(/^(\/|\\)/, '