@@ -325,9 +325,9 @@ import styles from './css/button-bundle.css'; // No @imports!
325325
326326#### B. Spec System (JS Source → JSON Snapshot + Component Spec)
327327
328- ** The New Architecture ** (as of 2025-01):
328+ Component specs are authored in ** JavaScript ** ( ` .spec.js ` files) with JSON snapshots generated for tooling.
329329
330- Component specs are now authored in ** JavaScript ** ( ` .spec. js` files) with JSON snapshots generated for tooling .
330+ > ** Historical Note ** : Prior to November 2025, specs were authored as ` .json ` files with ` . js` files generated from them. This was reversed to provide better DX (comments, imports, trailing commas). If you encounter guides or documentation referencing editing ` .json ` spec files, that is legacy information - all specs should now be authored as ` .spec.js ` files .
331331
332332** Source spec format** (` .spec.js ` ):
333333``` javascript
@@ -400,61 +400,29 @@ Ensures specs remain JSON-serializable:
400400 };
401401 ```
402402
403- #### C. Legacy JSON Support (Transitional)
403+ #### C. Spec Processing Flow
404404
405- ** Dual format support** during migration:
406405``` javascript
407- // Supports both:
408- const jsonFiles = await glob (' src/primitives/**/specs/*.json' );
406+ // Get all .spec.js source files
409407const specJsFiles = await glob (' src/primitives/**/specs/*.spec.js' );
408+ const entryPoints = specJsFiles;
410409
411- const allFiles = [... jsonFiles, ... specJsFiles];
412- ```
413-
414- ** Legacy ` .json ` files** :
415- - Still supported (read directly)
416- - Generate ` .component.js ` output
417- - Will be migrated to ` .spec.js ` over time
418-
419- ** Exclusions** (generated files not processed):
420- ``` javascript
421- const entryPoints = allFiles .filter (path =>
422- ! path .endsWith (' .component.json' ) // Generated
423- && ! path .endsWith (' .component.js' ) // Generated
424- && ! path .endsWith (' .spec.json' ) // Generated from .spec.js
425- );
426- ```
427-
428- #### D. Spec Processing Flow
429-
430- ``` javascript
431410const createComponentSpecs = async () => {
432411 await asyncEach (entryPoints, async (entryPath ) => {
433- let spec;
434- const isJsSpec = entryPath .endsWith (' .spec.js' );
412+ // Load JS module with cache busting for watch mode
413+ const specModule = await import (` ${ pathToFileURL (entryPath).href } ?t=${ Date .now ()} ` );
414+ const spec = specModule .default ;
435415
436- if (isJsSpec) {
437- // Load JS module with cache busting for watch mode
438- const specModule = await import (` ${ pathToFileURL (entryPath).href } ?t=${ Date .now ()} ` );
439- spec = specModule .default ;
416+ // Validate purity
417+ validateSpec (spec, entryPath);
440418
441- // Validate purity
442- validateSpec (spec, entryPath);
419+ // Generate JSON snapshot
420+ const jsonPath = entryPath .replace (' .spec.js' , ' .spec.json' );
421+ writeFileSync (jsonPath, JSON .stringify (spec, null , 2 ));
443422
444- // Generate JSON snapshot
445- const jsonPath = entryPath .replace (' .spec.js' , ' .spec.json' );
446- writeFileSync (jsonPath, JSON .stringify (spec, null , 2 ));
447- }
448- else {
449- // Legacy JSON loading
450- spec = JSON .parse (readFileSync (entryPath, ' utf8' ));
451- }
452-
453- // Generate component spec (both paths converge here)
423+ // Generate component spec
454424 const componentSpecJS = await generateComponentSpecJS (spec, false , {}, entryPath);
455- const componentJSPath = isJsSpec
456- ? entryPath .replace (' .spec.js' , ' .component.js' )
457- : entryPath .replace (' .json' , ' .component.js' );
425+ const componentJSPath = entryPath .replace (' .spec.js' , ' .component.js' );
458426 writeFileSync (componentJSPath, componentSpecJS);
459427
460428 // Generate plural variant if supported
@@ -474,45 +442,31 @@ Source: Generated:
474442button .spec .js → button .spec .json (snapshot)
475443 (authored) → button .component .js (processed)
476444 → buttons .component .js (if plural)
477-
478- OR (legacy):
479-
480- button .json → button .component .js (processed)
481- (authored) → buttons .component .js (if plural)
482445` ` `
483446
484- #### E . Watch Mode for Specs
447+ #### D . Watch Mode for Specs
485448
486449` ` ` javascript
487450if (watch) {
488- // Watch both legacy JSON and source .spec.js files
489- const jsonSpecFiles = await glob (' src/primitives/**/specs/*.json' );
490- const jsSpecFiles = await glob (' src/primitives/**/specs/*.spec.js' );
491-
492- // Exclude generated files
493- const watchedFiles = [... jsonSpecFiles, ... jsSpecFiles].filter (
494- path =>
495- ! path .endsWith (' .component.json' )
496- && ! path .endsWith (' .component.js' )
497- && ! path .endsWith (' .spec.json' ), // Don't watch generated JSON
498- );
499-
500- specWatcher = build ({
501- watch,
502- write: false , // Don't write esbuild output
503- logLevel: ' silent' , // Just watch for changes
504- entryPoints: watchedFiles,
505- plugins: [
506- callbackPlugin ({
507- onComplete: async (result , { isRebuild }) => {
508- if (isRebuild) {
509- await createComponentSpecs ();
510- await generateJSExportsFromSpecs ();
511- }
512- },
513- }),
514- ],
515- });
451+ // Watch .spec.js source files
452+ if (entryPoints .length > 0 ) {
453+ specWatcher = build ({
454+ watch,
455+ write: false , // Don't write esbuild output
456+ logLevel: ' silent' , // Just watch for changes
457+ entryPoints, // Same entryPoints (*.spec.js files)
458+ outdir: ' .temp-watch' , // Required by esbuild
459+ plugins: [
460+ callbackPlugin ({
461+ onComplete: async (result , { isRebuild }) => {
462+ if (isRebuild) {
463+ await createComponentSpecs ();
464+ }
465+ },
466+ }),
467+ ],
468+ });
469+ }
516470}
517471` ` `
518472
@@ -1394,10 +1348,12 @@ watch({
13941348
13951349### 2. JS → JSON Snapshot Generation (Spec System)
13961350
1351+ > **Historical Note**: This architecture was reversed in November 2025. Previously, JSON was the source with JS generated. Now JS is the source with JSON generated as snapshots.
1352+
13971353**Why it happens**:
1398- - **JS is the source of truth** for component specs (as of 2025-01)
1354+ - **JS is the source of truth** for component specs
13991355- Allows comments, imports, trailing commas, better DX
1400- - But tooling /LLMs benefit from JSON snapshots
1356+ - Tooling /LLMs benefit from JSON snapshots for machine readability
14011357
14021358**Solution**:
14031359- Author specs in ` .spec .js ` format:
@@ -1427,8 +1383,8 @@ watch({
14271383
14281384**Impact**:
14291385- ` .spec .js ` files authored by developers
1430- - ` .spec .json ` files generated by build
1431- - ` .component .js ` files generated from either source
1386+ - ` .spec .json ` files generated by build (snapshot)
1387+ - ` .component .js ` files generated from spec (processed)
14321388- All committed to repo for source consumption
14331389
14341390---
@@ -1475,7 +1431,6 @@ buttons.component.js # Generated component spec (plural, if supported)
14751431- Multiple generated files per spec
14761432- Spec changes require rebuild
14771433- Watch mode handles auto-regeneration with cache busting
1478- - Legacy ` .json ` specs still supported during transition
14791434
14801435---
14811436
@@ -1682,7 +1637,6 @@ What do you need to build?
16821637| Source specs (JS) | `src/primitives/*/ specs/* .spec.js` |
16831638| Generated specs (JSON) | `src/primitives/*/ specs/* .spec.json` |
16841639| Generated component specs | `src/primitives/*/ specs/* .component.js` |
1685- | Legacy specs (JSON) | `src/primitives/*/ specs/* .json` (transitional) |
16861640| Package source | `packages/*/ src/ ` |
16871641| Package dist | ` packages/* /dist/` |
16881642| Core source | `src/` |
@@ -1700,7 +1654,6 @@ What do you need to build?
17001654| `*.spec.json` | Generated | JSON snapshot for tooling/LLMs | ✅ Yes |
17011655| `*.component.js` | Generated | Processed component spec (singular) | ✅ Yes |
17021656| `*s.component.js` | Generated | Plural variant (e.g., `buttons.component.js`) | ✅ Yes |
1703- | `*.json` | Legacy | Old JSON specs (transitional) | ✅ Yes |
17041657
17051658**Example for button primitive**:
17061659```
0 commit comments