optimizer-mini
Version:
uni-app 分包优化插件化实现
896 lines (876 loc) • 35.8 kB
JavaScript
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 };