@czap/vite 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/css-quantize.d.ts +53 -0
  4. package/dist/css-quantize.d.ts.map +1 -0
  5. package/dist/css-quantize.js +247 -0
  6. package/dist/css-quantize.js.map +1 -0
  7. package/dist/environments.d.ts +36 -0
  8. package/dist/environments.d.ts.map +1 -0
  9. package/dist/environments.js +67 -0
  10. package/dist/environments.js.map +1 -0
  11. package/dist/hmr.d.ts +37 -0
  12. package/dist/hmr.d.ts.map +1 -0
  13. package/dist/hmr.js +84 -0
  14. package/dist/hmr.js.map +1 -0
  15. package/dist/html-transform.d.ts +19 -0
  16. package/dist/html-transform.d.ts.map +1 -0
  17. package/dist/html-transform.js +54 -0
  18. package/dist/html-transform.js.map +1 -0
  19. package/dist/index.d.ts +51 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +43 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/normalize-css-eol.d.ts +7 -0
  24. package/dist/normalize-css-eol.d.ts.map +1 -0
  25. package/dist/normalize-css-eol.js +9 -0
  26. package/dist/normalize-css-eol.js.map +1 -0
  27. package/dist/plugin.d.ts +48 -0
  28. package/dist/plugin.d.ts.map +1 -0
  29. package/dist/plugin.js +404 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/dist/primitive-resolve.d.ts +56 -0
  32. package/dist/primitive-resolve.d.ts.map +1 -0
  33. package/dist/primitive-resolve.js +71 -0
  34. package/dist/primitive-resolve.js.map +1 -0
  35. package/dist/resolve-fs.d.ts +13 -0
  36. package/dist/resolve-fs.d.ts.map +1 -0
  37. package/dist/resolve-fs.js +80 -0
  38. package/dist/resolve-fs.js.map +1 -0
  39. package/dist/resolve-utils.d.ts +20 -0
  40. package/dist/resolve-utils.d.ts.map +1 -0
  41. package/dist/resolve-utils.js +45 -0
  42. package/dist/resolve-utils.js.map +1 -0
  43. package/dist/style-transform.d.ts +49 -0
  44. package/dist/style-transform.d.ts.map +1 -0
  45. package/dist/style-transform.js +122 -0
  46. package/dist/style-transform.js.map +1 -0
  47. package/dist/theme-transform.d.ts +44 -0
  48. package/dist/theme-transform.d.ts.map +1 -0
  49. package/dist/theme-transform.js +85 -0
  50. package/dist/theme-transform.js.map +1 -0
  51. package/dist/token-transform.d.ts +42 -0
  52. package/dist/token-transform.d.ts.map +1 -0
  53. package/dist/token-transform.js +84 -0
  54. package/dist/token-transform.js.map +1 -0
  55. package/dist/virtual-modules.d.ts +55 -0
  56. package/dist/virtual-modules.d.ts.map +1 -0
  57. package/dist/virtual-modules.js +141 -0
  58. package/dist/virtual-modules.js.map +1 -0
  59. package/dist/wasm-resolve.d.ts +25 -0
  60. package/dist/wasm-resolve.d.ts.map +1 -0
  61. package/dist/wasm-resolve.js +36 -0
  62. package/dist/wasm-resolve.js.map +1 -0
  63. package/package.json +63 -0
  64. package/src/css-quantize.ts +294 -0
  65. package/src/environments.ts +98 -0
  66. package/src/hmr.ts +121 -0
  67. package/src/html-transform.ts +61 -0
  68. package/src/index.ts +71 -0
  69. package/src/normalize-css-eol.ts +8 -0
  70. package/src/plugin.ts +492 -0
  71. package/src/primitive-resolve.ts +106 -0
  72. package/src/resolve-fs.ts +82 -0
  73. package/src/resolve-utils.ts +54 -0
  74. package/src/style-transform.ts +157 -0
  75. package/src/theme-transform.ts +119 -0
  76. package/src/token-transform.ts +117 -0
  77. package/src/virtual-modules.ts +160 -0
  78. package/src/wasm-resolve.ts +54 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * HTML transform -- resolves `data-czap="name"` to boundary JSON.
