From ce34c655bb7a88f9c8ac3de03570bc61372996c0 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Thu, 9 Jan 2025 01:31:04 +0700 Subject: [PATCH] refactor: generate radix stories from templates (#4733) Reimplemented generate-stories command in sdk-cli to not rely on sdk components and load metas and templates dynamically according to files convention. --- packages/react-sdk/src/css/css.test.tsx | 3 +- packages/react-sdk/src/css/css.ts | 11 +- packages/sdk-cli/package.json | 2 +- packages/sdk-cli/src/generate-stories.ts | 94 +++--- .../src/__generated__/accordion.stories.tsx | 123 +++++--- .../src/__generated__/checkbox.stories.tsx | 52 +-- .../src/__generated__/collapsible.stories.tsx | 53 ++-- .../src/__generated__/dialog.stories.tsx | 130 ++++---- .../src/__generated__/label.stories.tsx | 10 - .../__generated__/navigation-menu.stories.tsx | 295 +++++++++--------- .../src/__generated__/popover.stories.tsx | 40 +-- .../src/__generated__/radio-group.stories.tsx | 92 +++--- .../src/__generated__/select.stories.tsx | 134 ++++---- .../src/__generated__/sheet.stories.tsx | 134 ++++---- .../src/__generated__/switch.stories.tsx | 46 +-- .../src/__generated__/tabs.stories.tsx | 68 ++-- .../src/__generated__/tooltip.stories.tsx | 44 +-- .../src/accordion.template.tsx | 30 +- .../src/checkbox.tsx | 2 +- pnpm-lock.yaml | 4 +- 20 files changed, 618 insertions(+), 749 deletions(-) diff --git a/packages/react-sdk/src/css/css.test.tsx b/packages/react-sdk/src/css/css.test.tsx index 5202f815d..7a25bff19 100644 --- a/packages/react-sdk/src/css/css.test.tsx +++ b/packages/react-sdk/src/css/css.test.tsx @@ -21,9 +21,8 @@ const generateAllCss = (config: Omit) => { test("generate css for one instance with two tokens", () => { const { cssText, atomicCssText, atomicClasses } = generateAllCss({ + ...renderJsx(<$.Box ws:id="box">), assets: new Map(), - instances: new Map(), - props: new Map(), breakpoints: toMap([{ id: "base", label: "" }]), styleSourceSelections: new Map([ ["box", { instanceId: "box", values: ["token", "local"] }], diff --git a/packages/react-sdk/src/css/css.ts b/packages/react-sdk/src/css/css.ts index c57bbc7a6..a0b3cfc81 100644 --- a/packages/react-sdk/src/css/css.ts +++ b/packages/react-sdk/src/css/css.ts @@ -160,7 +160,10 @@ export const generateCss = ({ let descendantSuffix = ""; // render selector component as descendant selector const instance = instances.get(instanceId); - if (instance?.component === descendantComponent) { + if (instance === undefined) { + continue; + } + if (instance.component === descendantComponent) { const parentId = parentIdByInstanceId.get(instanceId); const descendantSelector = descendantSelectorByInstanceId.get(instanceId); if (parentId && descendantSelector) { @@ -168,9 +171,9 @@ export const generateCss = ({ instanceId = parentId; } } - const meta = instance ? componentMetas.get(instance.component) : undefined; - const baseName = - instance?.label ?? meta?.label ?? instance?.component ?? instanceId; + const meta = componentMetas.get(instance.component); + const [_namespace, shortName] = parseComponentName(instance.component); + const baseName = instance.label ?? meta?.label ?? shortName; const className = `w-${scope.getName(instanceId, baseName)}`; if (atomic === false) { let classList = classes.get(instanceId); diff --git a/packages/sdk-cli/package.json b/packages/sdk-cli/package.json index 15e9ab5db..c71f76ab6 100644 --- a/packages/sdk-cli/package.json +++ b/packages/sdk-cli/package.json @@ -19,7 +19,7 @@ "dependencies": { "@webstudio-is/react-sdk": "workspace:^", "@webstudio-is/sdk": "workspace:^", - "@webstudio-is/sdk-components-react": "workspace:^", + "@webstudio-is/template": "workspace:^", "change-case": "^5.4.4" }, "devDependencies": { diff --git a/packages/sdk-cli/src/generate-stories.ts b/packages/sdk-cli/src/generate-stories.ts index dc2243e96..5c4da4140 100644 --- a/packages/sdk-cli/src/generate-stories.ts +++ b/packages/sdk-cli/src/generate-stories.ts @@ -1,24 +1,25 @@ +import { cwd } from "node:process"; import { dirname, join } from "node:path"; -import { mkdir, writeFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { kebabCase } from "change-case"; import { type Instances, type Instance, type Scope, - type WsComponentMeta, createScope, parseComponentName, getStyleDeclKey, + WsComponentMeta, } from "@webstudio-is/sdk"; import { generateCss, - generateDataFromEmbedTemplate, generateWebstudioComponent, getIndexesWithinAncestors, + namespaceMeta, } from "@webstudio-is/react-sdk"; -import * as baseMetasExports from "@webstudio-is/sdk-components-react/metas"; +import { renderTemplate, type TemplateMeta } from "@webstudio-is/template"; -const baseMetas = new Map(Object.entries(baseMetasExports)); +const BASE_NAMESPACE = "@webstudio-is/sdk-components-react"; const generateComponentImports = ({ scope, @@ -33,19 +34,13 @@ const generateComponentImports = ({ string, Set<[shortName: string, componentName: string]> >(); - const BASE_NAMESPACE = "@webstudio-is/sdk-components-react"; for (const component of components) { const parsed = parseComponentName(component); - let [namespace] = parsed; + let [namespace = BASE_NAMESPACE] = parsed; const [_namespace, shortName] = parsed; - if (namespace === undefined) { - // use base as fallback namespace and consider remix overrides - if (metas.has(shortName)) { - namespace = "../components"; - } else { - namespace = BASE_NAMESPACE; - } + if (metas.has(shortName)) { + namespace = "../components"; } if (namespaces.has(namespace) === false) { namespaces.set( @@ -61,7 +56,7 @@ const generateComponentImports = ({ const specifiers = Array.from(componentsSet) .map( ([shortName, component]) => - `${component} as ${scope.getName(component, shortName)}` + `${shortName} as ${scope.getName(component, shortName)}` ) .join(", "); componentImports += `import { ${specifiers} } from "${namespace}";\n`; @@ -96,25 +91,22 @@ export { Story as ${name} } `; export const generateStories = async () => { - const metasModule = join(process.cwd(), "src/metas.ts"); + const packageFile = await readFile(join(cwd(), "package.json"), "utf8"); + const packageJson = JSON.parse(packageFile); + const templatesModule = join(cwd(), "src/templates.ts"); + const templates = new Map( + Object.entries(await import(templatesModule)) + ); + const metasModule = join(cwd(), "src/metas.ts"); const metas = new Map( Object.entries(await import(metasModule)) ); - const storiesDir = join(dirname(metasModule), "__generated__"); + const storiesDir = join(dirname(templatesModule), "__generated__"); await mkdir(storiesDir, { recursive: true }); - for (const [name, meta] of metas) { - if (meta.template === undefined) { - continue; - } + for (const [name, meta] of templates) { const rootInstanceId = "root"; - let id = 0; - const generateStableId = () => (++id).toString(); - const data = generateDataFromEmbedTemplate( - meta.template, - metas, - generateStableId - ); + const data = renderTemplate(meta.template); const instances: Instances = new Map([ [ rootInstanceId, @@ -125,30 +117,48 @@ export const generateStories = async () => { children: data.children, }, ], - ...data.instances.map( - (instance) => - [instance.id, instance] satisfies [Instance["id"], Instance] - ), + ...data.instances.map((instance) => [instance.id, instance] as const), ]); const props = new Map(data.props.map((prop) => [prop.id, prop])); const breakpoints = new Map( data.breakpoints.map((breakpoint) => [breakpoint.id, breakpoint]) ); const components = new Set(); - const usedMetas = new Map(); - const bodyMeta = baseMetas.get("Body"); - // add body styles for stories - if (bodyMeta) { - usedMetas.set("Body", bodyMeta); - } + const namespaces: string[] = []; for (const instance of instances.values()) { + const [namespace = BASE_NAMESPACE] = parseComponentName( + instance.component + ); components.add(instance.component); - const meta = - metas.get(instance.component) ?? baseMetas.get(instance.component); - if (meta) { - usedMetas.set(instance.component, meta); + namespaces.push(namespace); + } + const usedMetas = new Map(); + for (const namespace of namespaces) { + let namespaceMetas; + if (namespace === packageJson.name) { + namespaceMetas = metas; + } else { + const metasUrl = import.meta.resolve( + `${namespace}/metas`, + templatesModule + ); + namespaceMetas = new Map(Object.entries(await import(metasUrl))); + } + for (let [name, meta] of namespaceMetas) { + if (namespace !== BASE_NAMESPACE) { + name = `${namespace}:${name}`; + meta = namespaceMeta( + meta as WsComponentMeta, + namespace, + new Set(metas.keys()) + ); + } + if (components.has(name)) { + usedMetas.set(name, meta as WsComponentMeta); + } } } + const { cssText, classes } = generateCss({ instances, props, diff --git a/packages/sdk-components-react-radix/src/__generated__/accordion.stories.tsx b/packages/sdk-components-react-radix/src/__generated__/accordion.stories.tsx index e02b94c70..5142ecd76 100644 --- a/packages/sdk-components-react-radix/src/__generated__/accordion.stories.tsx +++ b/packages/sdk-components-react-radix/src/__generated__/accordion.stories.tsx @@ -26,7 +26,7 @@ const Component = () => { ' + '' } className={"w-html-embed"} /> @@ -44,7 +44,7 @@ const Component = () => { ' + '' } className={"w-html-embed"} /> @@ -64,7 +64,7 @@ const Component = () => { ' + '' } className={"w-html-embed"} /> @@ -93,16 +93,6 @@ const Story = {