@uni-ku/root
Version:
借助 Vite 模拟出虚拟的全局组件,解决 Uniapp 无法使用全局共享组件问题
234 lines (233 loc) • 10.3 kB
JavaScript
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let node_path = require("node:path");
let node_process = require("node:process");
node_process = __toESM(node_process, 1);
let chokidar = require("chokidar");
chokidar = __toESM(chokidar, 1);
let vite = require("vite");
let _vue_compiler_sfc = require("@vue/compiler-sfc");
let node_fs = require("node:fs");
let jsonc_parser = require("jsonc-parser");
//#region src/utils.ts
async function parseSFC(code) {
try {
return (0, _vue_compiler_sfc.parse)(code, { pad: "space" }).descriptor || (0, _vue_compiler_sfc.parse)({ source: code });
} catch {
throw new Error("[@uni-ku/root] Vue's version must be 3.2.13 or higher.");
}
}
const PAGE_FILE_EXTS = [".vue", ".nvue"];
function formatPagePaths(root, path) {
const joinedPath = (0, node_path.join)(root, path);
if ((0, node_path.extname)(joinedPath)) return [(0, vite.normalizePath)(joinedPath)];
const pageFilePaths = PAGE_FILE_EXTS.map((fileExt) => `${joinedPath}${fileExt}`).filter((filePath) => (0, node_fs.existsSync)(filePath));
if (pageFilePaths.length) return pageFilePaths.map((filePath) => (0, vite.normalizePath)(filePath));
return [(0, vite.normalizePath)(`${joinedPath}.vue`)];
}
function loadPagePaths(path, rootPath) {
const pagesJson = (0, jsonc_parser.parse)((0, node_fs.readFileSync)(path, "utf-8"));
const { pages = [] } = pagesJson;
const subPackages = pagesJson.subPackages ?? pagesJson.subpackages ?? [];
return [...pages.flatMap((page) => formatPagePaths(rootPath, page.path)), ...subPackages.map(({ pages = [], root = "" }) => {
return pages.flatMap((page) => formatPagePaths((0, node_path.join)(rootPath, root), page.path));
}).flat()];
}
function toKebabCase(str) {
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
}
function toPascalCase(str) {
return str.replace(/(^\w|-+\w)/g, (match) => match.toUpperCase().replace(/-/g, ""));
}
function findNode(sfc, rawTagName) {
const templateSource = sfc.template?.content;
if (!templateSource) return;
let tagName = "";
if (templateSource.includes(`<${toKebabCase(rawTagName)}`)) tagName = toKebabCase(rawTagName);
else if (templateSource.includes(`<${toPascalCase(rawTagName)}`)) tagName = toPascalCase(rawTagName);
if (!tagName) return;
const nodeAst = sfc.template?.ast;
if (!nodeAst) return;
const traverse = (nodes) => {
for (const node of nodes) if (node.type === 1) {
if (node.tag === tagName) return node;
if (node.children?.length) {
const found = traverse(node.children);
if (found) return found;
}
}
};
return traverse(nodeAst.children);
}
const platform = node_process.default.env.UNI_PLATFORM;
function normalizePlatformPath(id) {
if ((0, node_path.extname)(id) !== ".vue") return id;
if (!id.includes(`.${platform}.`)) return id;
return id.replace(`.${platform}.`, ".");
}
function toArray(value) {
if (!value) return [];
return Array.isArray(value) ? value : [value];
}
function getRelativePath(fromFile, toFile) {
const importPath = (0, vite.normalizePath)((0, node_path.relative)((0, node_path.dirname)(fromFile), toFile));
return importPath.startsWith(".") ? importPath : `./${importPath}`;
}
//#endregion
//#region src/page.ts
async function transformPage(code, enabledGlobalRef = false) {
const sfc = await parseSFC(code);
const ms = new _vue_compiler_sfc.MagicString(code);
wrapTemplate(ms, sfc, "global-ku-root", getPageRootRefSource(sfc, enabledGlobalRef));
return ms;
}
async function transformNvuePage(code, componentImportPath, enabledGlobalRef = false) {
const sfc = await parseSFC(code);
const ms = new _vue_compiler_sfc.MagicString(code);
wrapTemplate(ms, sfc, "GlobalKuRoot", getPageRootRefSource(sfc, enabledGlobalRef));
if (sfc.scriptSetup) ms.appendLeft(sfc.scriptSetup.loc.start.offset, getNvueRootImport(componentImportPath));
else if (sfc.script) {
ms.appendLeft(sfc.script.loc.start.offset, getNvueRootImport(componentImportPath));
ensureNvueOptionsComponent(ms, sfc.script);
} else ms.append(`\n<script>\n${getNvueRootImport(componentImportPath)}export default {\n components: { GlobalKuRoot },\n}\n<\/script>\n`);
return ms;
}
function getPageRootRefSource(sfc, enabledGlobalRef = false) {
const pageTempAttrs = sfc.template?.attrs;
if (pageTempAttrs && pageTempAttrs.root) return `ref="${pageTempAttrs.root}"`;
return enabledGlobalRef ? "ref=\"uniKuRoot\"" : "";
}
function wrapTemplate(ms, sfc, tagName, rootRefSource = "") {
const pageTempStart = sfc.template?.loc.start.offset;
const pageTempEnd = sfc.template?.loc.end.offset;
let pageMetaSource = "";
const pageMetaNode = findNode(sfc, "PageMeta");
if (pageMetaNode) {
pageMetaSource = pageMetaNode.loc.source;
const metaTempStart = pageMetaNode.loc.start.offset;
const metaTempEnd = pageMetaNode.loc.end.offset;
ms.remove(metaTempStart, metaTempEnd);
}
if (pageTempStart != null && pageTempEnd != null) {
const refSource = rootRefSource ? ` ${rootRefSource}` : "";
const pageMetaPrefix = pageMetaSource ? `\n${pageMetaSource}\n` : "\n";
ms.appendLeft(pageTempStart, `${pageMetaPrefix}<${tagName}${refSource}>`);
ms.appendRight(pageTempEnd, `\n</${tagName}>\n`);
}
}
function getNvueRootImport(componentImportPath) {
return `import GlobalKuRoot from '${componentImportPath}'\n`;
}
function findExportDefaultObjectStart(scriptContent) {
const exportDefaultIndex = scriptContent.indexOf("export default");
if (exportDefaultIndex < 0) return -1;
const afterExportDefault = scriptContent.slice(exportDefaultIndex + 14);
const defineComponentMatch = afterExportDefault.match(/^\s*defineComponent\s*\(\s*\{/);
if (defineComponentMatch) return exportDefaultIndex + 14 + defineComponentMatch[0].length;
const objectMatch = afterExportDefault.match(/^\s*\{/);
if (objectMatch) return exportDefaultIndex + 14 + objectMatch[0].length;
return -1;
}
function ensureNvueOptionsComponent(ms, script) {
const componentsMatch = script.content.match(/components\s*:\s*\{/);
if (componentsMatch && componentsMatch.index != null) {
ms.appendLeft(componentsMatch.index + componentsMatch[0].length, " GlobalKuRoot,");
return;
}
const exportDefaultObjectStart = findExportDefaultObjectStart(script.content);
if (exportDefaultObjectStart >= 0) {
ms.appendLeft(exportDefaultObjectStart, "\n components: { GlobalKuRoot },");
return;
}
ms.appendLeft(script.loc.end.offset, "\nexport default {\n components: { GlobalKuRoot },\n}\n");
}
//#endregion
//#region src/root.ts
async function registerKuApp(code, fileName = "App.ku") {
const ms = new _vue_compiler_sfc.MagicString(code);
const importCode = `import GlobalKuRoot from "./${fileName}.vue";`;
ms.prepend(`${importCode}\n`).replace(/(createApp[\s\S]*?)(return\s\{\s*app)/, `$1app.component("global-ku-root", GlobalKuRoot);\n$2`);
return ms;
}
async function rebuildKuApp(code, enabledVirtualHost = false) {
const ms = new _vue_compiler_sfc.MagicString(code);
ms.replace(/<(KuRootView|ku-root-view)(?:\s*\/>|><\/\1>)/, "<slot />");
if (enabledVirtualHost) {
const sfc = await parseSFC(code);
if (sfc.script) return ms;
const langType = sfc.scriptSetup?.lang;
ms.append(`<script ${langType ? `lang="${langType}"` : ""}>
export default {
options: {
virtualHost: true,
}
}\n<\/script>`);
}
return ms;
}
//#endregion
//#region src/index.ts
function UniKuRoot(options) {
options = {
enabledVirtualHost: false,
enabledGlobalRef: false,
rootFileName: "App.ku",
...options
};
const rootPath = (0, vite.normalizePath)(node_process.default.env.UNI_INPUT_DIR || (0, node_path.resolve)(node_process.default.env.INIT_CWD || node_process.default.cwd(), "src"));
const appKuPath = (0, vite.normalizePath)((0, node_path.resolve)(rootPath, `${options.rootFileName}.vue`));
const pagesPath = (0, vite.normalizePath)((0, node_path.resolve)(rootPath, "pages.json"));
const excludedPaths = toArray(options.excludePages).filter(Boolean).map((path) => (0, vite.normalizePath)((0, node_path.resolve)(rootPath, path)));
const mainFiles = [(0, vite.normalizePath)((0, node_path.resolve)(rootPath, "main.ts")), (0, vite.normalizePath)((0, node_path.resolve)(rootPath, "main.js"))];
let pagePaths = loadPagePaths(pagesPath, rootPath);
let watcher = null;
let hasPlatformPlugin = false;
return {
name: "vite-plugin-uni-root",
enforce: "pre",
configResolved({ plugins }) {
hasPlatformPlugin = plugins.some((v) => v.name === "vite-plugin-uni-platform");
},
buildStart() {
watcher = chokidar.default.watch(pagesPath).on("all", (event) => {
if (["add", "change"].includes(event)) pagePaths = loadPagePaths(pagesPath, rootPath);
});
},
async transform(code, id) {
let ms = null;
if ((0, vite.createFilter)(mainFiles)(id)) ms = await registerKuApp(code, options.rootFileName);
if ((0, vite.createFilter)(appKuPath)(id)) ms = await rebuildKuApp(code, options.enabledVirtualHost);
const pageId = hasPlatformPlugin ? normalizePlatformPath(id) : id;
if ((0, vite.createFilter)(pagePaths, excludedPaths)(pageId)) ms = id.endsWith(".nvue") ? await transformNvuePage(code, getRelativePath(id, appKuPath), options.enabledGlobalRef) : await transformPage(code, options.enabledGlobalRef);
if (ms) return {
code: ms.toString(),
map: ms.generateMap({ hires: true })
};
},
buildEnd() {
if (watcher) watcher.close();
}
};
}
//#endregion
module.exports = UniKuRoot;