UNPKG

storybook-addon-stencil

Version:
95 lines (94 loc) 3.42 kB
import path from "node:path"; import ts from "typescript"; export class ProgramService { memoBuild; config; program; constructor() { this.config = this.loadConfig(); this.program = this.createProgram(); } /** * Load typescript configuration. * @returns {ParsedCommandLine} */ loadConfig() { const configFileName = ts.findConfigFile("./", ts.sys.fileExists, "tsconfig.json"); const { config } = ts.readConfigFile(configFileName, ts.sys.readFile); return ts.parseJsonConfigFileContent(config, ts.sys, "./"); } /** * Create a program instance from the loaded typescript configuration. * @returns {Program} */ createProgram() { const { fileNames, options: compilerOptions } = this.config; return ts.createProgram(fileNames, { ...compilerOptions, noEmit: true, }); } /** * Get the source file of a given file path. * @param {string} fileName The file path. * @returns {ts.SourceFile} */ getSourceFile(fileName) { return this.program.getSourceFile(fileName); } /** * Provide the cached map of all Stencil components or create a new one. * @returns Map<string, string> */ getComponents() { if (this.memoBuild) { return this.memoBuild; } return (this.memoBuild = this.createComponentsMap()); } /** * Create a map of all Stencil components using their tag name as key and the file path as value. * @returns {Map<string, string>} */ async createComponentsMap() { const componentMap = new Map(); const currentDirectory = this.program.getCurrentDirectory(); const visit = (node) => { if (ts.isClassDeclaration(node)) { let fileName = node.getSourceFile().fileName; if (!path.isAbsolute(fileName)) { fileName = path.join(currentDirectory, fileName); } // fix path with correct separator (it needs to be '/' for JS imports) fileName = fileName.split(path.sep).join("/"); /** * Add tagName to classDoc, extracted from `@Component({tag: 'foo-bar'})` decorator * Add custom-element-definition to exports */ const componentDecorator = ts .getDecorators(node) ?.find((decorator) => decorator?.expression?.expression?.getText() === "Component")?.expression; if (!componentDecorator) { return; } const tagProperty = componentDecorator.arguments?.[0]?.properties?.find((prop) => prop?.name?.getText() === "tag"); if (!tagProperty) { return; } const tagName = tagProperty?.initializer?.text; if (tagName) { componentMap.set(tagName, fileName); } } ts.forEachChild(node, visit); }; this.program.emit(); for (const sourceFile of this.program.getSourceFiles()) { if (!sourceFile.isDeclarationFile) { // Walk the tree to search for classes visit(sourceFile); } } return componentMap; } }