storybook-addon-stencil
Version:
A Stencil compiler integration for Storybook.
95 lines (94 loc) • 3.42 kB
JavaScript
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;
}
}