storybook-addon-stencil
Version:
A Stencil compiler integration for Storybook.
119 lines (118 loc) • 5 kB
JavaScript
import ts from "typescript";
/**
* Convert Stencil data to Custom Elements v1 manifest.
* @see https://github.com/webcomponents/custom-elements-manifest
* @param classDeclaration The resulting Stencil transpiled data.
* @param fileName The file name of the module.
* @returns The manifest.
*/
export function generateCustomElementsManifest(classDeclaration, fileName) {
const decl = {
kind: "class",
description: classDeclaration.docs?.text,
name: classDeclaration.componentClassName,
tagName: classDeclaration.tagName,
customElement: true,
members: classDeclaration.properties.map((prop) => ({
kind: "field",
name: prop.name,
type: prop.type,
description: prop.docs?.text,
default: prop.defaultValue,
})),
events: classDeclaration.events.map((event) => ({
kind: "event",
name: event.name,
description: event.docs?.text,
})),
cssProperties: classDeclaration.cssProperties,
cssParts: classDeclaration.cssParts,
slots: classDeclaration.slots,
};
return {
schemaVersion: "1.0.0",
modules: [
{
kind: "javascript-module",
path: "",
declarations: [decl],
exports: [
{
kind: "js",
name: classDeclaration.componentClassName,
declaration: {
name: classDeclaration.componentClassName,
module: fileName,
},
},
{
kind: "custom-element-definition",
name: classDeclaration.tagName,
declaration: {
name: classDeclaration.componentClassName,
module: fileName,
},
},
],
},
],
};
}
export function generateCustomElementDeclaration(classDeclaration, sourceFile) {
const { componentClassName } = classDeclaration;
const lineParseRegex = /^([^\s]*)([\s-]*)(.*?)$/g;
ts.forEachChild(sourceFile, (node) => {
if (ts.isClassDeclaration(node) && node.name.text === componentClassName) {
const jsdoc = node.jsDoc;
if (jsdoc) {
/**
* Normalize a tag comment trimming it and replacing any new lines with a space.
* @param comment Tag comment.
* @returns normalized comment
*/
const normalizeComment = (comment) => comment.trim().replace(/\n/g, " ");
const tags = jsdoc.reduce((tags, comment) => [...tags, ...(comment.tags || [])], []);
tags.forEach((tag) => {
const tagName = tag.tagName.text;
switch (tagName) {
case "slot": {
const match = [...normalizeComment(tag.comment).matchAll(lineParseRegex)][0];
classDeclaration.slots = classDeclaration.slots || [];
classDeclaration.slots.push({
name: match?.[1] || "-",
description: match?.[3] || "",
});
break;
}
case "cssprop":
case "cssproperty": {
const match = [...normalizeComment(tag.comment).matchAll(lineParseRegex)][0];
const propName = match?.[1];
if (propName) {
classDeclaration.cssProperties = classDeclaration.cssProperties || [];
classDeclaration.cssProperties.push({
name: propName,
description: match?.[3] || "",
});
}
break;
}
case "csspart": {
const match = [...normalizeComment(tag.comment).matchAll(lineParseRegex)][0];
const partName = match?.[1];
if (partName) {
classDeclaration.cssParts = classDeclaration.cssParts || [];
classDeclaration.cssParts.push({
name: partName,
description: match?.[3] || "",
});
}
break;
}
}
});
}
}
});
return classDeclaration;
}