UNPKG

@uni-ku/root

Version:

借助 Vite 模拟出虚拟的全局组件,解决 Uniapp 无法使用全局共享组件问题

234 lines (233 loc) 10.3 kB
//#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;