Skip to content

Instantly share code, notes, and snippets.

@sergeysova
Last active January 12, 2022 07:34
Show Gist options
  • Save sergeysova/33061985430617c417fb90915e4f29df to your computer and use it in GitHub Desktop.
Save sergeysova/33061985430617c417fb90915e4f29df to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import { VFile } from "vfile";
import { write } from "to-vfile";
import generate from "@babel/generator";
import t from "@babel/types";
import template from "@babel/template";
import change from "change-case";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const source = await fs.readFile(
path.resolve(__dirname, "./src/icons.html"),
"utf-8"
);
/**
* Chunks separated by ---
* Each chunks is a line with a name, on the next lines svg source
*/
const chunks = source.split("\n---\n");
const DIR_ICONS_TARGET = path.resolve(__dirname, "src/icons");
await fs.mkdir(DIR_ICONS_TARGET, { recursive: true });
const icons = chunks
.map((chunk) => {
const separatorPlace = chunk.indexOf("\n");
const name = chunk.substr(0, separatorPlace);
const source = chunk.substr(separatorPlace + 1);
if (!name || !source) return null;
return { name: name.trim(), source: source.trim() };
})
.filter(Boolean);
const COMPONENT_TPL = template.default(
`
import { h } from "forest";
export function %%name%%(fn?: () => void) {
%%content%%
}
`,
{ plugins: ["typescript"] }
);
const removeProperties = ['className', 'width', 'height']
// TODO: icons still broken due https://github.com/rehypejs/rehype/issues/87
const remapProperties = {
clipRule: 'clip-rule',
fillRule: 'fill-rule',
}
function tagToForest(tree) {
const children = [];
if (Array.isArray(tree.children)) {
tree.children.forEach((subTree) => {
const converted = tagToForest(subTree);
if (converted) {
if (Array.isArray(converted)) {
children.push(...converted);
} else {
children.push(converted);
}
}
});
}
if (tree.type === "element") {
const svgRoot = tree.tagName === "svg";
const properties = [];
if (svgRoot) {
properties.push({ key: "xmlns", value: "http://www.w3.org/2000/svg" });
}
for (let property in tree.properties) {
const key = property in remapProperties ? remapProperties[property] : property
const value = tree.properties[property];
if (removeProperties.includes(property)) {
// Just remove it
}
else if (Array.isArray(value)) {
properties.push({ key, value: value.join(" ") });
} else {
properties.push({ key, value: value });
}
}
const attrs = t.objectProperty(
t.stringLiteral("attr"),
t.objectExpression(
properties.map((p) =>
t.objectProperty(t.stringLiteral(p.key), t.stringLiteral(p.value))
)
)
);
if (svgRoot) {
children.unshift(
t.expressionStatement(
t.optionalCallExpression(t.identifier("fn"), [], true)
)
);
}
const fn = t.objectMethod(
"method",
t.identifier("fn"),
[],
t.blockStatement(children)
);
return t.expressionStatement(
t.callExpression(t.identifier("h"), [
t.stringLiteral(tree.tagName),
t.objectExpression([attrs, fn]),
])
);
}
if (tree.type === "root" && children.length > 0) {
return children[0];
}
return children;
}
function forestStringify() {
this.Compiler = compiler;
function compiler(tree, file) {
const Name = change.pascalCase(file.stem);
file.extname = ".ts";
const ast = COMPONENT_TPL({
name: Name,
content: tagToForest(tree),
});
return generate.default(
{
type: "Program",
body: ast,
},
{
filename: file.basename,
}
).code;
}
}
const processor = unified()
.use(rehypeParse, {
emitParseErrors: true,
duplicateAttribute: false,
fragment: true,
space: 'svg',
})
.use(forestStringify);
const tasks = icons.map(async (icon) => {
const fileName = change.paramCase(icon.name);
console.log(`Writing "${fileName}"`);
const file = new VFile({
path: `${DIR_ICONS_TARGET}/${fileName}.html`,
value: icon.source,
});
await processor.process(file).then((file) => write(file));
});
await tasks;
console.log(`Finished ${tasks.length} icons`);
const indexSource = icons
.map(
(icon) => `export { ${icon.name} } from './${change.paramCase(icon.name)}';`
)
.join("\n");
await fs.writeFile(`${DIR_ICONS_TARGET}/index.ts`, indexSource);
console.log("Wrote index.ts file");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment