UNPKG

@uni-ku/bundle-optimizer

Version:
1,186 lines (1,160 loc) 46.1 kB
'use strict'; const fs = require('node:fs'); const chalk = require('chalk'); const path = require('node:path'); const process$1 = require('node:process'); const uniCliShared = require('@dcloudio/uni-cli-shared'); const MagicString = require('magic-string'); const types = require('node:util/types'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const fs__default = /*#__PURE__*/_interopDefaultCompat(fs); const chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk); const path__default = /*#__PURE__*/_interopDefaultCompat(path); const process__default = /*#__PURE__*/_interopDefaultCompat(process$1); const MagicString__default = /*#__PURE__*/_interopDefaultCompat(MagicString); const name = "@uni-ku/bundle-optimizer"; 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 Logger { constructor(level = "INFO" /* INFO */, context = "Plugin", isImplicit = false) { __publicField$3(this, "level"); __publicField$3(this, "context"); /** TODO: 可以使用其他的 debug 日志库 */ __publicField$3(this, "Debugger", null); /** 全局兜底:是否是隐式log */ __publicField$3(this, "isImplicit"); __publicField$3(this, "onLog"); this.level = level; this.context = context; this.isImplicit = isImplicit; } log(level, message, isImplicit) { if (this.shouldLog(level)) { const coloredMessage = this.getColoredMessage(level, message); this.onLog?.(this.context, level, message, Date.now()); if (isImplicit ?? this.isImplicit) ; else { const c = 69; const colorCode = `\x1B[3${`8;5;${c}`};1m`; console.log(` ${chalk__default(`${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__default.blue(`[${level}] ${message}`); case "INFO" /* INFO */: return chalk__default.green(`[${level}] ${message}`); case "WARN" /* WARN */: return chalk__default.yellow(`[${level}] ${message}`); case "ERROR" /* ERROR */: return chalk__default.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 */, "uni-ku:bundle-optimizer"); const EXTNAME_JS_RE = /\.(js|jsx|ts|uts|tsx|mjs|json)$/; const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/; const knownJsSrcRE = /\.(?:[jt]sx?|m[jt]s|vue|marko|svelte|astro|imba|mdx)(?:$|\?)/; const ASSETS_DIR_RE = /^(\.?\/)?assets\//; const SRC_DIR_RE = /^(\.?\/)?src\//; const EXT_RE = /\.\w+$/; const ROOT_DIR = process__default.env.VITE_ROOT_DIR; if (!ROOT_DIR) { throw new Error("`ROOT_DIR` is not defined"); } function splitOnFirst(str, separator) { if (!(typeof str === "string" && (typeof separator === "string" || separator instanceof RegExp))) { throw new TypeError("Expected the arguments to be of type `string` and `RegExp`"); } if (str === "" || separator === "") { return []; } let separatorIndex = -1; let matchLength = 1; if (typeof separator === "string") { separatorIndex = str.indexOf(separator); matchLength = separator.length; } else if (separator instanceof RegExp) { const nonGlobalRegex = new RegExp(separator.source, separator.flags.replace("g", "")); const match = nonGlobalRegex.exec(str); if (match) { separatorIndex = match.index; matchLength = match[0].length; } } if (separatorIndex === -1) { return []; } return [ str.slice(0, separatorIndex), str.slice(separatorIndex + matchLength) ]; } const token = "%[a-f0-9]{2}"; const singleMatcher = new RegExp(`(${token})|([^%]+)`, "gi"); const multiMatcher = new RegExp(`(${token})+`, "gi"); function decodeComponents(components, split) { try { return [decodeURIComponent(components.join(""))]; } catch { } if (components.length === 1) { return components; } split = split || 1; const left = components.slice(0, split); const right = components.slice(split); return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right)); } function decode$1(input) { try { return decodeURIComponent(input); } catch { let tokens = input.match(singleMatcher) || []; for (let i = 1; i < tokens.length; i++) { input = decodeComponents(tokens, i).join(""); tokens = input.match(singleMatcher) || []; } return input; } } function customDecodeURIComponent(input) { const replaceMap = { "%FE%FF": "\uFFFD\uFFFD", "%FF%FE": "\uFFFD\uFFFD" }; let match = multiMatcher.exec(input); while (match) { try { replaceMap[match[0]] = decodeURIComponent(match[0]); } catch { const result = decode$1(match[0]); if (result !== match[0]) { replaceMap[match[0]] = result; } } match = multiMatcher.exec(input); } replaceMap["%C2"] = "\uFFFD"; const entries = Object.keys(replaceMap); for (const key of entries) { input = input.replace(new RegExp(key, "g"), replaceMap[key]); } return input; } function decodeUriComponent(encodedURI) { if (typeof encodedURI !== "string") { throw new TypeError(`Expected \`encodedURI\` to be of type \`string\`, got \`${typeof encodedURI}\``); } try { return decodeURIComponent(encodedURI); } catch { return customDecodeURIComponent(encodedURI); } } function isNil(value) { return value === null || value === void 0; } function decode(value, options = {}) { if (options.decode) { return decodeUriComponent(value); } return value; } function parse(query, options = {}) { options = { decode: true, arrayFormat: "none", arrayFormatSeparator: ",", types: /* @__PURE__ */ Object.create(null), ...options }; if (!validateArrayFormatSeparator(options.arrayFormatSeparator)) { throw new TypeError("arrayFormatSeparator must be single character string"); } const formatter = parserForArrayFormat(options); const returnValue = /* @__PURE__ */ Object.create(null); if (typeof query !== "string") { return returnValue; } query = query.trim().replace(/^[?#&]/, ""); if (!query) { return returnValue; } for (const pair of query.split("&")) { if (pair === "") { continue; } const parameter = options.decode ? pair.replaceAll("+", " ") : pair; let [key, value] = splitOnFirst(parameter, "="); key ?? (key = parameter); value = isNil(value) ? null : decode(value, options); formatter(decode(key, options), value, returnValue); } return returnValue; } function validateArrayFormatSeparator(value) { return typeof value === "string" && value.length === 1; } function parserForArrayFormat(options) { let result = null; function canBeArray(value) { return typeof value === "string" && value.includes(options.arrayFormatSeparator); } function canBeEncodedArray(value) { return typeof value === "string" && !canBeArray(value) && decode(value, options).includes(options.arrayFormatSeparator); } function tryBeArray(value) { return canBeArray(value) || canBeEncodedArray(value); } switch (options.arrayFormat) { case "index": { return (key, value, accumulator) => { result = /\[(\d*)\]$/.exec(key); key = key.replace(/\[\d*\]$/, ""); if (!result) { accumulator[key] = value; return; } if (accumulator[key] === void 0) { accumulator[key] = {}; } accumulator[key][result[1]] = value; }; } case "bracket": { return (key, value, accumulator) => { result = /(\[\])$/.exec(key); key = key.replace(/\[\]$/, ""); if (!result) { accumulator[key] = value; return; } if (accumulator[key] === void 0) { accumulator[key] = [value]; return; } accumulator[key] = [...accumulator[key], value]; }; } case "colon-list-separator": { return (key, value, accumulator) => { result = /(:list)$/.exec(key); key = key.replace(/:list$/, ""); if (!result) { accumulator[key] = value; return; } if (accumulator[key] === void 0) { accumulator[key] = [value]; return; } accumulator[key] = [...accumulator[key], value]; }; } case "comma": case "separator": { return (key, value, accumulator) => { value = isNil(value) ? value : canBeEncodedArray(value) ? decode(value, options) : value; const newValue = isNil(value) ? value : tryBeArray(value) ? value.split(options.arrayFormatSeparator).map((item) => decode(item, options)) : decode(value, options); accumulator[key] = newValue; }; } case "bracket-separator": { return (key, value, accumulator) => { const isArray = /\[\]$/.test(key); key = key.replace(/\[\]$/, ""); if (!isArray) { accumulator[key] = value ? decode(value, options) : value; return; } const arrayValue = isNil(value) ? [] : decode(value, options).split(options.arrayFormatSeparator); if (accumulator[key] === void 0) { accumulator[key] = arrayValue; return; } accumulator[key] = [...accumulator[key], ...arrayValue]; }; } default: { return (key, value, accumulator) => { if (accumulator[key] === void 0) { accumulator[key] = value; return; } accumulator[key] = [...[accumulator[key]].flat(), value]; }; } } } 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" && !types.isRegExp(replacement) && !replacement.includes("*")) { replacement = normalizePath(replacement); } if (!types.isRegExp(replacement) && typeof replacement === "string" && !replacement.includes("*") && !types.isRegExp(find) && typeof find === "string" && !find.includes("*")) { if (source === find) { return relative ? replacement : path__default.resolve(replacement); } else if (source.startsWith(find)) { const realPath = source.replace(find, replacement); return relative ? realPath : path__default.resolve(realPath); } } else if (source.match(find) && (types.isRegExp(find) || !find.includes("*"))) { const realPath = source.replace(find, replacement); return relative ? realPath : path__default.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__default.platform === "win32" ? slash(id) : id; } function ensureDirectoryExists(filePath) { const dir = path__default.dirname(filePath); if (!fs__default.existsSync(dir)) { fs__default.mkdirSync(dir, { recursive: true }); } } function moduleIdProcessor(id, rootDir = ROOT_DIR, removeQuery = true) { rootDir = normalizePath(rootDir); if (!rootDir.endsWith("/")) rootDir += "/"; const normalized = normalizePath(id); const name = removeQuery ? normalized.split("?")[0] : normalized; const updatedName = name.replace(rootDir, ""); if (updatedName.startsWith("\0")) return updatedName.slice(1); return updatedName; } function calculateRelativePath(importer, imported) { if (imported.match(/^(\.\/|\.\.\/)+/)) { imported = path__default.resolve(path__default.dirname(importer), imported); } const relativePath = path__default.relative(path__default.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 parseQuerystring(url) { if (!url || typeof url !== "string") { return null; } const rmExtUrl = url.replace(EXT_RE, ""); const queryStr = rmExtUrl.split("?")[1] || ""; if (!queryStr) { return null; } try { return Object.entries(parse(queryStr)).reduce((acc, [key, value]) => { acc[key] = value === null || value === "true" ? true : value === "false" ? false : value; return acc; }, {}); } catch (error) { console.error("Error parsing query string:", error); return null; } } 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 ParseOptions { constructor(options) { __publicField$2(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)])); } } 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 AsyncComponents { constructor() { __publicField$1(this, "scriptDescriptors", /* @__PURE__ */ new Map()); __publicField$1(this, "jsonAsyncComponentsCache", /* @__PURE__ */ new Map()); /** 当前状态下热更新时会导致把原有的json内容清除,操作过的page-json需要记录之前的内容 */ __publicField$1(this, "pageJsonCache", /* @__PURE__ */ new Map()); __publicField$1(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 || {}); } } function AsyncComponentProcessor(options, enableLogger) { const inputDir = process__default.env.UNI_INPUT_DIR; const platform = process__default.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__default.resolve(ROOT_DIR, normalizePath(options.path)); ensureDirectoryExists(typesFilePath); let cache = []; if (fs__default.existsSync(typesFilePath)) { const list = lexFunctionCalls(fs__default.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__default.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 = uniCliShared.removeExt(uniCliShared.normalizeMiniProgramFilename(importer, inputDir)); const tempBindings = {}; const magicString = new MagicString__default(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(Object.assign({}, cache, { 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 @uni-ku/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; } 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 AsyncImports { constructor() { __publicField(this, "cache", /* @__PURE__ */ new Map()); __publicField(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__default.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__default.resolve(ROOT_DIR, normalizePath(options.path)); ensureDirectoryExists(typesFilePath); let cache = []; if (fs__default.existsSync(typesFilePath)) { const list = lexFunctionCalls(fs__default.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__default.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__default(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__default.resolve(path__default.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__default(code); asyncImports.forEach(({ full, args }) => { args.forEach(({ start, end, value }) => { const url = value.toString(); if (isMP ? Object.values(hashFileMap).flat().includes(normalizePath(path__default.posix.join(path__default.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 @uni-ku/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]> } `; } function SubPackagesOptimization(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 = uniCliShared.parseManifestJsonOnce(inputDir); const platformOptions = manifestJson[platform] || {}; const optimization = platformOptions.optimization || {}; process.env.UNI_OPT_TRACE = `${!!optimization.subPackages}`; const pagesJsonPath = path__default.resolve(inputDir, "pages.json"); const jsonStr = fs__default.readFileSync(pagesJsonPath, "utf8"); const { appJson } = uniCliShared.parseMiniProgramPagesJson(jsonStr, platform, { subpackages: true }); const pagesFlat = { pages: appJson.pages || [], subPackages: (appJson.subPackages || []).flatMap((pkg) => { return pkg.pages.map((page) => `${pkg.root}/${page}`.replace(/\/{2,}/g, "/")); }), get all() { return [...this.pages, ...this.subPackages]; } }; logger.info(`pagesFlat: ${JSON.stringify(pagesFlat, null, 2)}`, 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, removeQuery = true) { return moduleIdProcessor(id, process.env.UNI_INPUT_DIR, removeQuery); } 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 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 findNodeModules = function(importers) { const mainPackageList = findMainPackage(importers); return importers.filter((item) => { const id = moduleIdProcessor$1(item); return !mainPackageList.includes(item) && !subPackageRoots.some((root) => id.indexOf(root) === 0) && id.includes("node_modules"); }); }; const findNodeModulesComponent = function(importers) { const list = findNodeModules(importers); const nodeModulesComponent = new Set(list.map((item) => moduleIdProcessor$1(item)).filter((name) => name.endsWith(".vue") || name.endsWith(".nvue"))); return nodeModulesComponent; }; 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); }; const isVueScript = (moduleInfo) => { if (!moduleInfo.id || !moduleInfo.importers?.length) { return false; } const importer = moduleIdProcessor$1(moduleInfo.importers[0]); const id = moduleInfo.id; const clearId = moduleIdProcessor$1(id, false); const parsedUrl = parseQuerystring(clearId); return parsedUrl && parsedUrl.type === "script" && parsedUrl.vue && importer === moduleIdProcessor$1(id); }; 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]; let mainFlag = false; if (EXTNAME_JS_RE.test(filename) && (filename.startsWith(normalizePath(inputDir)) || filename.includes("node_modules"))) { const moduleInfo = meta.getModuleInfo(id); if (!moduleInfo) { throw new Error(`moduleInfo is not found: ${id}`); } const importersGraph = getDependencyGraph(id); const newMatchSubPackages = findSubPackages(importersGraph); const newMainPackageComponent = findMainPackageComponent(importersGraph); const nodeModulesComponent = findNodeModulesComponent(importersGraph); const isEntry = hasEntryFile(importersGraph, meta); if (!isEntry && newMatchSubPackages.size === 1 && newMainPackageComponent.size === 0 && nodeModulesComponent.size === 0) { logger.info(`[optimization] \u5B50\u5305: ${[...newMatchSubPackages].join(", ")} <- ${filename}`, !enableLogger); return `${newMatchSubPackages.values().next().value}common/vendor`; } mainFlag = id; } if (existingManualChunks && typeof existingManualChunks === "function") { const result = existingManualChunks(id, meta); if (result === void 0) { const moduleInfo = meta.getModuleInfo(id); if (moduleInfo) { const clearId = moduleIdProcessor$1(moduleInfo.id); if (mainFlag === id && !moduleInfo.isEntry && !findNodeModules([moduleInfo.id]).length) { logger.info(`[optimization] \u4E3B\u5305\u5185\u5BB9\u5F3A\u5236\u843D\u76D8: ${clearId}`, !enableLogger); return clearId; } if (isVueScript(moduleInfo) && !path__default.isAbsolute(clearId)) { const target = clearId.replace(knownJsSrcRE, ""); if (!pagesFlat.all.includes(target)) { logger.info(`[optimization] \u89C4\u6574 vue-script \u6A21\u5757: ${target} -> ${target}-vendor`, !enableLogger); return `${target}-vendor`; } } } } logger.warn(`[optimization] default: ${result} <- ${id}`, !enableLogger); return result; } }; return { build: { rollupOptions: { output: { manualChunks: mergedManualChunks } } } }; } }; } const index = (options = {}) => { const parse = new ParseOptions(options); let logToFile = options.logToFile; if (logToFile) { logToFile = typeof logToFile === "string" ? logToFile : `node_modules/.cache/${name}/logs.log`; if (typeof logToFile !== "string") { logger.warn("logToFile should be a string, using default path: node_modules/.cache/bundle-optimizer/logs.log"); logToFile = `node_modules/.cache/${name}/logs.log`; } ensureDirectoryExists(logToFile); try { fs__default.unlinkSync(logToFile); } catch (error) { logger.error(`Failed to delete old log file: ${error}`); } logger.onLog = (context, level, message, timestamp) => { const line = `${context} ${level} [${timestamp}]: ${message}`; fs__default.writeFileSync(logToFile, `${line} `, { flag: "a" }); }; } return [ { name: "optimization:initialized", config(config) { initializeVitePathResolver(config); } }, // 分包优化 parse.enable.optimization && SubPackagesOptimization(parse.logger.optimization), // js/ts插件的异步调用 // 处理 `AsyncImport` 函数调用的路径传参 parse.enable["async-import"] && AsyncImportProcessor(parse.dts["async-import"], parse.logger["async-import"]), // vue组件的异步调用 // 处理 `.vue?async` 查询参数的静态导入 parse.enable["async-component"] && AsyncComponentProcessor(parse.dts["async-component"], parse.logger["async-component"]) ]; }; module.exports = index;