UNPKG

module-federation-exposes-webpack-plugin

Version:
174 lines (141 loc) 5.19 kB
import { Project, SyntaxKind, ts, Node } from "ts-morph"; import path from "path"; import fs from "fs-extra"; type Content = { name: string; content: string; }; export type Options = { /** 导出组件的入口文件 */ exportFile: string; /** 生成的文件目录 */ generateDir: string; /** 生成的文件类型, 默认为ts */ filetype?: "ts" | "js"; /** 转换引入的包名称 */ transform?: (libraryName: string) => string; }; /** 根据语法类型过滤节点,返回数组 */ function filterNodeBySyntaxKind(nodes: Node<ts.Node>[], kind: ts.SyntaxKind) { return nodes .filter((node) => node.getKind() === kind) .map((node) => node.getText()); } /** 根据语法类型过滤节点,返回对象 */ function findNodeBySyntaxKind(nodes: Node<ts.Node>[], kind: ts.SyntaxKind) { return filterNodeBySyntaxKind(nodes, kind)?.[0]; } /** 根据导出index文件获取导出内容 */ export const getExportContent = ( path: string, transform?: Options["transform"] ) => { const project = new Project(); // 添加源代码 const source = project.addSourceFileAtPath(path); const content: Content[] = []; source.forEachChild((item) => { const text = item.getText(); // 不处理非导出类型语法 if (item.getKind() !== SyntaxKind.ExportDeclaration) { return; } // 不处理导出类型 if (text.startsWith("export type")) { return; } const nodes = item.forEachDescendantAsArray(); // 参考 https://github.com/estree/estree/blob/master/es2015.md#exports // export { get } from 'module' => ['get'] // export { get as myGet } from 'module' => ['get as myGet'] // export { get, pick as myPick } from 'module' => ['get', 'pick as myPick'] // export { default as Module, get } from 'module' => ['default as Module', 'get'] // export * as module from 'module' => undefined const exportSpecifier = filterNodeBySyntaxKind( nodes, SyntaxKind.ExportSpecifier ); // export { get as myGet } from 'module' => ['get', 'myGet'] // export { get, pick } from 'module' => ['get', 'pick'] // export { get, pick as myPick } from 'module' => ['get', 'pick', 'myPick'] // export { default as Module, get } from 'module' => ['default', 'Module', 'get'] // export * as module from 'module' => [] const identifiers = filterNodeBySyntaxKind(nodes, SyntaxKind.Identifier); // 如果是*号导出时有值,此时identifiers返回为空数组 // export * as module from 'module' => '* as module' // export { get } from 'module' => undefined const namespaceExport = findNodeBySyntaxKind( nodes, SyntaxKind.NamespaceExport ); // export { get } from 'module' => 'module' const stringLiteral = findNodeBySyntaxKind(nodes, SyntaxKind.StringLiteral); const libraryName = transform?.(stringLiteral) || stringLiteral; // 跳过此次循环的下标 let breakIndex = -1; for (let i = 0; i < identifiers.length; i++) { if (breakIndex === i) { continue; } const identifier = identifiers[i]; const reNamedIdentifier = identifier === "default" ? "default" : `${identifier} as default`; // 是否有重命名导出,如get as myGet, default as Utils const isRenamedExport = exportSpecifier?.[i]?.includes(" as "); // 如果有默认导出, 则跳过下一次循环 if (isRenamedExport) { breakIndex = i + 1; content.push({ name: identifiers[i + 1], content: `export { ${reNamedIdentifier} } from ${libraryName};`, }); continue; } // 如果包含*号导出 if (namespaceExport?.includes("* as")) { content.push({ name: identifier, content: `export * as default from ${libraryName};`, }); continue; } content.push({ name: identifier, content: `export { ${reNamedIdentifier} } from ${libraryName};`, }); } }); return content; }; /** 生成导出文件 */ async function generateExposesFile( dir: string, content: Content[], filetype: string ) { // 生成目录时先删除旧目录数据 await fs.remove(dir); // 删除后重新创建目录 await fs.mkdir(dir); for (const item of content) { const filepath = path.resolve(dir, `${item.name}.${filetype}`); await fs.writeFile(filepath, item.content); } } /** 生成模块联邦exposes JSON配置 */ async function generateExposesJson(dir: string, content: Content[]) { const filename = path.resolve(dir, "index.json"); // 目录名称 const dirName = path.basename(dir); const map: Record<string, string> = {}; content.forEach((item) => { map[`./${item.name}`] = `./${dirName}/${item.name}`; }); await fs.writeFile(filename, JSON.stringify(map, null, 2)); } export async function generateExposes(options: Options) { const { exportFile, generateDir, filetype = "ts", transform } = options; const content = getExportContent(exportFile, transform); await generateExposesFile(generateDir, content, filetype); await generateExposesJson(generateDir, content); }