3
+ *
4
+ * Scans HTML/Astro source for `data-czap="..."` attributes, resolves
5
+ * the named boundary via the existing boundary resolution infrastructure,
6
+ * and replaces with `data-czap-boundary='...'` containing serialized JSON.
7
+ *
8
+ * @module
9
+ */
10
+ import { Diagnostics } from '@czap/core';
11
+ import { resolvePrimitive } from './primitive-resolve.js';
12
+ // Match data-czap="boundaryName" (not data-czap-* which are other attrs)
13
+ const DATA_CZAP_PATTERN = /data-czap="([^"]+)"/g;
14
+ /**
15
+ * Transform HTML source, replacing `data-czap="name"` with resolved boundary JSON.
16
+ *
17
+ * @param source - The HTML/Astro source string
18
+ * @param fromFile - The file path of the source (for resolution context)
19
+ * @param projectRoot - The project root directory
20
+ * @returns The transformed source, or the original if no transforms needed
21
+ */
22
+ export async function transformHTML(source, fromFile, projectRoot) {
23
+ const matches = [...source.matchAll(DATA_CZAP_PATTERN)];
24
+ if (matches.length === 0)
25
+ return source;
26
+ let result = source;
27
+ for (const match of matches) {
28
+ const fullMatch = match[0];
29
+ const boundaryName = match[1];
30
+ const resolution = await resolvePrimitive('boundary', boundaryName, fromFile, projectRoot);
31
+ if (!resolution) {
32
+ Diagnostics.warn({
33
+ source: 'czap/vite.html-transform',
34
+ code: 'boundary-not-found',
35
+ message: `Boundary "${boundaryName}" could not be resolved for HTML transform.`,
36
+ detail: { fromFile },
37
+ });
38
+ continue;
39
+ }
40
+ const boundary = resolution.primitive;
41
+ const serialized = JSON.stringify({
42
+ id: boundary.id,
43
+ input: boundary.input,
44
+ thresholds: boundary.thresholds,
45
+ states: boundary.states,
46
+ hysteresis: boundary.hysteresis,
47
+ });
48
+ // Replace data-czap="name" with data-czap-boundary='...'
49
+ const replacement = `data-czap-boundary='${serialized.replace(/'/g, ''')}'`;
50
+ result = result.replace(fullMatch, replacement);
51
+ }
52
+ return result;
53
+ }
54
+ //# sourceMappingURL=html-transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-transform.js","sourceRoot":"","sources":["../src/html-transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,yEAAyE;AACzE,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,QAAgB,EAAE,WAAmB;IACvF,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAExC,IAAI,MAAM,GAAG,MAAM,CAAC;IAEpB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAE/B,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC3F,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,WAAW,CAAC,IAAI,CAAC;gBACf,MAAM,EAAE,0BAA0B;gBAClC,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,aAAa,YAAY,6CAA6C;gBAC/E,MAAM,EAAE,EAAE,QAAQ,EAAE;aACrB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;YAChC,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;SAChC,CAAC,CAAC;QAEH,yDAAyD;QACzD,MAAM,WAAW,GAAG,uBAAuB,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;QAChF,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `@czap/vite` — **LiteShip** Vite 8 plugin: turns `@token` / `@theme` /
3
+ * `@style` / `@quantize` at-rule blocks into native CSS and **rigs** HMR for
4
+ * `@czap/*` definitions.
5
+ *
6
+ * The plugin hooks into Vite's `resolveId`, `load`, `transform`, and
7
+ * `handleHotUpdate` phases:
8
+ *
9
+ * - `resolveId` + `load`: map `virtual:czap/*` specifiers to generated
10
+ * modules (device capabilities, WASM URL, ...).
11
+ * - `transform`: rewrite `@token`, `@theme`, `@style`, and `@quantize`
12
+ * at-rule blocks into native CSS (custom properties,
13
+ * `html[data-theme]` selectors, scoped `@layer` / `@scope` rules,
14
+ * and `@container` queries).
15
+ * - `handleHotUpdate`: emit surgical HMR payloads so CSS variables,
16
+ * shader uniforms, and boundary definitions update without a full
17
+ * page reload.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // vite.config.ts
22
+ * import { defineConfig } from 'vite';
23
+ * import { plugin as czap } from '@czap/vite';
24
+ *
25
+ * const config = defineConfig({
26
+ * plugins: [czap({ themes: ['./themes/default.ts'] })],
27
+ * });
28
+ * ```
29
+ *
30
+ * @module
31
+ */
32
+ export type { PluginConfig } from './plugin.js';
33
+ export { plugin } from './plugin.js';
34
+ export { resolveWASM } from './wasm-resolve.js';
35
+ export type { WASMResolution } from './wasm-resolve.js';
36
+ export type { QuantizeBlock } from './css-quantize.js';
37
+ export { parseQuantizeBlocks, compileQuantizeBlock } from './css-quantize.js';
38
+ export type { TokenBlock } from './token-transform.js';
39
+ export { parseTokenBlocks, compileTokenBlock } from './token-transform.js';
40
+ export type { ThemeBlock } from './theme-transform.js';
41
+ export { parseThemeBlocks, compileThemeBlock } from './theme-transform.js';
42
+ export type { StyleBlock } from './style-transform.js';
43
+ export { parseStyleBlocks, compileStyleBlock } from './style-transform.js';
44
+ export { transformHTML } from './html-transform.js';
45
+ export type { VirtualModuleId } from './virtual-modules.js';
46
+ export { resolveVirtualId, isVirtualId, loadVirtualModule } from './virtual-modules.js';
47
+ export type { HMRPayload } from './hmr.js';
48
+ export { handleHMR } from './hmr.js';
49
+ export type { PrimitiveKind, PrimitiveResolution, PrimitiveShape } from './primitive-resolve.js';
50
+ export { resolvePrimitive } from './primitive-resolve.js';
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAG9E,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG3E,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG3E,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG3E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGxF,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAMrC,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `@czap/vite` — **LiteShip** Vite 8 plugin: turns `@token` / `@theme` /
3
+ * `@style` / `@quantize` at-rule blocks into native CSS and **rigs** HMR for
4
+ * `@czap/*` definitions.
5
+ *
6
+ * The plugin hooks into Vite's `resolveId`, `load`, `transform`, and
7
+ * `handleHotUpdate` phases:
8
+ *
9
+ * - `resolveId` + `load`: map `virtual:czap/*` specifiers to generated
10
+ * modules (device capabilities, WASM URL, ...).
11
+ * - `transform`: rewrite `@token`, `@theme`, `@style`, and `@quantize`
12
+ * at-rule blocks into native CSS (custom properties,
13
+ * `html[data-theme]` selectors, scoped `@layer` / `@scope` rules,
14
+ * and `@container` queries).
15
+ * - `handleHotUpdate`: emit surgical HMR payloads so CSS variables,
16
+ * shader uniforms, and boundary definitions update without a full
17
+ * page reload.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // vite.config.ts
22
+ * import { defineConfig } from 'vite';
23
+ * import { plugin as czap } from '@czap/vite';
24
+ *
25
+ * const config = defineConfig({
26
+ * plugins: [czap({ themes: ['./themes/default.ts'] })],
27
+ * });
28
+ * ```
29
+ *
30
+ * @module
31
+ */
32
+ export { plugin } from './plugin.js';
33
+ export { resolveWASM } from './wasm-resolve.js';
34
+ export { parseQuantizeBlocks, compileQuantizeBlock } from './css-quantize.js';
35
+ export { parseTokenBlocks, compileTokenBlock } from './token-transform.js';
36
+ export { parseThemeBlocks, compileThemeBlock } from './theme-transform.js';
37
+ export { parseStyleBlocks, compileStyleBlock } from './style-transform.js';
38
+ // HTML transform
39
+ export { transformHTML } from './html-transform.js';
40
+ export { resolveVirtualId, isVirtualId, loadVirtualModule } from './virtual-modules.js';
41
+ export { handleHMR } from './hmr.js';
42
+ export { resolvePrimitive } from './primitive-resolve.js';
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI3E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI3E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE3E,iBAAiB;AACjB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAIxF,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAOrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Normalize CSS newlines to LF for line-based parsing of CSS at-rules.
3
+ *
4
+ * @module
5
+ */
6
+ export declare function normalizeCssLineEndings(css: string): string;
7
+ //# sourceMappingURL=normalize-css-eol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-css-eol.d.ts","sourceRoot":"","sources":["../src/normalize-css-eol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3D"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Normalize CSS newlines to LF for line-based parsing of CSS at-rules.
3
+ *
4
+ * @module
5
+ */
6
+ export function normalizeCssLineEndings(css) {
7
+ return css.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
8
+ }
9
+ //# sourceMappingURL=normalize-css-eol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-css-eol.js","sourceRoot":"","sources":["../src/normalize-css-eol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAW;IACjD,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Main Vite 8 plugin for czap -- processes `@token`, `@theme`,
3
+ * `@style`, and `@quantize` CSS blocks, handles HMR, serves virtual
4
+ * modules, and configures build environments.
5
+ *
6
+ * Transform pipeline order: tokens -- themes -- styles -- quantize.
7
+ * This ordering ensures themes / styles can reference token custom
8
+ * properties that were already compiled earlier in the pipeline.
9
+ *
10
+ * @module
11
+ */
12
+ import type { Plugin } from 'vite';
13
+ /**
14
+ * Configuration options for the {@link plugin} factory. Every field
15
+ * is optional; omitted values use convention-based defaults.
16
+ */
17
+ export interface PluginConfig {
18
+ /** Override source directories for each primitive kind. */
19
+ readonly dirs?: Partial<Record<'boundary' | 'token' | 'theme' | 'style', string>>;
20
+ /** Toggle surgical HMR emission (default `true`). */
21
+ readonly hmr?: boolean;
22
+ /** Named Vite environments to configure (browser / server / shader). */
23
+ readonly environments?: readonly ('browser' | 'server' | 'shader')[];
24
+ /** Opt-in WASM runtime configuration. */
25
+ readonly wasm?: {
26
+ readonly enabled?: boolean;
27
+ readonly path?: string;
28
+ };
29
+ }
30
+ /**
31
+ * Create the czap Vite plugin.
32
+ *
33
+ * Transforms CSS files containing `@token`, `@theme`, `@style`, and
34
+ * `@quantize` blocks into native CSS custom properties,
35
+ * `html[data-theme]` selectors, scoped `@layer` / `@scope` rules, and
36
+ * `@container` queries respectively. Uses convention-based definition
37
+ * resolution and provides HMR support for surgical CSS and shader
38
+ * uniform updates.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // vite.config.ts
43
+ * import { plugin as czap } from '@czap/vite';
44
+ * const config = { plugins: [czap()] };
45
+ * ```
46
+ */
47
+ export declare function plugin(config?: PluginConfig): Plugin;
48
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAiBnC;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAClF,qDAAqD;IACrD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,EAAE,CAAC;IACrE,yCAAyC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAwTpD"}
package/dist/plugin.js ADDED
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Main Vite 8 plugin for czap -- processes `@token`, `@theme`,
3
+ * `@style`, and `@quantize` CSS blocks, handles HMR, serves virtual
4
+ * modules, and configures build environments.
5
+ *
6
+ * Transform pipeline order: tokens -- themes -- styles -- quantize.
7
+ * This ordering ensures themes / styles can reference token custom
8
+ * properties that were already compiled earlier in the pipeline.
9
+ *
10
+ * @module
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+ import { parseQuantizeBlocks, compileQuantizeBlock } from './css-quantize.js';
14
+ import { resolvePrimitive } from './primitive-resolve.js';
15
+ import { transformHTML } from './html-transform.js';
16
+ import { parseTokenBlocks, compileTokenBlock } from './token-transform.js';
17
+ import { parseThemeBlocks, compileThemeBlock } from './theme-transform.js';
18
+ import { parseStyleBlocks, compileStyleBlock } from './style-transform.js';
19
+ import { resolveVirtualId, loadVirtualModule } from './virtual-modules.js';
20
+ import { buildEnvironments } from './environments.js';
21
+ import { resolveWASM } from './wasm-resolve.js';
22
+ import { normalizeCssLineEndings } from './normalize-css-eol.js';
23
+ // ---------------------------------------------------------------------------
24
+ // Plugin
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Create the czap Vite plugin.
28
+ *
29
+ * Transforms CSS files containing `@token`, `@theme`, `@style`, and
30
+ * `@quantize` blocks into native CSS custom properties,
31
+ * `html[data-theme]` selectors, scoped `@layer` / `@scope` rules, and
32
+ * `@container` queries respectively. Uses convention-based definition
33
+ * resolution and provides HMR support for surgical CSS and shader
34
+ * uniform updates.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // vite.config.ts
39
+ * import { plugin as czap } from '@czap/vite';
40
+ * const config = { plugins: [czap()] };
41
+ * ```
42
+ */
43
+ export function plugin(config) {
44
+ const hmrEnabled = config?.hmr !== false;
45
+ const wasmEnabled = config?.wasm?.enabled === true;
46
+ let projectRoot = process.cwd();
47
+ let isBuild = false;
48
+ let resolvedWasm = null;
49
+ if (wasmEnabled) {
50
+ resolvedWasm = resolveWASM(projectRoot, config?.wasm?.path);
51
+ }
52
+ let emittedWasmRefId = null;
53
+ // Caches for resolved definitions to avoid re-importing on every transform
54
+ const boundaryCache = new Map();
55
+ const tokenCache = new Map();
56
+ const themeCache = new Map();
57
+ const styleCache = new Map();
58
+ return {
59
+ name: '@czap/vite',
60
+ enforce: 'pre',
61
+ configResolved(resolvedConfig) {
62
+ projectRoot = resolvedConfig.root;
63
+ isBuild = resolvedConfig.command === 'build';
64
+ resolvedWasm = null;
65
+ if (wasmEnabled) {
66
+ resolvedWasm = resolveWASM(projectRoot, config?.wasm?.path);
67
+ }
68
+ },
69
+ buildStart() {
70
+ if (!wasmEnabled) {
71
+ return;
72
+ }
73
+ resolvedWasm = resolveWASM(projectRoot, config?.wasm?.path);
74
+ if (!resolvedWasm) {
75
+ this.warn('WASM support was enabled, but no czap-compute binary could be resolved. Runtime will fall back to TypeScript kernels.');
76
+ return;
77
+ }
78
+ if (isBuild) {
79
+ emittedWasmRefId = this.emitFile({
80
+ type: 'asset',
81
+ name: 'czap-compute.wasm',
82
+ source: readFileSync(resolvedWasm.filePath),
83
+ });
84
+ }
85
+ },
86
+ // -----------------------------------------------------------------------
87
+ // HMR client script injection
88
+ // -----------------------------------------------------------------------
89
+ transformIndexHtml() {
90
+ if (!hmrEnabled)
91
+ return [];
92
+ return [
93
+ {
94
+ tag: 'script',
95
+ attrs: { type: 'module' },
96
+ children: `import 'virtual:czap/hmr-client';`,
97
+ injectTo: 'head',
98
+ },
99
+ ];
100
+ },
101
+ // -----------------------------------------------------------------------
102
+ // Virtual module resolution
103
+ // -----------------------------------------------------------------------
104
+ resolveId(id) {
105
+ return resolveVirtualId(id);
106
+ },
107
+ load(id) {
108
+ if (id === '\0virtual:czap/wasm-url') {
109
+ if (!wasmEnabled) {
110
+ return 'export const wasmUrl = null;';
111
+ }
112
+ if (!resolvedWasm) {
113
+ return 'export const wasmUrl = null;';
114
+ }
115
+ if (isBuild && emittedWasmRefId) {
116
+ return `export const wasmUrl = import.meta.ROLLUP_FILE_URL_${emittedWasmRefId};`;
117
+ }
118
+ const browserUrl = resolvedWasm.source === 'public' ? '/czap-compute.wasm' : `/@fs/${resolvedWasm.filePath.replace(/\\/g, '/')}`;
119
+ return `export const wasmUrl = ${JSON.stringify(browserUrl)};`;
120
+ }
121
+ return loadVirtualModule(id);
122
+ },
123
+ // -----------------------------------------------------------------------
124
+ // CSS transform pipeline: tokens -> themes -> styles -> quantize
125
+ // -----------------------------------------------------------------------
126
+ async transform(code, id) {
127
+ if (id.endsWith('.html') || id.endsWith('.astro')) {
128
+ const transformed = await transformHTML(code, id, projectRoot);
129
+ if (transformed === code) {
130
+ return null;
131
+ }
132
+ return {
133
+ code: transformed,
134
+ map: null,
135
+ };
136
+ }
137
+ // Only process CSS files
138
+ if (!id.endsWith('.css'))
139
+ return null;
140
+ // Quick check -- skip files with no @czap at-rules
141
+ const hasToken = code.includes('@token');
142
+ const hasTheme = code.includes('@theme');
143
+ const hasStyle = code.includes('@style');
144
+ const hasQuantize = code.includes('@quantize');
145
+ if (!hasToken && !hasTheme && !hasStyle && !hasQuantize)
146
+ return null;
147
+ let transformed = normalizeCssLineEndings(code);
148
+ // ---- Phase 1: @token -> CSS custom properties + @property ----
149
+ if (hasToken) {
150
+ const tokenBlocks = parseTokenBlocks(transformed, id);
151
+ for (const block of tokenBlocks) {
152
+ const cacheKey = `${block.tokenName}:${id}`;
153
+ let token = tokenCache.get(cacheKey);
154
+ if (token === undefined) {
155
+ const resolution = await resolvePrimitive('token', block.tokenName, id, projectRoot, config?.dirs?.token);
156
+ token = resolution?.primitive ?? null;
157
+ tokenCache.set(cacheKey, token);
158
+ }
159
+ if (token === null) {
160
+ this.warn(`Could not resolve token "${block.tokenName}" referenced in ${id}:${block.line}`);
161
+ continue;
162
+ }
163
+ const compiled = compileTokenBlock(block, token);
164
+ const blockSpan = findAtRuleBlock(transformed, '@token', block.tokenName);
165
+ if (blockSpan) {
166
+ transformed = transformed.substring(0, blockSpan.start) + compiled + transformed.substring(blockSpan.end);
167
+ }
168
+ }
169
+ }
170
+ // ---- Phase 2: @theme -> html[data-theme] selectors + transitions ----
171
+ if (hasTheme) {
172
+ const themeBlocks = parseThemeBlocks(transformed, id);
173
+ for (const block of themeBlocks) {
174
+ const cacheKey = `${block.themeName}:${id}`;
175
+ let theme = themeCache.get(cacheKey);
176
+ if (theme === undefined) {
177
+ const resolution = await resolvePrimitive('theme', block.themeName, id, projectRoot, config?.dirs?.theme);
178
+ theme = resolution?.primitive ?? null;
179
+ themeCache.set(cacheKey, theme);
180
+ }
181
+ if (theme === null) {
182
+ this.warn(`Could not resolve theme "${block.themeName}" referenced in ${id}:${block.line}`);
183
+ continue;
184
+ }
185
+ const compiled = compileThemeBlock(block, theme);
186
+ const blockSpan = findAtRuleBlock(transformed, '@theme', block.themeName);
187
+ if (blockSpan) {
188
+ transformed = transformed.substring(0, blockSpan.start) + compiled + transformed.substring(blockSpan.end);
189
+ }
190
+ }
191
+ }
192
+ // ---- Phase 3: @style -> scoped CSS with @layer/@scope/@starting-style ----
193
+ if (hasStyle) {
194
+ const styleBlocks = parseStyleBlocks(transformed, id);
195
+ for (const block of styleBlocks) {
196
+ const cacheKey = `${block.styleName}:${id}`;
197
+ let style = styleCache.get(cacheKey);
198
+ if (style === undefined) {
199
+ const resolution = await resolvePrimitive('style', block.styleName, id, projectRoot, config?.dirs?.style);
200
+ style = resolution?.primitive ?? null;
201
+ styleCache.set(cacheKey, style);
202
+ }
203
+ if (style === null) {
204
+ this.warn(`Could not resolve style "${block.styleName}" referenced in ${id}:${block.line}`);
205
+ continue;
206
+ }
207
+ const compiled = compileStyleBlock(block, style);
208
+ const blockSpan = findAtRuleBlock(transformed, '@style', block.styleName);
209
+ if (blockSpan) {
210
+ transformed = transformed.substring(0, blockSpan.start) + compiled + transformed.substring(blockSpan.end);
211
+ }
212
+ }
213
+ }
214
+ // ---- Phase 4: @quantize -> @container queries (existing) ----
215
+ if (hasQuantize) {
216
+ const quantizeBlocks = parseQuantizeBlocks(transformed, id);
217
+ for (const block of quantizeBlocks) {
218
+ const cacheKey = `${block.boundaryName}:${id}`;
219
+ let boundary = boundaryCache.get(cacheKey);
220
+ if (boundary === undefined) {
221
+ const resolution = await resolvePrimitive('boundary', block.boundaryName, id, projectRoot, config?.dirs?.boundary);
222
+ boundary = resolution?.primitive ?? null;
223
+ boundaryCache.set(cacheKey, boundary);
224
+ }
225
+ if (boundary === null) {
226
+ this.warn(`Could not resolve boundary "${block.boundaryName}" referenced in ${id}:${block.line}`);
227
+ continue;
228
+ }
229
+ const compiled = compileQuantizeBlock(block, boundary);
230
+ const blockSpan = findAtRuleBlock(transformed, '@quantize', block.boundaryName);
231
+ if (blockSpan) {
232
+ transformed = transformed.substring(0, blockSpan.start) + compiled + transformed.substring(blockSpan.end);
233
+ }
234
+ }
235
+ }
236
+ if (transformed === code)
237
+ return null;
238
+ return {
239
+ code: transformed,
240
+ map: null,
241
+ };
242
+ },
243
+ // -----------------------------------------------------------------------
244
+ // HMR: invalidate caches + re-transform on definition file changes
245
+ // -----------------------------------------------------------------------
246
+ hotUpdate(options) {
247
+ if (!hmrEnabled)
248
+ return;
249
+ const file = options.file;
250
+ // Invalidate definition caches when source files change
251
+ const isDefFile = file.endsWith('.boundaries.ts') ||
252
+ file.endsWith('/boundaries.ts') ||
253
+ file.endsWith('.tokens.ts') ||
254
+ file.endsWith('/tokens.ts') ||
255
+ file.endsWith('.themes.ts') ||
256
+ file.endsWith('/themes.ts') ||
257
+ file.endsWith('.styles.ts') ||
258
+ file.endsWith('/styles.ts');
259
+ if (isDefFile) {
260
+ // Clear all caches since definitions may cross-reference
261
+ boundaryCache.clear();
262
+ tokenCache.clear();
263
+ themeCache.clear();
264
+ styleCache.clear();
265
+ const moduleGraph = this.environment.moduleGraph;
266
+ const transformModules = Array.from(moduleGraph.idToModuleMap.values()).filter((mod) => {
267
+ const moduleId = mod.id;
268
+ return (typeof moduleId === 'string' &&
269
+ (moduleId.endsWith('.css') || moduleId.endsWith('.astro') || moduleId.endsWith('.html')));
270
+ });
271
+ if (transformModules.length > 0) {
272
+ return transformModules;
273
+ }
274
+ }
275
+ if (file.endsWith('.css') || file.endsWith('.astro') || file.endsWith('.html')) {
276
+ const moduleGraph = this.environment.moduleGraph;
277
+ const mod = moduleGraph.getModuleById(file);
278
+ if (mod) {
279
+ return [mod];
280
+ }
281
+ }
282
+ return;
283
+ },
284
+ config() {
285
+ if (!config?.environments || config.environments.length === 0)
286
+ return {};
287
+ const envNames = config.environments;
288
+ const envs = buildEnvironments(envNames);
289
+ return {
290
+ environments: envs,
291
+ };
292
+ },
293
+ };
294
+ }
295
+ // ---------------------------------------------------------------------------
296
+ // Helpers
297
+ // ---------------------------------------------------------------------------
298
+ /**
299
+ * Find the full span of an at-rule block in CSS source.
300
+ * Returns the start/end character offsets, or null if not found.
301
+ *
302
+ * Works for any at-rule pattern: `@token`, `@theme`, `@style`,
303
+ * `@quantize`. Uses a state machine to correctly skip block comments,
304
+ * quoted strings, and `url(...)` tokens (which may contain braces in
305
+ * data URIs) before counting brace depth.
306
+ */
307
+ function findAtRuleBlock(css, marker, name) {
308
+ let searchFrom = 0;
309
+ while (searchFrom < css.length) {
310
+ const idx = css.indexOf(marker, searchFrom);
311
+ if (idx === -1)
312
+ return null;
313
+ // Verify this at-rule is followed by the target name
314
+ const afterMarker = css.substring(idx + marker.length).trimStart();
315
+ if (!afterMarker.startsWith(name)) {
316
+ searchFrom = idx + marker.length;
317
+ continue;
318
+ }
319
+ // Ensure the name isn't just a prefix of a longer identifier
320
+ const charAfterName = afterMarker[name.length];
321
+ if (charAfterName !== undefined && /[a-zA-Z0-9_-]/.test(charAfterName)) {
322
+ searchFrom = idx + marker.length;
323
+ continue;
324
+ }
325
+ // Find the opening brace
326
+ const braceStart = css.indexOf('{', idx);
327
+ /* v8 ignore next — unreachable under real call sites: `findAtRuleBlock` runs only
328
+ after `parseTokenBlocks`/etc. matched a `@marker name { ... }` block with braces,
329
+ so the `{` is always still present in the transformed source. Defensive against
330
+ future multi-phase edits that strip braces between parse and lookup. */
331
+ if (braceStart === -1)
332
+ return null;
333
+ // Walk forward tracking depth with full comment/string/url awareness
334
+ let depth = 1;
335
+ let pos = braceStart + 1;
336
+ while (pos < css.length && depth > 0) {
337
+ const ch = css[pos];
338
+ // Skip block comments: /* ... */
339
+ if (ch === '/' && css[pos + 1] === '*') {
340
+ pos += 2;
341
+ while (pos < css.length - 1 && !(css[pos] === '*' && css[pos + 1] === '/')) {
342
+ pos++;
343
+ }
344
+ pos += 2;
345
+ continue;
346
+ }
347
+ // Skip quoted strings: "..." and '...' (with backslash escapes)
348
+ if (ch === '"' || ch === "'") {
349
+ const quote = ch;
350
+ pos++;
351
+ while (pos < css.length && css[pos] !== quote) {
352
+ if (css[pos] === '\\')
353
+ pos++;
354
+ pos++;
355
+ }
356
+ pos++;
357
+ continue;
358
+ }
359
+ // Skip url(...) tokens: may contain unquoted data URIs with braces
360
+ if (ch === 'u' && css.slice(pos, pos + 4).toLowerCase() === 'url(') {
361
+ pos += 4;
362
+ // url() may use a quoted or unquoted value
363
+ if (css[pos] === '"' || css[pos] === "'") {
364
+ const quote = css[pos];
365
+ pos++;
366
+ while (pos < css.length && css[pos] !== quote) {
367
+ if (css[pos] === '\\')
368
+ pos++;
369
+ pos++;
370
+ }
371
+ pos++; // closing quote
372
+ }
373
+ else {
374
+ // unquoted -- scan until the matching ')'
375
+ let parenDepth = 1;
376
+ while (pos < css.length && parenDepth > 0) {
377
+ if (css[pos] === '(')
378
+ parenDepth++;
379
+ else if (css[pos] === ')')
380
+ parenDepth--;
381
+ pos++;
382
+ }
383
+ }
384
+ continue;
385
+ }
386
+ if (ch === '{')
387
+ depth++;
388
+ else if (ch === '}')
389
+ depth--;
390
+ pos++;
391
+ }
392
+ if (depth === 0) {
393
+ return { start: idx, end: pos };
394
+ }
395
+ return null;
396
+ }
397
+ /* v8 ignore next — unreachable under real call sites: the inner `while` only runs
398
+ when `parseTokenBlocks` has already matched a `@marker name { ... }` block, so the
399
+ first indexOf hit returns either a `{start,end}` span or null inside the loop.
400
+ This terminal `return null` is a defense against pathological CSS where the
401
+ marker+name hits but searchFrom exhausts without a `{` match. */
402
+ return null;
403
+ }
404
+ //# sourceMappingURL=plugin.js.map