|
1 | 1 | # 2nd-gen scaffolding |
2 | 2 |
|
3 | | -A [plop](https://plopjs.com/) generator that produces the deterministic file |
4 | | -skeleton for a 2nd-gen component: the `core` base layer, the `swc` concrete |
5 | | -layer, and the stories, docs, and test files that go with them. |
| 3 | +A [plop](https://plopjs.com/) scaffolder that produces the deterministic file |
| 4 | +skeletons for 2nd-gen components: the component itself, and the **test** and |
| 5 | +**VRT** files that go with it. |
6 | 6 |
|
7 | | -The generator exists to take the mechanical, every-time-identical part of |
8 | | -Phase 2 (`migration-setup`) off the critical path. It writes the boilerplate so |
9 | | -a human or an agent only has to apply the decisions that actually differ between |
10 | | -components: the base-vs-concrete property split, the real Spectrum 2 tokens, the |
11 | | -variant and state API, and the prose in the docs page. |
| 7 | +The generators exist to take the mechanical, every-time-identical part of |
| 8 | +authoring off the critical path. They write the boilerplate so a human or an |
| 9 | +agent only has to apply the decisions that actually differ between components: |
| 10 | +the base-vs-concrete property split, the real Spectrum 2 tokens, the |
| 11 | +variant/state API, the assertions, and the docs prose. |
| 12 | + |
| 13 | +## Generators |
| 14 | + |
| 15 | +| Generator | Scaffolds | Target | |
| 16 | +| ----------- | ------------------------------------------------------- | --------------------------------- | |
| 17 | +| `component` | Core base + SWC concrete + stories, docs, tests | new component (`core` + `swc`) | |
| 18 | +| `test` | Unit (`*.test.ts`) + a11y (`*.a11y.spec.ts`) test files | **existing** component (retrofit) | |
| 19 | +| `vrt` | A Chromatic VRT story (`test/vrt/<name>.vrt.ts`) | **existing** component (retrofit) | |
| 20 | + |
| 21 | +`component` always emits a baseline `test` + `a11y` file. Use the standalone |
| 22 | +`test` and `vrt` generators to add coverage to a component that already exists |
| 23 | +(for example, one migrated before these generators landed) — `vrt` in particular |
| 24 | +fills a gap, since the `component` skeleton ships no `.vrt.ts`. |
12 | 25 |
|
13 | 26 | ## Usage |
14 | 27 |
|
15 | 28 | ### Interactive (humans) |
16 | 29 |
|
17 | 30 | ```bash |
18 | 31 | yarn plop component |
| 32 | +yarn plop test |
| 33 | +yarn plop vrt |
19 | 34 | ``` |
20 | 35 |
|
21 | | -You will be prompted for a component name. Enter it in any form: `action-button`, |
22 | | -`actionButton`, `Action Button`, or even `sp-action-button` (the `sp-`/`swc-` |
23 | | -prefix is stripped). The name is normalized by the built-in case helpers, so the |
24 | | -output is identical regardless of how you type it. |
| 36 | +You are prompted for a name (enter it in any form: `action-button`, |
| 37 | +`actionButton`, `Action Button`, or `sp-action-button` — the `sp-`/`swc-` |
| 38 | +prefix is stripped and the case helpers normalize the rest). |
25 | 39 |
|
26 | 40 | ### Headless (agents, scripts, CI) |
27 | 41 |
|
28 | | -Pass the name as a bypass argument and `--force` to skip the interactive |
29 | | -confirmation: |
| 42 | +Pass the name as a positional bypass argument, plus `--force`: |
30 | 43 |
|
31 | 44 | ```bash |
32 | 45 | yarn plop component "action-button" --force |
| 46 | +yarn plop test "action-button" --force |
| 47 | +yarn plop vrt "action-button" --force |
33 | 48 | ``` |
34 | 49 |
|
35 | | -## What it generates |
| 50 | +Each generator emits a single fixed scaffold (a permutation-grid + forced-colors |
| 51 | +story for `vrt`, and a play-function + a11y-snapshot pair for `test`). The output |
| 52 | +is lint-clean and formatted; `TODO`s mark where the author fills in the |
| 53 | +component-specific detail. |
36 | 54 |
|
37 | | -For a component named `action-button`: |
| 55 | +## Retrofit contract (`test`, `vrt`) |
38 | 56 |
|
39 | | -``` |
40 | | -2nd-gen/packages/core/components/action-button/ |
41 | | - ActionButton.types.ts VALID_SIZES + Size type |
42 | | - ActionButton.base.ts abstract base class (SizedMixin + SpectrumElement) |
43 | | - index.ts re-exports base + types |
44 | | -
|
45 | | -2nd-gen/packages/swc/components/action-button/ |
46 | | - ActionButton.ts concrete class, render(), styles getter |
47 | | - index.ts re-exports the concrete class |
48 | | - swc-action-button.ts defineElement registration + tag-name map |
49 | | - action-button.css :host + .swc-ActionButton block, token() scaffolding |
50 | | - action-button.mdx per-unit docs page (DocsHeader/Canvas/DocsFooter) |
51 | | - stories/action-button.stories.ts Playground/Overview/Anatomy/Sizes/States/Accessibility |
52 | | - test/action-button.test.ts Vitest play function reusing the Overview story |
53 | | - test/action-button.a11y.spec.ts Playwright ARIA-snapshot accessibility test |
54 | | -``` |
| 57 | +`test` and `vrt` add files to a component that **already exists**. They: |
| 58 | + |
| 59 | +- **Guard first.** If `2nd-gen/packages/swc/components/<name>/` is missing, the |
| 60 | + generator aborts with a clear message before writing anything (a typo cannot |
| 61 | + silently scaffold tests for a non-existent component). |
| 62 | +- **Never clobber by default.** The `add` actions use `skipIfExists`, so an |
| 63 | + existing test/VRT file is left untouched. Pass `--force` to overwrite, or |
| 64 | + delete the file first to regenerate it. |
| 65 | +- **Stay scoped.** They format only the `test/` (or `test/vrt/`) subtree they |
| 66 | + wrote, leaving the surrounding component files untouched. |
55 | 67 |
|
56 | | -It also wires the core package's `exports` map: the `swc` package uses wildcard |
57 | | -exports and needs no edit, but `@spectrum-web-components/core` uses explicit |
58 | | -per-component entries, so the generator adds `./components/action-button` and |
59 | | -`./components/action-button/index.js` and re-sorts the `exports` keys |
60 | | -alphabetically (a minimal, deterministic diff). Finally it runs Prettier on the |
61 | | -two new directories so the output lands pre-formatted. |
| 68 | +## What gets wired automatically |
62 | 69 |
|
63 | | -## What it intentionally does NOT do |
| 70 | +- **Core `exports` + `typesVersions`.** The SWC package (`@adobe/spectrum-wc`) |
| 71 | + uses wildcard `exports`, so a new component needs no package.json edit there. |
| 72 | + The core package (`@spectrum-web-components/core`) uses explicit per-component |
| 73 | + entries, so the `component` generator adds both the `exports` and the matching |
| 74 | + `typesVersions` entry for the new component and re-sorts the keys (a minimal, |
| 75 | + deterministic diff). The `test`/`vrt` generators touch no package.json. |
| 76 | +- **Formatting.** Each generator runs Prettier on the directories it wrote. |
64 | 77 |
|
65 | | -The skeleton is a starting point, not a finished component. After generating, |
66 | | -follow `migration-setup` and the later migration phases to: |
| 78 | +## Custom-element registration |
67 | 79 |
|
68 | | -- Move properties, methods, and types from 1st-gen into the base and concrete |
69 | | - classes (Phase 3, `migration-api`). |
70 | | -- Implement semantics, ARIA, and keyboard support (Phase 4, `migration-a11y`). |
71 | | -- Replace the placeholder CSS with migrated Spectrum 2 tokens (Phase 5, |
72 | | - `migration-styling`). |
73 | | -- Flesh out the tests and stories (Phase 6, `migration-testing`). |
74 | | -- Write the docs prose and any `migration-guide.mdx` (Phase 7, |
75 | | - `migration-documentation`; the consumer guide is owned by the |
76 | | - `consumer-migration-guide` skill, so it is not scaffolded here). |
| 80 | +Components are custom elements; the split matters: |
77 | 81 |
|
78 | | -Every generated file contains `TODO`/placeholder markers showing where this work |
79 | | -goes. |
| 82 | +- `index.ts` re-exports the class only (no registration). |
| 83 | +- `swc-<tag>.ts` is the side-effectful entry: it calls |
| 84 | + `defineElement('swc-<tag>', Class)` (from `@adobe/spectrum-wc-core/element`) |
| 85 | + and augments `HTMLElementTagNameMap`. |
| 86 | + |
| 87 | +## What the generators intentionally do NOT do |
| 88 | + |
| 89 | +The skeletons are starting points, not finished components. After generating, |
| 90 | +follow `migration-setup` and the later migration phases (or the `vrt-authoring` |
| 91 | +skill for VRT) to move properties/types into the classes, implement semantics and |
| 92 | +ARIA, migrate CSS to Spectrum 2 tokens, and flesh out the tests, VRT grids, and |
| 93 | +docs prose. Every generated file contains `TODO`/placeholder markers. |
80 | 94 |
|
81 | 95 | ## Conventions the templates encode |
82 | 96 |
|
83 | | -These mirror the `badge` reference component and the project rules in `.ai/`: |
| 97 | +These mirror the `badge` and `button` reference components and the project rules |
| 98 | +in `.ai/`: |
84 | 99 |
|
85 | 100 | - **Two-layer architecture.** Shared, non-visual API lives on the core base |
86 | 101 | class; visual and version-specific API lives on the concrete `swc` class. |
87 | | -- **Sizing via host attribute.** `SizedMixin` reflects `size` to the host, so |
88 | | - size is styled with `:host([size="..."])` selectors, never a modifier class. |
89 | | -- **Public styling API.** Custom properties are exposed as `--swc-<name>-*` with |
90 | | - a `token()` fallback. |
| 102 | +- **Sizing via host attribute.** `SizedMixin` reflects `size` to the host, styled |
| 103 | + with `:host([size="..."])` selectors, never a modifier class. |
| 104 | +- **Public styling API.** Custom properties exposed as `--swc-<name>-*` with a |
| 105 | + `token()` fallback. |
91 | 106 | - **BEM-ish class block.** The render root carries `swc-<PascalCase>`. |
92 | | -- **Stories + per-unit MDX.** The Playground is tagged `['dev']` (not |
93 | | - `['autodocs', 'dev']`) because the generated `.mdx` is the docs page; each |
| 107 | +- **Stories + per-unit MDX.** The Playground is tagged `['dev']`; each |
94 | 108 | section-tagged story is referenced from the MDX via `<Canvas of={...} />`. |
| 109 | +- **Sentence-case titles.** Storybook titles are sentence case (`Action button`), |
| 110 | + matching the project's title rule. |
95 | 111 |
|
96 | 112 | ## Editing the templates |
97 | 113 |
|
98 | | -Templates live in `templates/component/` as Handlebars (`.hbs`) files. Name |
99 | | -derivations use plop's built-in case helpers plus two custom helpers defined in |
| 114 | +Templates live under `templates/` as Handlebars (`.hbs`) files, grouped by |
| 115 | +generator: |
| 116 | + |
| 117 | +``` |
| 118 | +templates/ |
| 119 | + component/{core,swc}/*.hbs the component skeleton |
| 120 | + test/*.hbs the standalone test + a11y files |
| 121 | + vrt/vrt.ts.hbs the VRT story |
| 122 | +``` |
| 123 | + |
| 124 | +Name derivations use plop's built-in case helpers plus custom helpers defined in |
100 | 125 | `plopfile.js`: |
101 | 126 |
|
102 | 127 | | Helper | `action-button` renders as | |
103 | 128 | | ----------------------- | -------------------------- | |
104 | 129 | | `{{dashCase name}}` | `action-button` | |
105 | 130 | | `{{pascalCase name}}` | `ActionButton` | |
106 | 131 | | `{{constantCase name}}` | `ACTION_BUTTON` | |
107 | | -| `{{titleName name}}` | `Action Button` (custom) | |
| 132 | +| `{{titleName name}}` | `Action button` (custom) | |
108 | 133 | | `{{lb}}` / `{{rb}}` | `{` / `}` (custom) | |
109 | 134 |
|
110 | | -`titleName` produces the space-separated proper-noun title used for Storybook |
| 135 | +`titleName` produces the space-separated sentence-case title used for Storybook |
111 | 136 | titles; plop's built-in `titleCase` keeps the dash (`Action-Button`) and must not |
112 | 137 | be used for titles. `lb`/`rb` emit literal braces in `.mdx` templates, where a |
113 | | -bare `{` next to a `{{helper}}` would otherwise be parsed as Handlebars. |
| 138 | +`{` next to a `{{helper}}` would otherwise be parsed as Handlebars. |
114 | 139 |
|
115 | 140 | When you change the file layout, naming, or wiring, update both the templates and |
116 | | -the `add` actions in `plopfile.js`, then regenerate a throwaway component and run |
117 | | -`yarn lint` / `yarn lint:css` on it to confirm the output still passes. |
| 141 | +the actions in `plopfile.js`, then regenerate a throwaway component and run |
| 142 | +`yarn lint`, `yarn lint:css`, and `yarn lint:docs-pages` on it to confirm the |
| 143 | +output still passes. Delete the throwaway component and revert |
| 144 | +`2nd-gen/packages/core/package.json` afterward. |
0 commit comments