mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-15 10:21:05 +00:00
refactor(types): integrate generate docs in rollup.config.js
This commit is contained in:
parent
25e437b746
commit
010fe986e9
7 changed files with 386 additions and 563 deletions
118
scripts/rollup/parse-component.js
Normal file
118
scripts/rollup/parse-component.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
import { parse, walk } from "svelte/compiler";
|
||||
import commentParser from "comment-parser";
|
||||
|
||||
/**
|
||||
* Parse Svelte component for metadata using the Svelte compiler
|
||||
* @param {string} source - raw Svelte component code
|
||||
* @param {{ component: string; onTypeDef: (tag: { type: "typedef"; tag: string; name: string; }) => void;}} hooks
|
||||
*/
|
||||
export function parseComponent(source, hooks) {
|
||||
const exported_props = new Map();
|
||||
const slots = new Map();
|
||||
const forwarded_events = new Map();
|
||||
const dispatched_events = new Map();
|
||||
|
||||
let hasDispatchedEvents = false;
|
||||
let dispatcher_name = null;
|
||||
let callee = [];
|
||||
|
||||
walk(parse(source), {
|
||||
enter(node, parent, prop) {
|
||||
switch (node.type) {
|
||||
case "CallExpression":
|
||||
if (hasDispatchedEvents) {
|
||||
if (node.callee.name === "createEventDispatcher") {
|
||||
dispatcher_name = parent.id.name;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Identifier":
|
||||
if (node.name === "createEventDispatcher") {
|
||||
hasDispatchedEvents = true;
|
||||
}
|
||||
|
||||
if (prop === "callee") {
|
||||
callee.push({ name: node.name, parent });
|
||||
}
|
||||
break;
|
||||
case "ExportNamedDeclaration":
|
||||
const { id, init } = node.declaration.declarations[0];
|
||||
|
||||
let value = undefined;
|
||||
let type = undefined;
|
||||
let description = null;
|
||||
|
||||
if (init != null) {
|
||||
value = init.raw;
|
||||
}
|
||||
|
||||
const general_comments = commentParser(source);
|
||||
|
||||
general_comments.forEach((comment) => {
|
||||
comment.tags.forEach((tag) => {
|
||||
if (tag.tag === "typedef") hooks.onTypeDef(tag);
|
||||
});
|
||||
});
|
||||
|
||||
if (node.leadingComments) {
|
||||
const comment = commentParser(
|
||||
"/*" + node.leadingComments[0].value + "*/"
|
||||
);
|
||||
|
||||
description = comment[0].description;
|
||||
type = comment[0].tags[0].type;
|
||||
} else {
|
||||
throw Error(
|
||||
`[${hooks.component}] property \`${id.name}\` should be annotated.`
|
||||
);
|
||||
}
|
||||
|
||||
exported_props.set(id.name, { value, type, description });
|
||||
break;
|
||||
case "Slot":
|
||||
let slot_name = null;
|
||||
|
||||
const slot_attributes = [];
|
||||
|
||||
node.attributes.forEach((attribute) => {
|
||||
if (attribute.name === "name") {
|
||||
slot_name = attribute.value[0].raw;
|
||||
} else {
|
||||
slot_attributes.push(attribute);
|
||||
}
|
||||
});
|
||||
|
||||
slots.set(slot_name == null ? "default" : slot_name, {
|
||||
attributes: node.attributes,
|
||||
children: node.children,
|
||||
default: slot_name == null,
|
||||
});
|
||||
break;
|
||||
case "EventHandler":
|
||||
if (node.expression == null) {
|
||||
forwarded_events.set(node.name, node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (hasDispatchedEvents) {
|
||||
callee.forEach((node) => {
|
||||
if (node.name === dispatcher_name) {
|
||||
const [name, detail] = node.parent.arguments;
|
||||
|
||||
dispatched_events.set(name.raw, {
|
||||
detail: detail ? source.slice(detail.start, detail.end) : undefined,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
exported_props,
|
||||
slots,
|
||||
forwarded_events,
|
||||
dispatched_events,
|
||||
};
|
||||
}
|
133
scripts/rollup/plugin-generate-docs.js
Normal file
133
scripts/rollup/plugin-generate-docs.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import prettier from "prettier";
|
||||
import pkg from "../../package.json";
|
||||
import { parseComponent } from "./parse-component";
|
||||
|
||||
function pluginGenerateDocs() {
|
||||
let definitions = `
|
||||
// Type definitions for ${pkg.name} ${pkg.version}
|
||||
// Project: ${pkg.homepage}
|
||||
|
||||
class SvelteComponent {
|
||||
$$prop_def: {};
|
||||
|
||||
$$slot_def: {};
|
||||
|
||||
// stub all 'on:{event}' directives
|
||||
$on(eventname: string, handler: (e: Event) => any): () => void;
|
||||
}\n\n`;
|
||||
|
||||
const shared_types = new Set();
|
||||
const groups = new Map();
|
||||
const components = new Map();
|
||||
|
||||
return {
|
||||
name: "generate-docs",
|
||||
async generateBundle(options, bundle) {
|
||||
for (const filename in bundle) {
|
||||
const chunkOrAsset = bundle[filename];
|
||||
|
||||
if (chunkOrAsset.type !== "asset" && chunkOrAsset.isEntry) {
|
||||
chunkOrAsset.exports.forEach((exportee) => {
|
||||
components.set(exportee, {});
|
||||
});
|
||||
|
||||
Object.keys(chunkOrAsset.modules)
|
||||
.sort()
|
||||
.forEach(async (filename) => {
|
||||
const { dir, ext, name } = path.parse(filename);
|
||||
const moduleName = name.replace(/\./g, "");
|
||||
|
||||
if (ext === ".svelte" && components.has(moduleName)) {
|
||||
const group = dir.split("src/").pop().split("/")[0];
|
||||
|
||||
if (groups.has(group)) {
|
||||
groups.set(group, [...groups.get(group), moduleName]);
|
||||
} else {
|
||||
groups.set(group, [moduleName]);
|
||||
}
|
||||
|
||||
const source = fs.readFileSync(filename, "utf-8");
|
||||
const component = parseComponent(source, {
|
||||
component: moduleName,
|
||||
onTypeDef: (tag) => {
|
||||
if (shared_types.has(tag.name)) return;
|
||||
|
||||
shared_types.add(tag.name);
|
||||
|
||||
const ts_type = tag.type.startsWith("{")
|
||||
? `interface ${tag.name} ${tag.type}`
|
||||
: `type ${tag.name} = ${tag.type};`;
|
||||
|
||||
definitions += ts_type + "\n\n";
|
||||
},
|
||||
});
|
||||
|
||||
let $$prop_def = "";
|
||||
|
||||
component.exported_props.forEach((prop, name) => {
|
||||
$$prop_def += "/**\n";
|
||||
|
||||
prop.description.split("\n").forEach((line) => {
|
||||
$$prop_def += "* " + line + "\n";
|
||||
});
|
||||
|
||||
if (prop.value !== undefined) {
|
||||
$$prop_def += "* @default " + prop.value + "\n";
|
||||
}
|
||||
|
||||
$$prop_def += "*/\n";
|
||||
|
||||
let value = "undefined";
|
||||
|
||||
if (prop.type) {
|
||||
value = prop.type;
|
||||
}
|
||||
|
||||
$$prop_def += name + "?: " + value + ";" + "\n\n";
|
||||
});
|
||||
|
||||
let $$slot_def = "";
|
||||
|
||||
component.slots.forEach((slot, slot_name) => {
|
||||
let value = "";
|
||||
|
||||
slot.attributes.forEach((attribute) => {
|
||||
if (attribute.name !== "name") {
|
||||
value += attribute.name + ": any;";
|
||||
}
|
||||
});
|
||||
|
||||
if (slot.default) {
|
||||
$$slot_def += "default: {" + value + "};" + "\n";
|
||||
} else {
|
||||
$$slot_def +=
|
||||
JSON.stringify(slot_name) + ": {" + value + "};" + "\n";
|
||||
}
|
||||
});
|
||||
|
||||
definitions += `
|
||||
export class ${moduleName} extends SvelteComponent {
|
||||
${!!$$prop_def ? "$$prop_def: {" + $$prop_def + "}\n" : ""}
|
||||
|
||||
${!!$$slot_def ? "$$slot_def: {" + $$slot_def + "}\n" : ""}
|
||||
}\n\n`;
|
||||
|
||||
components.set(moduleName, component);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
writeBundle() {
|
||||
const formatted_definitions = prettier.format(definitions, {
|
||||
parser: "typescript",
|
||||
});
|
||||
|
||||
fs.writeFileSync(pkg.types, formatted_definitions);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default pluginGenerateDocs;
|
Loading…
Add table
Add a link
Reference in a new issue