From b1cfbf2713583cbd6073baca4fec75efe72942b1 Mon Sep 17 00:00:00 2001 From: malangcat Date: Sat, 7 Dec 2024 18:03:17 +0900 Subject: [PATCH] feat: directly write mjs, dts instead of ts --- packages/rootage/cli/src/index.ts | 58 ++++++- .../core/src/languages/typescript.test.ts | 161 +++++++++++++++++- .../rootage/core/src/languages/typescript.ts | 86 ++++++++-- packages/rootage/core/src/types.ts | 1 + 4 files changed, 283 insertions(+), 23 deletions(-) diff --git a/packages/rootage/cli/src/index.ts b/packages/rootage/cli/src/index.ts index 01464b2a2..4ec72351b 100644 --- a/packages/rootage/cli/src/index.ts +++ b/packages/rootage/cli/src/index.ts @@ -2,10 +2,14 @@ import { buildRootage, - getComponentSpecTs, + getComponentSpecDts, + getComponentSpecIndexDts, + getComponentSpecIndexMjs, + getComponentSpecMjs, getJsonSchema, getTokenCss, - getTokenTs, + getTokenDts, + getTokenMjs, validate, type Model, } from "@seed-design/rootage-core"; @@ -63,10 +67,22 @@ async function prepare() { async function writeTokenTs() { const { ctx } = await prepare(); - const results = getTokenTs(ctx); + const mjsResults = getTokenMjs(ctx); + const dtsResults = getTokenDts(ctx); - for (const result of results) { - const writePath = path.join(process.cwd(), dir, `${result.path}.vars.ts`); + for (const result of mjsResults) { + const writePath = path.join(process.cwd(), dir, `${result.path}.mjs`); + + console.log("Writing", result.path, "to", writePath); + + if (!fs.existsSync(path.dirname(writePath))) { + fs.mkdirpSync(path.dirname(writePath)); + } + fs.writeFileSync(writePath, result.code); + } + + for (const result of dtsResults) { + const writePath = path.join(process.cwd(), dir, `${result.path}.d.ts`); console.log("Writing", result.path, "to", writePath); @@ -78,13 +94,37 @@ async function writeComponentSpec() { const { ctx } = await prepare(); for (const spec of ctx.componentSpecs) { - const code = getComponentSpecTs(spec.data); - const writePath = path.join(process.cwd(), dir, `${spec.id}.vars.ts`); + const mjsCode = getComponentSpecMjs(ctx, spec.id); + const mjsWritePath = path.join(process.cwd(), dir, `${spec.id}.mjs`); - console.log("Writing", spec.name, "to", writePath); + console.log("Writing", spec.name, "to", mjsWritePath); - fs.writeFileSync(writePath, code); + if (!fs.existsSync(path.dirname(mjsWritePath))) { + fs.mkdirpSync(path.dirname(mjsWritePath)); + } + fs.writeFileSync(mjsWritePath, mjsCode); + + const dtsCode = getComponentSpecDts(ctx, spec.id); + const dtsWritePath = path.join(process.cwd(), dir, `${spec.id}.d.ts`); + + console.log("Writing", spec.name, "to", dtsWritePath); + + fs.writeFileSync(dtsWritePath, dtsCode); } + + const mjsIndexCode = getComponentSpecIndexMjs(ctx); + const mjsIndexWritePath = path.join(process.cwd(), dir, "index.mjs"); + + console.log("Writing index to", mjsIndexWritePath); + + fs.writeFileSync(mjsIndexWritePath, mjsIndexCode); + + const dtsIndexCode = getComponentSpecIndexDts(ctx); + const dtsIndexWritePath = path.join(process.cwd(), dir, "index.d.ts"); + + console.log("Writing index to", dtsIndexWritePath); + + fs.writeFileSync(dtsIndexWritePath, dtsIndexCode); } async function writeTokenCss() { diff --git a/packages/rootage/core/src/languages/typescript.test.ts b/packages/rootage/core/src/languages/typescript.test.ts index 90ace44e7..97ff2ffaf 100644 --- a/packages/rootage/core/src/languages/typescript.test.ts +++ b/packages/rootage/core/src/languages/typescript.test.ts @@ -1,9 +1,10 @@ import { expect, test } from "vitest"; -import type { Model } from "../types"; -import { getTokenTs } from "./typescript"; +import YAML from "yaml"; import { buildRootage } from "../build"; +import type { Model } from "../types"; +import { getComponentSpecDts, getComponentSpecMjs, getTokenDts, getTokenMjs } from "./typescript"; -test("getTokenTs should generate typescript codes", () => { +test("getTokenMjs should generate esm definitions", () => { const models: Model[] = [ { kind: "Tokens", @@ -54,7 +55,7 @@ test("getTokenTs should generate typescript codes", () => { }, ]; - const result = getTokenTs(buildRootage(models)); + const result = getTokenMjs(buildRootage(models)); expect(result).toMatchInlineSnapshot(` [ @@ -74,3 +75,155 @@ test("getTokenTs should generate typescript codes", () => { ] `); }); + +test("getTokenDts should generate typescript definitions", () => { + const models: Model[] = [ + { + kind: "Tokens", + metadata: { + id: "2", + name: "color", + }, + data: { + collection: "color", + tokens: { + "$color.palette.gray-00": { + values: { + light: "#ffffff", + dark: "#000000", + }, + }, + "$color.palette.gray-100": { + values: { + light: "#f8f9fa", + dark: "#212529", + }, + }, + "$color.bg.layer-1": { + values: { + light: "$color.palette.gray-00", + dark: "$color.palette.gray-00", + }, + }, + }, + }, + }, + { + kind: "Tokens", + metadata: { + id: "3", + name: "unit", + }, + data: { + collection: "global", + tokens: { + "$unit.x1_5": { + values: { + default: "6px", + }, + }, + }, + }, + }, + ]; + + const result = getTokenDts(buildRootage(models)); + + expect(result).toMatchInlineSnapshot(` + [ + { + "code": "export declare const gray00 = \\"var(--seed-v3-color-palette-gray-00)\\"; + export declare const gray100 = \\"var(--seed-v3-color-palette-gray-100)\\";", + "path": "color/palette", + }, + { + "code": "export declare const layer1 = \\"var(--seed-v3-color-bg-layer-1)\\";", + "path": "color/bg", + }, + { + "code": "export declare const x1_5 = \\"var(--seed-v3-unit-x1_5)\\";", + "path": "unit", + }, + ] + `); +}); + +test("getComponentSpecMjs should generate esm definitions", () => { + const yaml = ` +kind: ComponentSpec +metadata: + id: test + name: test +data: + base: + enabled: + root: + color: "#ffffff" + variant=primary: + enabled: + root: + color: "#000000" +`; + const models = [YAML.parse(yaml)]; + + const result = getComponentSpecMjs(buildRootage(models), "test"); + + expect(result).toMatchInlineSnapshot(` + "export const vars = { + \\"base\\": { + \\"enabled\\": { + \\"root\\": { + \\"color\\": \\"#ffffff\\" + } + } + }, + \\"variantPrimary\\": { + \\"enabled\\": { + \\"root\\": { + \\"color\\": \\"#000000\\" + } + } + } + }" + `); +}); + +test("getComponentSpecDts should generate typescript definitions", () => { + const yaml = ` +kind: ComponentSpec +metadata: + id: test + name: test +data: + base: + enabled: + root: + color: "#ffffff" + variant=primary: + enabled: + root: + color: "#000000" +`; + const models = [YAML.parse(yaml)]; + + const result = getComponentSpecDts(buildRootage(models), "test"); + + expect(result).toMatchInlineSnapshot(` + "export declare const vars: { + \\"base\\": { + \\"enabled\\": { + \\"root\\": { + \\"color\\": \\"#ffffff\\" + } + } + }, + \\"variantPrimary\\": { + \\"enabled\\": { + \\"root\\": { + \\"color\\": \\"#000000\\" + } + } + } + }" + `); +}); diff --git a/packages/rootage/core/src/languages/typescript.ts b/packages/rootage/core/src/languages/typescript.ts index 508fd1e6d..8f60328cc 100644 --- a/packages/rootage/core/src/languages/typescript.ts +++ b/packages/rootage/core/src/languages/typescript.ts @@ -1,5 +1,5 @@ import { camelCase } from "change-case"; -import type { ComponentSpecExpression, RootageCtx, TokenExpression } from "../types"; +import type { RootageCtx, TokenExpression } from "../types"; import { stringifyCssValue, stringifyTokenReference } from "./css"; /** @@ -31,7 +31,12 @@ function stringifyStateKey(state: string[]) { return camelCase(state.join("-")); } -export function getComponentSpecTs(expressions: ComponentSpecExpression) { +function getComponentSpec(ctx: RootageCtx, componentId: string) { + const expressions = ctx.componentSpecs.find((spec) => spec.id === componentId)?.data; + if (!expressions) { + throw new Error(`Component spec not found: ${componentId}`); + } + const result: Record>>> = {}; for (const expression of expressions) { @@ -62,10 +67,46 @@ export function getComponentSpecTs(expressions: ComponentSpecExpression) { result[variantKey] = variant; } + return result; +} + +export function getComponentSpecMjs(ctx: RootageCtx, componentId: string) { + const result = getComponentSpec(ctx, componentId); return `export const vars = ${JSON.stringify(result, null, 2)}`; } -export function getTokenTs(ctx: RootageCtx) { +export function getComponentSpecDts(ctx: RootageCtx, componentId: string) { + const result = getComponentSpec(ctx, componentId); + return `export declare const vars: ${JSON.stringify(result, null, 2)}`; +} + +export function getComponentSpecIndexMjs(ctx: RootageCtx) { + const result = ctx.componentSpecs.map((spec) => { + return `export { vars as ${camelCase(spec.id, { mergeAmbiguousCharacters: true })} } from "./${spec.id}.mjs";`; + }); + + return result.join("\n"); +} + +export function getComponentSpecIndexDts(ctx: RootageCtx) { + const result = ctx.componentSpecs.map((spec) => { + return `export { vars as ${camelCase(spec.id, { mergeAmbiguousCharacters: true })} } from "./${spec.id}";`; + }); + + return result.join("\n"); +} + +interface TokenDefinition { + key: string; + value: string; +} + +interface TokenGroup { + path: string; + code: TokenDefinition[]; +} + +function getTokenGroups(ctx: RootageCtx): TokenGroup[] { const { tokens } = ctx; const tokenExpressions = tokens.map((decl) => decl.token); @@ -81,18 +122,43 @@ export function getTokenTs(ctx: RootageCtx) { } return Object.entries(groups).map(([group, expressions]) => { - const definitions = expressions - .map((expression) => { - const key = camelCasePreserveUnderscoreBetweenNumbers(expression.key); - const value = stringifyTokenReference(expression); + const definitions = expressions.map((expression) => { + const key = camelCasePreserveUnderscoreBetweenNumbers(expression.key); + const value = stringifyTokenReference(expression); + return { key, value }; + }); - return `export const ${key} = "${value}";`; + return { + path: group, + code: definitions, + }; + }); +} + +function generateTokenCode( + groups: TokenGroup[], + isDeclaration: boolean, +): { path: string; code: string }[] { + return groups.map(({ path, code }) => { + const definitions = code + .map(({ key, value }) => { + const exportKeyword = isDeclaration ? "export declare const" : "export const"; + return `${exportKeyword} ${key} = "${value}";`; }) .join("\n"); - return { - path: group, + path, code: definitions, }; }); } + +export function getTokenMjs(ctx: RootageCtx): { path: string; code: string }[] { + const groups = getTokenGroups(ctx); + return generateTokenCode(groups, false); +} + +export function getTokenDts(ctx: RootageCtx): { path: string; code: string }[] { + const groups = getTokenGroups(ctx); + return generateTokenCode(groups, true); +} diff --git a/packages/rootage/core/src/types.ts b/packages/rootage/core/src/types.ts index 289a8cabd..a98893045 100644 --- a/packages/rootage/core/src/types.ts +++ b/packages/rootage/core/src/types.ts @@ -106,6 +106,7 @@ export interface DependencyGraph { [tokenRef: TokenRef]: DependencyNode; } +// TODO: use record instead of array export interface RootageCtx { componentSpecs: ComponentSpecDeclaration[]; tokens: TokenDeclaration[];