// 解析 src 下的 auto-import.d.ts、components.d.ts,获取映射列表 import assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import * as ts from 'typescript'; import { pascalCase } from './utils'; import { kebabCase } from 'lodash-es'; export interface Source { /** 从哪个 module 引入,例如 ant-design-vue、./components/ColorPicker.vue */ module: string; /** * 导出的访问路径,例如 default、 ColorPicker * * 暂时我们只处理一层的场景吧 */ path: string; } export async function readUnpluginComponents(srcDir: string) { const filePath = join(srcDir, 'components.d.ts'); const content = await readFile(filePath, 'utf-8'); const { statements } = ts.createSourceFile( 'index.d.ts', content, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS, ); const parsed = new Map(); for (const s of statements) { if (!ts.isModuleDeclaration(s)) { continue; } const { body } = s; // declare module 'vue' { ... } assert(ts.isModuleBlock(body)); const { statements: [interfaceDeclaration, ...rest], } = body; // export interface GlobalComponents { ... } assert( ts.isInterfaceDeclaration(interfaceDeclaration) && interfaceDeclaration.name.text === 'GlobalComponents', ); assert(!rest.length, '不应该有多余语句了'); const { members } = interfaceDeclaration; for (const m of members) { // AAvatar: typeof import('ant-design-vue/es')['Avatar'] assert(ts.isPropertySignature(m)); // AAvatar const name = m.name.getText(); const { type } = m; // typeof import('ant-design-vue/es')['Avatar'] assert(ts.isIndexedAccessTypeNode(type)); const { objectType, indexType } = type; // typeof import('ant-design-vue/es') assert(ts.isImportTypeNode(objectType) && objectType.isTypeOf); const { argument } = objectType; assert(ts.isLiteralTypeNode(argument)); const module = argument.literal; assert(ts.isStringLiteral(module)); // ['Avatar'] assert(ts.isLiteralTypeNode(indexType)); const index = indexType.literal; assert(ts.isStringLiteral(index)); const source: Source = { module: fixModule(module.text), path: index.text, }; parsed.set(pascalCase(name), source); parsed.set(kebabCase(name), source); } } return parsed; } function fixModule(module: string) { if (module === 'ant-design-vue/es') { return 'ant-design-vue'; } return module; }