UNPKG

optimizer-mini

Version:
896 lines (876 loc) 35.8 kB
import fs from 'node:fs'; import path from 'node:path'; import process$1 from 'node:process'; import { removeExt, normalizeMiniProgramFilename, parseManifestJsonOnce, parseMiniProgramPagesJson } from '@dcloudio/uni-cli-shared'; import MagicString from 'magic-string'; import chalk from 'chalk'; import { isRegExp } from 'node:util/types'; var __defProp$3 = Object.defineProperty; var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$3 = (obj, key, value) => { __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class AsyncComponents { constructor() { __publicField$3(this, "scriptDescriptors", /* @__PURE__ */ new Map()); __publicField$3(this, "jsonAsyncComponentsCache", /* @__PURE__ */ new Map()); /** 当前状态下热更新时会导致把原有的json内容清除,操作过的page-json需要记录之前的内容 */ __publicField$3(this, "pageJsonCache", /* @__PURE__ */ new Map()); __publicField$3(this, "rename", (name) => name.startsWith("wx-") ? name.replace("wx-", "weixin-") : name); } addScriptDescriptor(filename, binding) { binding && filename && this.scriptDescriptors.set(filename, { bindingAsyncComponents: binding }); } addAsyncComponents(filename, json) { this.jsonAsyncComponentsCache.set(filename, json); } generateBinding(tag, path) { return { tag, value: path, type: "asyncComponent" }; } getComponentPlaceholder(filename) { const cache = this.jsonAsyncComponentsCache.get(filename); if (!cache) return null; const componentPlaceholder = Object.entries(cache).reduce((p, [key, value]) => { p[this.rename(key)] = "view"; return p; }, {}); return componentPlaceholder; } generateComponentPlaceholderJson(filename, originJson = {}) { const componentPlaceholder = this.getComponentPlaceholder(filename); return Object.assign(originJson || {}, componentPlaceholder || {}); } } var __defProp$2 = Object.defineProperty; var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$2 = (obj, key, value) => { __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class Logger { constructor(level = "INFO" /* INFO */, context = "Plugin", isImplicit = false) { __publicField$2(this, "level"); __publicField$2(this, "context"); /** TODO: 可以使用其他的 debug 日志库 */ __publicField$2(this, "Debugger", null); /** 全局兜底:是否是隐式log */ __publicField$2(this, "isImplicit"); this.level = level; this.context = context; this.isImplicit = isImplicit; } log(level, message, isImplicit) { if (this.shouldLog(level)) { const coloredMessage = this.getColoredMessage(level, message); if (isImplicit ?? this.isImplicit) ; else { const c = 69; const colorCode = `\x1B[3${`8;5;${c}`};1m`; console.log(` ${chalk(`${colorCode}${this.context}`)} ${coloredMessage}`); } } } shouldLog(level) { const levels = ["DEBUG" /* DEBUG */, "INFO" /* INFO */, "WARN" /* WARN */, "ERROR" /* ERROR */]; return levels.indexOf(level) >= levels.indexOf(this.level); } getColoredMessage(level, message) { switch (level) { case "DEBUG" /* DEBUG */: return chalk.blue(`[${level}] ${message}`); case "INFO" /* INFO */: return chalk.green(`[${level}] ${message}`); case "WARN" /* WARN */: return chalk.yellow(`[${level}] ${message}`); case "ERROR" /* ERROR */: return chalk.red(`[${level}] ${message}`); default: return message; } } debug(message, isImplicit) { this.log("DEBUG" /* DEBUG */, message, isImplicit); } info(message, isImplicit) { this.log("INFO" /* INFO */, message, isImplicit); } warn(message, isImplicit) { this.log("WARN" /* WARN */, message, isImplicit); } error(message, isImplicit) { this.log("ERROR" /* ERROR */, message, isImplicit); } } const logger = new Logger("INFO" /* INFO */, "optimizer-mini"); const EXTNAME_JS_RE = /\.(js|jsx|ts|uts|tsx|mjs)$/; const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/; const ASSETS_DIR_RE = /^(\.?\/)?assets\//; const SRC_DIR_RE = /^(\.?\/)?src\//; const ROOT_DIR = process$1.env.VITE_ROOT_DIR; if (!ROOT_DIR) { throw new Error("`ROOT_DIR` is not defined"); } function createVitePathResolver(config) { const tempAlias = config.resolve?.alias ?? []; let alias = []; if (!Array.isArray(tempAlias)) { alias = Object.entries(tempAlias).map(([find, replacement]) => ({ find, replacement })); } else { alias = tempAlias; } return (source, relative = false) => { for (let { find, replacement, customResolver: _customResolver } of alias) { if (!find || !replacement) continue; source = normalizePath(source); if (typeof replacement === "string" && !isRegExp(replacement) && !replacement.includes("*")) { replacement = normalizePath(replacement); } if (!isRegExp(replacement) && typeof replacement === "string" && !replacement.includes("*") && !isRegExp(find) && typeof find === "string" && !find.includes("*")) { if (source === find) { return relative ? replacement : path.resolve(replacement); } else if (source.startsWith(find)) { const realPath = source.replace(find, replacement); return relative ? realPath : path.resolve(realPath); } } else if (source.match(find) && (isRegExp(find) || !find.includes("*"))) { const realPath = source.replace(find, replacement); return relative ? realPath : path.resolve(realPath); } } return source; }; } let vitePathResolver = null; function getVitePathResolver() { if (!vitePathResolver) { throw new Error("Vite path resolver has not been initialized. Please call createVitePathResolver first."); } return vitePathResolver; } function initializeVitePathResolver(config) { vitePathResolver = createVitePathResolver(config); } function lexFunctionCalls(code, functionName) { const functionPattern = new RegExp( `\\b${functionName}\\s*\\(\\s*([^\\)]*)\\s*\\)`, // 函数名 + 参数部分 "g" ); const matches = []; let match; while ((match = functionPattern.exec(code)) !== null) { const argsString = match[1]; const fullMatchLocation = { start: match.index, // 函数调用的起始位置 end: match.index + match[0].length, // 函数调用的结束位置 fullMatch: match[0] // 完整匹配的函数调用 }; const functionCallPrefixEnd = match.index + match[0].indexOf("(") + 1; const padStartCount = match[0].indexOf(argsString) - (match[0].indexOf("(") + 1); const args = parseArguments(argsString.padStart(argsString.length + padStartCount), functionCallPrefixEnd); matches.push({ full: fullMatchLocation, // 完整匹配的函数调用 args // 参数解析结果 }); } return matches; } function parseArguments(argsString, functionPrefixEnd, code) { const args = []; const argPattern = /'(?:\\'|[^'])*'|"(?:\\"|[^"])*"|\d+(?:\.\d+)?|\w+/g; let match; while ((match = argPattern.exec(argsString)) !== null) { const argValue = match[0]; const argStart = functionPrefixEnd + argsString.slice(0, match.index).length; const argEnd = argStart + argValue.length; let value = argValue; if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) { value = value.slice(1, -1); } else if (!Number.isNaN(Number(value))) { value = Number(value); } args.push({ value, // 只保留实际的参数值 start: argStart, // 修正后的起始位置 end: argEnd // 修正后的结束位置 }); } return args; } const IMPORT_DEFAULT_WITH_QUERY_RE = /import\s+(\w+)\s+from\s+(['"])([^'"]+)\?(\w+(?:&\w+)*)\2(?:\s*;)?/g; function parseValue(value) { if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) { return value.slice(1, -1); } return value; } function lexDefaultImportWithQuery(code) { const matches = []; let match; while ((match = IMPORT_DEFAULT_WITH_QUERY_RE.exec(code)) !== null) { const fullMatchLocation = { start: match.index, // 函数调用的起始位置 end: match.index + match[0].length, // 函数调用的结束位置 fullMatch: match[0] // 完整匹配的函数调用 }; const defaultVariable = { value: parseValue(match[1]), start: match.index + match[0].indexOf(match[1]), end: match.index + match[0].indexOf(match[1]) + match[1].length }; const modulePath = { value: parseValue(match[3]), start: match.index + match[0].indexOf(match[3]), end: match.index + match[0].indexOf(match[3]) + match[3].length }; let lastLength = 0; const query = match[4].split("&").map((queryParam, _index, list) => { lastLength += (list[_index - 1]?.length || 0) + 1; const prevLength = modulePath.end + lastLength; const start = prevLength + match[0].slice(prevLength - fullMatchLocation.start).indexOf(queryParam); const end = start + queryParam.length; return { value: parseValue(queryParam), start, end }; }); const fullPath = { value: parseValue(`${match[3]}?${match[4]}`), start: modulePath.start, end: query[query.length - 1].end }; matches.push({ full: fullMatchLocation, // 完整匹配的函数调用 defaultVariable, // 参数解析结果 modulePath, query, fullPath // 完整路径信息 }); } return matches; } function parseAsyncImports(source) { return lexFunctionCalls(source, "AsyncImport"); } function slash(p) { return p.replace(/\\/g, "/"); } function normalizePath(id) { return process$1.platform === "win32" ? slash(id) : id; } function ensureDirectoryExists(filePath) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } function moduleIdProcessor(id, rootDir = ROOT_DIR) { rootDir = normalizePath(rootDir); if (!rootDir.endsWith("/")) rootDir += "/"; const normalized = normalizePath(id); const name = normalized.split("?")[0]; const updatedName = name.replace(rootDir, ""); if (updatedName.startsWith("\0")) return updatedName.slice(1); return updatedName; } function calculateRelativePath(importer, imported) { if (imported.match(/^(\.\/|\.\.\/)+/)) { imported = path.resolve(path.dirname(importer), imported); } const relativePath = path.relative(path.dirname(importer), imported); return relativePath.replace(/\\/g, "/"); } function resolveAssetsPath(id) { return id.replace(ASSETS_DIR_RE, "./"); } function kebabCase(key) { if (!key) return key; const result = key.replace(/([A-Z])/g, " $1").trim(); return result.split(" ").join("-").toLowerCase(); } function findFirstNonConsecutiveBefore(arr) { if (arr.length < 2) return null; const result = arr.find((value, index) => index > 0 && value !== arr[index - 1] + 1); return result !== void 0 && result !== null ? arr[arr.indexOf(result) - 1] : null; } function AsyncComponentProcessor(options, enableLogger) { const inputDir = process$1.env.UNI_INPUT_DIR; const platform = process$1.env.UNI_PLATFORM; const AsyncComponentsInstance = new AsyncComponents(); const isMP = platform?.startsWith("mp-"); function generateTypeFile(parseResult) { if (options === false || options.enable === false) return; const typesFilePath = path.resolve(ROOT_DIR, normalizePath(options.path)); ensureDirectoryExists(typesFilePath); let cache = []; if (fs.existsSync(typesFilePath)) { const list = lexFunctionCalls(fs.readFileSync(typesFilePath, "utf-8"), "import").flatMap(({ args }) => args.map(({ value }) => value.toString())); list && list.length && (cache = Array.from(new Set(list))); } const typeDefinition = generateModuleDeclaration$1(parseResult, cache); fs.writeFileSync(typesFilePath, typeDefinition); logger.info(`[async-component] ${parseResult === void 0 ? "\u521D\u59CB\u5316" : "\u751F\u6210"}\u7C7B\u578B\u5B9A\u4E49\u6587\u4EF6 ${typesFilePath.replace(`${ROOT_DIR}\\`, "")}`, !enableLogger); } generateTypeFile(); logger.info("[async-component] \u5F02\u6B65\u7EC4\u4EF6\u5904\u7406\u5668\u5DF2\u542F\u7528", !enableLogger); return { name: "async-component-processor", async transform(source, importer) { const parseResult = lexDefaultImportWithQuery(source).filter(({ modulePath }) => modulePath.value.toString().split("?")[0].endsWith(".vue")); if (!importer.split("?")[0].endsWith(".vue") || parseResult.length === 0 || !parseResult.some(({ query }) => query.some(({ value }) => value.toString().trim() === "async"))) { return; } generateTypeFile(parseResult); const filename = removeExt(normalizeMiniProgramFilename(importer, inputDir)); const tempBindings = {}; const magicString = new MagicString(source); parseResult.forEach(({ full, fullPath, defaultVariable, modulePath, query }) => { const cache = {}; query.forEach(({ start, end, value }, index, list) => { const prevChar = source[start - 1]; if (["async", ""].includes(value.toString().trim()) && start !== end) { magicString.overwrite(start, end, ""); if (prevChar === "&") { magicString.overwrite(start - 1, start, ""); } cache[index] = { start, end, value }; if (isMP) { const url = modulePath.value.toString(); let normalizedPath = getVitePathResolver()(url, true); normalizedPath = calculateRelativePath(importer, normalizedPath); normalizedPath = normalizedPath.replace(/\.vue$/, ""); const tag = kebabCase(defaultVariable.value.toString()); tempBindings[tag] = AsyncComponentsInstance.generateBinding(tag, normalizedPath); } } }); if (cache[0]) { const flag = findFirstNonConsecutiveBefore(Object.keys(cache).map(Number)); const { start, end } = flag !== null ? query[flag + 1] : cache[0]; const char = flag !== null ? "&" : "?"; const prevChar = source[start - 1]; if (prevChar === char) { magicString.overwrite(start - 1, start, ""); } } }); if (isMP) { AsyncComponentsInstance.addScriptDescriptor(filename, tempBindings); AsyncComponentsInstance.addAsyncComponents(filename, tempBindings); } return { code: magicString.toString(), map: magicString.generateMap({ hires: true }) }; }, generateBundle(_, bundle) { if (!isMP) return; AsyncComponentsInstance.jsonAsyncComponentsCache.forEach((value, key) => { const chunk = bundle[`${key}.json`]; const asyncComponents = Object.entries(value).reduce((p, [key2, value2]) => (p[AsyncComponentsInstance.rename(key2)] = value2.value, p), {}); if (chunk && chunk.type === "asset" && AsyncComponentsInstance.jsonAsyncComponentsCache.get(key)) { const jsonCode = JSON.parse(chunk.source.toString()); AsyncComponentsInstance.pageJsonCache.set(key, jsonCode); jsonCode.componentPlaceholder = AsyncComponentsInstance.generateComponentPlaceholderJson(key, jsonCode.componentPlaceholder); jsonCode.usingComponents = Object.assign(jsonCode.usingComponents || {}, asyncComponents); chunk.source = JSON.stringify(jsonCode, null, 2); } else { let componentPlaceholder = AsyncComponentsInstance.generateComponentPlaceholderJson(key); let usingComponents = asyncComponents; const cache = AsyncComponentsInstance.pageJsonCache.get(key); if (cache) { usingComponents = Object.assign(cache.usingComponents || {}, usingComponents); componentPlaceholder = Object.assign(cache.componentPlaceholder || {}, componentPlaceholder); } bundle[`${key}.json`] = { type: "asset", name: key, fileName: `${key}.json`, source: JSON.stringify({ usingComponents, componentPlaceholder }, null, 2) }; } }); }, buildStart() { AsyncComponentsInstance.jsonAsyncComponentsCache.clear(); AsyncComponentsInstance.scriptDescriptors.clear(); } }; } function generateModuleDeclaration$1(parsedResults, cache) { let typeDefs = ""; const prefixList = [ "/* eslint-disable */", "/* prettier-ignore */", "// @ts-nocheck", "// Generated by bundle-optimizer", "declare module '*?async' {", " const component: any", " export = component", "}" ]; prefixList.forEach((prefix) => { typeDefs += `${prefix} `; }); function generateDeclareModule(modulePath, fullPath) { typeDefs += `declare module '${fullPath}' { `; typeDefs += ` const component: typeof import('${modulePath}') `; typeDefs += ` export = component `; typeDefs += `} `; } cache?.forEach((item) => { const modulePath = item; const fullPath = `${modulePath}?async`; generateDeclareModule(modulePath, fullPath); }); parsedResults?.filter((item) => !cache?.includes(item.modulePath.value.toString())).forEach((result) => { const modulePath = result.modulePath.value; const fullPath = result.fullPath.value; generateDeclareModule(modulePath, fullPath); }); return typeDefs; } const AsyncComponent = (...options) => { return [ // 处理 `.vue?async` 查询参数的静态导入 AsyncComponentProcessor(...options) ]; }; var __defProp$1 = Object.defineProperty; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$1 = (obj, key, value) => { __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class AsyncImports { constructor() { __publicField$1(this, "cache", /* @__PURE__ */ new Map()); __publicField$1(this, "valueMap", /* @__PURE__ */ new Map()); } addCache(id, value, realPath) { if (this.cache.has(id) && !this.cache.get(id)?.includes(value)) { this.cache.get(id)?.push(value); } else { this.cache.set(id, [value]); } if (!realPath) { return; } if (this.valueMap.has(value) && !this.valueMap.get(value)?.includes(realPath)) { this.valueMap.get(value)?.push(realPath); } else { this.valueMap.set(value, [realPath]); } } getCache(id) { return this.cache.get(id); } getRealPath(value) { return this.valueMap.get(value); } clearCache() { this.cache.clear(); this.valueMap.clear(); } } function AsyncImportProcessor(options, enableLogger) { const platform = process$1.env.UNI_PLATFORM; const isMP = platform?.startsWith("mp"); const isH5 = platform === "h5"; const isApp = platform === "app"; const AsyncImportsInstance = new AsyncImports(); function generateTypeFile(paths) { if (options === false || options.enable === false) return; const typesFilePath = path.resolve(ROOT_DIR, normalizePath(options.path)); ensureDirectoryExists(typesFilePath); let cache = []; if (fs.existsSync(typesFilePath)) { const list = lexFunctionCalls(fs.readFileSync(typesFilePath, "utf-8"), "import").flatMap(({ args }) => args.map(({ value }) => value.toString())); list && list.length && (cache = Array.from(new Set(list))); } const typeDefinition = generateModuleDeclaration(paths, cache); fs.writeFileSync(typesFilePath, typeDefinition); logger.info(`[async-import] ${paths === void 0 ? "\u521D\u59CB\u5316" : "\u751F\u6210"}\u7C7B\u578B\u5B9A\u4E49\u6587\u4EF6 ${typesFilePath.replace(`${ROOT_DIR}\\`, "")}`, !enableLogger); } generateTypeFile(); logger.info("[async-import] \u5F02\u6B65\u5BFC\u5165\u5904\u7406\u5668\u5DF2\u542F\u7528", !enableLogger); return { name: "async-import-processor", enforce: "post", // 插件执行时机,在其他处理后执行 async transform(code, id) { const asyncImports = parseAsyncImports(code); if (asyncImports.length > 0 && !isApp) { const magicString = new MagicString(code); const paths = asyncImports.map((item) => item.args[0].value.toString()); generateTypeFile(paths); for (const { full, args } of asyncImports) { for (const { start, end, value } of args) { const target = value.toString(); let resolveId = (await this.resolve(target, id))?.id; if (resolveId) { resolveId = moduleIdProcessor(resolveId); } AsyncImportsInstance.addCache(moduleIdProcessor(id), target, resolveId); magicString.overwrite(full.start, full.start + "AsyncImport".length, "import", { contentOnly: true }); } } return { code: magicString.toString(), map: magicString.generateMap({ hires: true }) }; } }, renderDynamicImport(options2) { const cache = AsyncImportsInstance.getCache(moduleIdProcessor(options2.moduleId)); if (cache && options2.targetModuleId && !isApp && !isH5) { const targetModuleId = moduleIdProcessor(options2.targetModuleId).replace(JS_TYPES_RE, ""); const temp = cache.map((item) => ({ value: moduleIdProcessor(item.match(/^(\.\/|\.\.\/)+/) ? path.resolve(path.dirname(options2.moduleId), item) : getVitePathResolver()(item).replace(SRC_DIR_RE, "src/")), realPath: AsyncImportsInstance.getRealPath(item)?.[0] })); if (temp.some((item) => moduleIdProcessor(item.realPath ?? item.value).replace(JS_TYPES_RE, "") === targetModuleId)) { return { left: "AsyncImport(", right: ")" }; } } }, generateBundle({ format }, bundle) { if (!["es", "cjs", "iife"].includes(format) || isApp) return; const pageComponents = []; const hashFileMap = Object.entries(bundle).reduce((acc, [file, chunk]) => { if (chunk.type === "chunk") { let moduleId = chunk.facadeModuleId ?? void 0; if (moduleId?.startsWith("uniPage://") || moduleId?.startsWith("uniComponent://")) { const moduleIds = chunk.moduleIds.filter((id) => id !== moduleId).map((id) => moduleIdProcessor(id)); if (moduleIds.length >= 1 && moduleIds.length < chunk.moduleIds.length) { moduleId = moduleIds.at(-1); } else if (!moduleIds.length && chunk.fileName) { pageComponents.push(chunk); return acc; } } if (moduleId) { acc[moduleIdProcessor(moduleId)] = chunk.fileName; } else { const temp = chunk.moduleIds.filter((id) => !id.startsWith("\0")); temp.forEach((id) => { acc[moduleIdProcessor(id)] = chunk.fileName; }); } } return acc; }, {}); if (pageComponents.length) { const chunks = Object.values(bundle); for (let index = 0; index < chunks.length; index++) { const chunk = chunks[index]; if (chunk.type === "chunk") { const targetKey = Object.keys(hashFileMap).find((key) => { const value = hashFileMap[key]; return typeof value === "string" ? chunk.imports.includes(value) : value.some((item) => chunk.imports.includes(item)); }); if (targetKey) { const old = typeof hashFileMap[targetKey] === "string" ? [hashFileMap[targetKey]] : hashFileMap[targetKey] || []; hashFileMap[targetKey] = [...old, chunk.fileName]; } } } } for (const file in bundle) { const chunk = bundle[file]; if (chunk.type === "chunk" && chunk.code.includes("AsyncImport")) { const code = chunk.code; const asyncImports = parseAsyncImports(code); if (asyncImports.length > 0) { const magicString = new MagicString(code); asyncImports.forEach(({ full, args }) => { args.forEach(({ start, end, value }) => { const url = value.toString(); if (isMP ? Object.values(hashFileMap).flat().includes(normalizePath(path.posix.join(path.dirname(chunk.fileName), url))) : Object.values(hashFileMap).flat().map(resolveAssetsPath).includes(url)) { magicString.overwrite(full.start, full.start + "AsyncImport".length, isMP ? "require.async" : "import", { contentOnly: true }); } }); }); chunk.code = magicString.toString(); } } } } }; } function generateModuleDeclaration(paths, cache) { const moduleMapEntries = Array.from(/* @__PURE__ */ new Set([...cache || [], ...paths || []]))?.map((p) => { return ` '${p}': typeof import('${p}')`; }).join("\n"); return `/* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // Generated by bundle-optimizer export {} interface ModuleMap { ${moduleMapEntries ? `${moduleMapEntries} [path: string]: any` : " [path: string]: any"} } declare global { function AsyncImport<T extends keyof ModuleMap>(arg: T): Promise<ModuleMap[T]> } `; } const AsyncImport = (...options) => { return [ // 处理 `AsyncImport` 函数调用的路径传参 AsyncImportProcessor(...options) ]; }; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class ParseOptions { constructor(options) { __publicField(this, "options"); this.options = options; } get enable() { const { enable: origin = true } = this.options; return typeof origin === "boolean" ? { "optimization": origin, "async-component": origin, "async-import": origin } : { "optimization": origin.optimization ?? true, "async-component": origin["async-component"] ?? true, "async-import": origin["async-import"] ?? true }; } get dts() { const { dts: origin = true } = this.options; if (typeof origin === "boolean") { return { "async-component": this.generateDtsOptions(origin, "async-component.d.ts"), "async-import": this.generateDtsOptions(origin, "async-import.d.ts") }; } return { "async-component": (origin.enable ?? true) !== false && this.generateDtsOptions(origin["async-component"], "async-component.d.ts", origin.base), "async-import": (origin.enable ?? true) !== false && this.generateDtsOptions(origin["async-import"], "async-import.d.ts", origin.base) }; } generateDtsOptions(params = true, name, base = "./") { if (params === false) return false; const path = typeof params === "boolean" ? `${normalizePath(base).replace(/\/$/, "")}/${name}` : params.enable !== false && normalizePath(params.path || `${normalizePath(params.base ?? base).replace(/\/$/, "")}/${params.name ?? name}`); return path !== false && { enable: true, path }; } get logger() { const { logger: origin = false } = this.options; const _ = ["optimization", "async-component", "async-import"]; const temp = typeof origin === "boolean" ? origin ? _ : false : origin; return Object.fromEntries(_.map((item) => [item, (temp || []).includes(item)])); } } function UniappSubPackagesOptimization(enableLogger) { const platform = process.env.UNI_PLATFORM; const inputDir = process.env.UNI_INPUT_DIR; if (!platform || !inputDir) { throw new Error("`UNI_INPUT_DIR` or `UNI_PLATFORM` is not defined"); } const manifestJson = parseManifestJsonOnce(inputDir); const platformOptions = manifestJson[platform] || {}; const optimization = platformOptions.optimization || {}; process.env.UNI_OPT_TRACE = `${!!optimization.subPackages}`; const pagesJsonPath = path.resolve(inputDir, "pages.json"); const jsonStr = fs.readFileSync(pagesJsonPath, "utf8"); const { appJson } = parseMiniProgramPagesJson(jsonStr, platform, { subpackages: true }); process.UNI_SUBPACKAGES = appJson.subPackages || {}; const UNI_SUBPACKAGES = process.UNI_SUBPACKAGES || {}; const subPkgsInfo = Object.values(UNI_SUBPACKAGES); const normalFilter = ({ independent }) => !independent; const independentFilter = ({ independent }) => independent; const map2Root = ({ root }) => `${root.replace(/\/$/, "")}/`; const subPackageRoots = subPkgsInfo.map(map2Root); const normalSubPackageRoots = subPkgsInfo.filter(normalFilter).map(map2Root); subPkgsInfo.filter(independentFilter).map(map2Root); function moduleIdProcessor$1(id) { return moduleIdProcessor(id, process.env.UNI_INPUT_DIR); } const moduleFrom = (id) => { let root = normalizePath(ROOT_DIR); if (!root.endsWith("/")) root = `${root}/`; const clearId = moduleIdProcessor$1(id); if (!path.isAbsolute(clearId)) { const pkgRoot = normalSubPackageRoots.find((root2) => moduleIdProcessor$1(clearId).indexOf(root2) === 0); if (pkgRoot === void 0) return { from: clearId.startsWith("node_modules/") ? "node_modules" : "main", clearId }; else return { from: "sub", clearId, pkgRoot }; } else { if (clearId.includes("/node_modules/")) return { from: "node_modules", clearId }; } }; const findSubPackages = function(importers) { return importers.reduce((pkgs, item) => { const pkgRoot = normalSubPackageRoots.find((root) => moduleIdProcessor$1(item).indexOf(root) === 0); pkgRoot && pkgs.add(pkgRoot); return pkgs; }, /* @__PURE__ */ new Set()); }; const hasNoSubPackage = function(importers) { return importers.some((item) => { return !subPackageRoots.some((root) => moduleIdProcessor$1(item).indexOf(root) === 0); }); }; const findMainPackage = function(importers) { const list = importers.filter((item) => { const id = moduleIdProcessor$1(item); return !subPackageRoots.some((root) => id.indexOf(root) === 0) && !id.includes("node_modules"); }); return list; }; const findMainPackageComponent = function(importers) { const list = findMainPackage(importers); const mainPackageComponent = new Set(list.map((item) => moduleIdProcessor$1(item)).filter((name) => name.endsWith(".vue") || name.endsWith(".nvue"))); return mainPackageComponent; }; const hasEntryFile = function(importers, meta) { const list = findMainPackage(importers); return list.some((item) => meta.getModuleInfo(item)?.isEntry); }; logger.info("[optimization] \u5206\u5305\u4F18\u5316\u63D2\u4EF6\u5DF2\u542F\u7528", !enableLogger); return { name: "uniapp-subpackages-optimization", enforce: "post", // 控制执行顺序,post 保证在其他插件之后执行 config(config, { command }) { if (!platform.startsWith("mp")) { logger.warn("[optimization] \u5206\u5305\u4F18\u5316\u63D2\u4EF6\u4EC5\u9700\u5728\u5C0F\u7A0B\u5E8F\u5E73\u53F0\u542F\u7528\uFF0C\u8DF3\u8FC7", !enableLogger); return; } const UNI_OPT_TRACE = process.env.UNI_OPT_TRACE === "true"; logger.info(`[optimization] \u5206\u5305\u4F18\u5316\u5F00\u542F\u72B6\u6001: ${UNI_OPT_TRACE}`, false); if (!UNI_OPT_TRACE) return; const originalOutput = config?.build?.rollupOptions?.output; const existingManualChunks = Array.isArray(originalOutput) ? originalOutput[0]?.manualChunks : originalOutput?.manualChunks; const mergedManualChunks = (id, meta) => { function getDependencyGraph(startId, getRelated = (info) => info.importers) { const visited = /* @__PURE__ */ new Set(); const result = []; function traverse(currentId, getRelated2) { if (visited.has(currentId)) return; visited.add(currentId); result.push(currentId); const moduleInfo = meta.getModuleInfo(currentId); if (!moduleInfo) return; getRelated2(moduleInfo).forEach((relatedId) => { traverse(relatedId, getRelated2); }); } traverse(startId, getRelated); return result; } const normalizedId = normalizePath(id); const filename = normalizedId.split("?")[0]; if (EXTNAME_JS_RE.test(filename) && (!filename.startsWith(inputDir) || filename.includes("node_modules"))) { const moduleInfo = meta.getModuleInfo(id); if (!moduleInfo) { throw new Error(`moduleInfo is not found: ${id}`); } const importers = moduleInfo.importers || []; const matchSubPackages = findSubPackages(importers); const mainPackageComponent = findMainPackageComponent(importers); const isEntry = hasEntryFile(importers, meta); const moduleFromInfos = moduleFrom(id); let isMain = false; if ( // 未知来源的模块、commonjsHelpers => 打入主包 !moduleFromInfos || moduleFromInfos.clearId === "commonjsHelpers.js" || isEntry || moduleFromInfos.from === "main" && (!importers.length || !matchSubPackages.size) || mainPackageComponent.size > 0 || matchSubPackages.size > 1 ) { isMain = true; } if (!isMain) { if (matchSubPackages.size === 1 && !hasNoSubPackage(importers)) { return `${matchSubPackages.values().next().value}common/vendor`; } const importersGraph = getDependencyGraph(id); const newMatchSubPackages = findSubPackages(importersGraph); const newMainPackageComponent = findMainPackageComponent(importersGraph); if (newMatchSubPackages.size === 1 && newMainPackageComponent.size === 0) { return `${newMatchSubPackages.values().next().value}common/vendor`; } } } if (existingManualChunks && typeof existingManualChunks === "function") return existingManualChunks(id, meta); }; return { build: { rollupOptions: { output: { manualChunks: mergedManualChunks } } } }; } }; } const index = (options = {}) => { const parse = new ParseOptions(options); return [ { name: "optimization:initialized", config(config) { initializeVitePathResolver(config); } }, // 分包优化 parse.enable.optimization && UniappSubPackagesOptimization(parse.logger.optimization), // js/ts插件的异步调用 parse.enable["async-import"] && AsyncImport(parse.dts["async-import"], parse.logger["async-import"]), // vue组件的异步调用 parse.enable["async-component"] && AsyncComponent(parse.dts["async-component"], parse.logger["async-component"]) ]; }; export { index as default };