@uni-ku/root
Version:
借助 Vite 模拟出虚拟的全局组件,解决 Uniapp 无法使用全局共享组件问题
202 lines (193 loc) • 6.31 kB
JavaScript
const node_path = require('node:path');
const process = require('node:process');
const chokidar = require('chokidar');
const vite = require('vite');
const compilerSfc = require('@vue/compiler-sfc');
const node_fs = require('node:fs');
const jsoncParser = require('jsonc-parser');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const process__default = /*#__PURE__*/_interopDefaultCompat(process);
const chokidar__default = /*#__PURE__*/_interopDefaultCompat(chokidar);
async function parseSFC(code) {
try {
return compilerSfc.parse(code, { pad: "space" }).descriptor || compilerSfc.parse({ source: code });
} catch {
throw new Error(
"[@uni-ku/root] Vue's version must be 3.2.13 or higher."
);
}
}
function formatPagePath(root, path) {
return vite.normalizePath(`${node_path.join(root, path)}.vue`);
}
function loadPagesJson(path, rootPath) {
const pagesJsonRaw = node_fs.readFileSync(path, "utf-8");
const { pages = [], subPackages = [] } = jsoncParser.parse(pagesJsonRaw);
return [
...pages.map((page) => formatPagePath(rootPath, page.path)),
...subPackages.map(({ pages: pages2 = {}, root = "" }) => {
return pages2.map((page) => formatPagePath(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 void 0;
};
return traverse(nodeAst.children);
}
const platform = process__default.env.UNI_PLATFORM;
function normalizePlatformPath(id) {
const idExt = node_path.extname(id);
if (idExt !== ".vue") {
return id;
}
if (!id.includes(`.${platform}.`)) {
return id;
}
return id.replace(`.${platform}.`, ".");
}
async function transformPage(code, enabledGlobalRef = false) {
const sfc = await parseSFC(code);
const ms = new compilerSfc.MagicString(code);
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);
}
const pageTempAttrs = sfc.template?.attrs;
let pageRootRefSource = enabledGlobalRef ? 'ref="uniKuRoot"' : "";
if (pageTempAttrs && pageTempAttrs.root) {
pageRootRefSource = `ref="${pageTempAttrs.root}"`;
}
if (pageTempStart && pageTempEnd) {
ms.appendLeft(pageTempStart, `
${pageMetaSource}
<global-ku-root ${pageRootRefSource}>`);
ms.appendRight(pageTempEnd, `
</global-ku-root>
`);
}
return ms;
}
async function registerKuApp(code, fileName = "App.ku") {
const ms = new compilerSfc.MagicString(code);
const importCode = `import GlobalKuRoot from "./${fileName}.vue";`;
const vueUseComponentCode = `app.component("global-ku-root", GlobalKuRoot);`;
ms.prepend(`${importCode}
`).replace(
/(createApp[\s\S]*?)(return\s\{\s*app)/,
`$1${vueUseComponentCode}
$2`
);
return ms;
}
async function rebuildKuApp(code, enabledVirtualHost = false) {
const ms = new compilerSfc.MagicString(code);
const rootTagNameRE = /<(KuRootView|ku-root-view)(\s*\/>|><\/\1>)/;
ms.replace(rootTagNameRE, "<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,
}
}
<\/script>`);
}
return ms;
}
function UniKuRoot(options = {
rootFileName: "App.ku"
}) {
const rootPath = process__default.env.UNI_INPUT_DIR || `${process__default.env.INIT_CWD}\\src`;
const appKuPath = node_path.resolve(rootPath, `${options.rootFileName}.vue`);
const pagesPath = node_path.resolve(rootPath, "pages.json");
let pagesJson = loadPagesJson(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)) {
pagesJson = loadPagesJson(pagesPath, rootPath);
}
});
},
async transform(code, id) {
let ms = null;
const filterMain = vite.createFilter(`${rootPath}/main.(ts|js)`);
if (filterMain(id)) {
ms = await registerKuApp(code, options.rootFileName);
}
const filterKuRoot = vite.createFilter(appKuPath);
if (filterKuRoot(id)) {
ms = await rebuildKuApp(code, options.enabledVirtualHost);
}
const pageId = hasPlatformPlugin ? normalizePlatformPath(id) : id;
const filterPage = vite.createFilter(pagesJson);
if (filterPage(pageId)) {
ms = await transformPage(code, options.enabledGlobalRef);
}
if (ms) {
return {
code: ms.toString(),
map: ms.generateMap({ hires: true })
};
}
},
buildEnd() {
if (watcher) {
watcher.close();
}
}
};
}
module.exports = UniKuRoot;
;