初始化本地仓库
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { normalizePath } from 'vite';
|
||||
|
||||
/**
|
||||
* Parse Vue SFC file, return descriptor
|
||||
*
|
||||
* When vue/compiler-sfc is not available (e.g. HBuilderX projects where
|
||||
* vue is not in node_modules), returns null instead of throwing.
|
||||
* Callers should fall back to regex-based matching.
|
||||
*/
|
||||
export async function parseSFC(code: string) {
|
||||
try {
|
||||
const { parse } = await import('vue/compiler-sfc');
|
||||
return parse(code, { pad: 'space' }).descriptor;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip JSON comments (single-line and multi-line)
|
||||
*/
|
||||
export function stripJsonComments(str: string): string {
|
||||
return str.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert pages.json path to absolute file path
|
||||
*/
|
||||
export function formatPagePath(root: string, path: string) {
|
||||
return normalizePath(`${join(root, path)}.vue`).replace(/^[A-Z]:/, match => match.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load pages.json and resolve all page paths (main + subPackages)
|
||||
*/
|
||||
export function loadPagesJson(path: string, rootPath: string): string[] {
|
||||
const raw = readFileSync(path, 'utf-8');
|
||||
const { pages = [], subPackages = [] } = JSON.parse(stripJsonComments(raw));
|
||||
|
||||
return [
|
||||
...pages.map((page: any) => formatPagePath(rootPath, page.path)),
|
||||
...subPackages
|
||||
.map(({ pages: subPages = [], root = '' }: any) => {
|
||||
return subPages.map((page: any) => formatPagePath(join(rootPath, root), page.path));
|
||||
})
|
||||
.flat()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* camelCase to kebab-case: PageMeta -> page-meta
|
||||
*/
|
||||
export function toKebabCase(str: string) {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||
.replace(/[_\s]+/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* kebab-case to PascalCase: page-meta -> PageMeta
|
||||
*/
|
||||
export function toPascalCase(str: string) {
|
||||
return str.replace(/(^\w|-+\w)/g, match => match.toUpperCase().replace(/-/g, ''));
|
||||
}
|
||||
|
||||
interface TagNode {
|
||||
loc: {
|
||||
source: string;
|
||||
start: { offset: number };
|
||||
end: { offset: number };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a tag node in SFC template AST by tag name
|
||||
* Supports both kebab-case and PascalCase
|
||||
*/
|
||||
export function findNode(sfc: any, rawTagName: string): TagNode | undefined {
|
||||
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;
|
||||
|
||||
// Recursively traverse AST to find the target node
|
||||
const traverse = (nodes: any): TagNode | undefined => {
|
||||
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 undefined;
|
||||
};
|
||||
|
||||
return traverse(nodeAst.children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize platform-specific page paths (.mp-weixin.vue -> .vue)
|
||||
*/
|
||||
export function normalizePlatformPath(id: string) {
|
||||
const platform = process.env.UNI_PLATFORM;
|
||||
if (!platform) return id;
|
||||
|
||||
const regex = new RegExp(`\\.${platform}\\.vue$`);
|
||||
if (regex.test(id)) {
|
||||
return id.replace(`.${platform}.`, '.');
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce utility
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number) {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => fn(...args), delay);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user