@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,294 @@
1
+ /**
2
+ * `@quantize` CSS block parser and compiler.
3
+ *
4
+ * Parses custom `@quantize boundaryName { state { prop: value } }` blocks
5
+ * from CSS source and compiles them into native `@container` queries using
6
+ * resolved `BoundaryDef` thresholds.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type { Boundary } from '@czap/core';
12
+ import { CSSCompiler } from '@czap/compiler';
13
+ import { normalizeCssLineEndings } from './normalize-css-eol.js';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Types
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * A single parsed `@quantize` block: the boundary being quantised, the
21
+ * per-state property bag, and provenance info so HMR can emit
22
+ * source-mapped warnings.
23
+ */
24
+ export interface QuantizeBlock {
25
+ /** Boundary name referenced in the at-rule preamble. */
26
+ readonly boundaryName: string;
27
+ /** `{ stateName: { cssProp: value } }` mapping. */
28
+ readonly states: Record<string, Record<string, string>>;
29
+ /** Absolute path of the CSS source file. */
30
+ readonly sourceFile: string;
31
+ /** 1-based source line where the block begins. */
32
+ readonly line: number;
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Parser helpers
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /**
40
+ * Parse all property declarations inside a state block starting at `pos`
41
+ * (the character immediately after the opening `{` of the state block).
42
+ *
43
+ * Uses character-level scanning so multi-line values -- e.g.
44
+ * background: linear-gradient(
45
+ * to bottom,
46
+ * red,
47
+ * blue
48
+ * );
49
+ * -- are collected as a single declaration before matching.
50
+ *
51
+ * Tracks paren depth so commas/semicolons inside functional notation
52
+ * (var(), calc(), linear-gradient(), etc.) are not treated as delimiters.
53
+ * Tracks brace depth so values containing braces (e.g. `var(--x, empty)`)
54
+ * do not prematurely close the state block.
55
+ *
56
+ * Returns the parsed properties and the position immediately after the
57
+ * closing `}` of the state block.
58
+ */
59
+ function parseStateDeclarations(css: string, pos: number): { props: Record<string, string>; end: number } {
60
+ const props: Record<string, string> = {};
61
+ let braceDepth = 0;
62
+
63
+ while (pos < css.length) {
64
+ // Skip whitespace between declarations
65
+ while (pos < css.length && /\s/.test(css[pos]!)) pos++;
66
+ if (pos >= css.length) break;
67
+
68
+ const ch = css[pos]!;
69
+
70
+ // Skip block comments
71
+ if (ch === '/' && css[pos + 1] === '*') {
72
+ pos += 2;
73
+ while (pos < css.length - 1 && !(css[pos] === '*' && css[pos + 1] === '/')) pos++;
74
+ pos += 2;
75
+ continue;
76
+ }
77
+
78
+ // Closing brace of the state block
79
+ if (ch === '}' && braceDepth === 0) {
80
+ pos++;
81
+ return { props, end: pos };
82
+ }
83
+
84
+ // Opening brace nested inside a value (e.g. var(--x, {}))
85
+ if (ch === '{') {
86
+ braceDepth++;
87
+ pos++;
88
+ continue;
89
+ }
90
+
91
+ if (ch === '}') {
92
+ braceDepth--;
93
+ pos++;
94
+ continue;
95
+ }
96
+
97
+ // Accumulate a full declaration: collect until `;` at paren-depth 0,
98
+ // or until `}` that closes this state block, whichever comes first.
99
+ let declBuf = '';
100
+ let parenDepth = 0;
101
+
102
+ while (pos < css.length) {
103
+ const dc = css[pos]!;
104
+
105
+ // Skip block comments inside declaration
106
+ if (dc === '/' && css[pos + 1] === '*') {
107
+ pos += 2;
108
+ while (pos < css.length - 1 && !(css[pos] === '*' && css[pos + 1] === '/')) pos++;
109
+ pos += 2;
110
+ continue;
111
+ }
112
+
113
+ // Skip quoted strings
114
+ if (dc === '"' || dc === "'") {
115
+ const quote = dc;
116
+ declBuf += dc;
117
+ pos++;
118
+ while (pos < css.length && css[pos] !== quote) {
119
+ if (css[pos] === '\\') {
120
+ declBuf += css[pos]!;
121
+ pos++;
122
+ }
123
+ declBuf += css[pos]!;
124
+ pos++;
125
+ }
126
+ if (pos < css.length) {
127
+ declBuf += css[pos]!;
128
+ pos++;
129
+ }
130
+ continue;
131
+ }
132
+
133
+ if (dc === '(') {
134
+ parenDepth++;
135
+ declBuf += dc;
136
+ pos++;
137
+ continue;
138
+ }
139
+ if (dc === ')') {
140
+ parenDepth--;
141
+ declBuf += dc;
142
+ pos++;
143
+ continue;
144
+ }
145
+
146
+ // Semicolon at paren-depth 0 ends the declaration
147
+ if (dc === ';' && parenDepth === 0) {
148
+ pos++;
149
+ break;
150
+ }
151
+
152
+ // Unmatched `}` at paren-depth 0 closes the state block --
153
+ // do NOT consume it here; the outer loop will handle it.
154
+ if (dc === '}' && parenDepth === 0) {
155
+ break;
156
+ }
157
+
158
+ declBuf += dc;
159
+ pos++;
160
+ }
161
+
162
+ const decl = declBuf.trim();
163
+ if (decl.length === 0) continue;
164
+
165
+ // Match `property-name: value` (property names are [a-zA-Z-][a-zA-Z0-9-]*)
166
+ const colonIdx = decl.indexOf(':');
167
+ if (colonIdx > 0) {
168
+ const propName = decl.slice(0, colonIdx).trim();
169
+ const propValue = decl.slice(colonIdx + 1).trim();
170
+ if (/^[a-zA-Z-][a-zA-Z0-9-]*$/.test(propName) && propValue.length > 0) {
171
+ props[propName] = propValue;
172
+ }
173
+ }
174
+ }
175
+
176
+ return { props, end: pos };
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Parser
181
+ // ---------------------------------------------------------------------------
182
+
183
+ /**
184
+ * Parse every `@quantize` block from CSS source text.
185
+ *
186
+ * Grammar:
187
+ *
188
+ * ```css
189
+ * @quantize boundaryName {
190
+ * stateName {
191
+ * property: value;
192
+ * }
193
+ * }
194
+ * ```
195
+ *
196
+ * The outer `@quantize` and state-name matching is line-based for
197
+ * simplicity; property declarations inside state blocks use a
198
+ * character-level parser so that multi-line values (e.g.
199
+ * `linear-gradient` spread across lines) are collected correctly
200
+ * before being matched.
201
+ */
202
+ export function parseQuantizeBlocks(css: string, sourceFile: string): readonly QuantizeBlock[] {
203
+ const normalized = normalizeCssLineEndings(css);
204
+ const blocks: QuantizeBlock[] = [];
205
+ const lines = normalized.split('\n');
206
+ let i = 0;
207
+
208
+ while (i < lines.length) {
209
+ const line = lines[i]!;
210
+ const atMatch = line.match(/^\s*@quantize\s+([a-zA-Z_][a-zA-Z0-9_-]*)\s*\{/);
211
+
212
+ if (atMatch) {
213
+ const boundaryName = atMatch[1]!;
214
+ const blockStartLine = i + 1; // 1-indexed
215
+ const states: Record<string, Record<string, string>> = {};
216
+
217
+ i++; // advance past @quantize line
218
+ let braceDepth = 1;
219
+
220
+ while (i < lines.length && braceDepth > 0) {
221
+ const currentLine = lines[i]!;
222
+ const trimmed = currentLine.trim();
223
+
224
+ if (braceDepth === 1) {
225
+ // Look for a state block opening: `stateName {`
226
+ const stateMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*\{/);
227
+ if (stateMatch) {
228
+ const stateName = stateMatch[1]!;
229
+
230
+ // Compute the character offset of the `{` that opens this state
231
+ // block inside the full CSS string so we can hand off to the
232
+ // character-level parser.
233
+ const lineOffset = normalized.split('\n').slice(0, i).join('\n').length + 1;
234
+ const openBrace = lineOffset + currentLine.indexOf('{') + 1;
235
+
236
+ const { props, end } = parseStateDeclarations(normalized, openBrace);
237
+ states[stateName] = props;
238
+
239
+ // Advance the line cursor to the line that contains `end`
240
+ let charCount = 0;
241
+ let lineIdx = 0;
242
+ for (const l of normalized.split('\n')) {
243
+ charCount += l.length + 1; // +1 for the '\n'
244
+ lineIdx++;
245
+ if (charCount >= end) break;
246
+ }
247
+ i = lineIdx;
248
+ continue;
249
+ }
250
+
251
+ // Closing brace for the @quantize block
252
+ if (trimmed === '}') {
253
+ braceDepth--;
254
+ i++;
255
+ continue;
256
+ }
257
+ }
258
+
259
+ // Track nested braces outside of state blocks for robustness
260
+ for (const ch of trimmed) {
261
+ if (ch === '{') braceDepth++;
262
+ if (ch === '}') braceDepth--;
263
+ }
264
+ i++;
265
+ }
266
+
267
+ blocks.push({
268
+ boundaryName,
269
+ states,
270
+ sourceFile,
271
+ line: blockStartLine,
272
+ });
273
+ } else {
274
+ i++;
275
+ }
276
+ }
277
+
278
+ return blocks;
279
+ }
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // Compiler (delegates to @czap/compiler CSSCompiler)
283
+ // ---------------------------------------------------------------------------
284
+
285
+ /**
286
+ * Compile a parsed {@link QuantizeBlock} plus its resolved
287
+ * {@link Boundary.Shape} into CSS `@container` query rules. Delegates
288
+ * to the canonical `CSSCompiler` to avoid duplicating threshold-to-query
289
+ * logic.
290
+ */
291
+ export function compileQuantizeBlock(block: QuantizeBlock, boundary: Boundary.Shape): string {
292
+ const result = CSSCompiler.compile(boundary, block.states);
293
+ return CSSCompiler.serialize(result);
294
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Vite 8 Environment API configuration.
3
+ *
4
+ * Provides environment-specific resolve conditions and optimisation
5
+ * settings for browser, server, and shader build targets.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Types
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /** Named czap build environment. */
15
+ export type CzapEnvironmentName = 'browser' | 'server' | 'shader';
16
+
17
+ /**
18
+ * Subset of a Vite `Environment` config that czap touches: resolve
19
+ * conditions plus `optimizeDeps` include/exclude lists. Returned by
20
+ * {@link getEnvironmentConfig} and merged into the host Vite config
21
+ * via {@link buildEnvironments}.
22
+ */
23
+ export interface CzapEnvironmentConfig {
24
+ readonly resolve: {
25
+ readonly conditions: string[];
26
+ readonly extensions: string[];
27
+ };
28
+ readonly optimizeDeps: {
29
+ readonly include: string[];
30
+ readonly exclude: string[];
31
+ };
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Environment Definitions
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const BROWSER_ENV: CzapEnvironmentConfig = {
39
+ resolve: {
40
+ conditions: ['browser', 'import', 'module', 'default'],
41
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.css'],
42
+ },
43
+ optimizeDeps: {
44
+ include: ['@czap/core', '@czap/detect'],
45
+ exclude: [],
46
+ },
47
+ } as const;
48
+
49
+ const SERVER_ENV: CzapEnvironmentConfig = {
50
+ resolve: {
51
+ conditions: ['node', 'import', 'module', 'default'],
52
+ extensions: ['.ts', '.tsx', '.js', '.jsx'],
53
+ },
54
+ optimizeDeps: {
55
+ include: [],
56
+ exclude: ['@czap/core', '@czap/detect'],
57
+ },
58
+ } as const;
59
+
60
+ const SHADER_ENV: CzapEnvironmentConfig = {
61
+ resolve: {
62
+ conditions: ['browser', 'import', 'module', 'default'],
63
+ extensions: ['.ts', '.js', '.glsl', '.wgsl', '.vert', '.frag'],
64
+ },
65
+ optimizeDeps: {
66
+ include: ['@czap/core'],
67
+ exclude: ['@czap/detect'],
68
+ },
69
+ } as const;
70
+
71
+ const ENVIRONMENT_MAP: Record<CzapEnvironmentName, CzapEnvironmentConfig> = {
72
+ browser: BROWSER_ENV,
73
+ server: SERVER_ENV,
74
+ shader: SHADER_ENV,
75
+ } as const;
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Public API
79
+ // ---------------------------------------------------------------------------
80
+
81
+ /**
82
+ * Get the Vite environment configuration for a specific czap target.
83
+ */
84
+ export function getEnvironmentConfig(name: CzapEnvironmentName): CzapEnvironmentConfig {
85
+ return ENVIRONMENT_MAP[name];
86
+ }
87
+
88
+ /**
89
+ * Build the Vite environments configuration object from a list of
90
+ * requested environment names.
91
+ */
92
+ export function buildEnvironments(names: readonly CzapEnvironmentName[]): Record<string, CzapEnvironmentConfig> {
93
+ const result: Record<string, CzapEnvironmentConfig> = {};
94
+ for (const name of names) {
95
+ result[name] = getEnvironmentConfig(name);
96
+ }
97
+ return result;
98
+ }
package/src/hmr.ts ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * HMR handler for `czap:update` messages.
3
+ *
4
+ * Performs surgical DOM updates when `@quantize` CSS or shader
5
+ * uniforms change during development, avoiding full page reloads.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ declare global {
11
+ interface HTMLCanvasElement {
12
+ /**
13
+ * czap runtime-attached WebGL program for HMR uniform updates.
14
+ * Set by the shader directive when a program is linked.
15
+ */
16
+ __czapProgram?: WebGLProgram;
17
+ }
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Types
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /**
25
+ * Shape of the HMR payload the czap Vite plugin ships over the Vite
26
+ * dev-server WebSocket. Handled by {@link handleHMR} on the client.
27
+ */
28
+ export interface HMRPayload {
29
+ /** Message discriminator. Always `'czap:update'`. */
30
+ readonly type: 'czap:update';
31
+ /** Boundary id whose compiled output changed. */
32
+ readonly boundary: string;
33
+ /** New compiled CSS (omitted when only uniforms changed). */
34
+ readonly css?: string;
35
+ /** New shader-uniform values (omitted when only CSS changed). */
36
+ readonly uniforms?: Record<string, number>;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // CSS Hot Update
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Find or create a <style> element for a specific boundary's compiled CSS.
45
+ * Uses a data attribute for identification across HMR cycles.
46
+ */
47
+ function getOrCreateStyleElement(boundaryId: string): HTMLStyleElement {
48
+ const selector = `style[data-czap-boundary="${boundaryId}"]`;
49
+ const existing = document.querySelector(selector);
50
+ if (existing instanceof HTMLStyleElement) return existing;
51
+
52
+ const el = document.createElement('style');
53
+ el.setAttribute('data-czap-boundary', boundaryId);
54
+ document.head.appendChild(el);
55
+ return el;
56
+ }
57
+
58
+ /**
59
+ * Apply CSS updates by replacing the boundary's style element content.
60
+ */
61
+ function applyCSSUpdate(boundary: string, css: string): void {
62
+ const el = getOrCreateStyleElement(boundary);
63
+ el.textContent = css;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Shader Uniform Hot Update
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * Update shader uniform values on all canvases that have a czap-boundary
72
+ * attribute matching the boundary name.
73
+ *
74
+ * This works by dispatching a custom event that the czap runtime shader
75
+ * system listens for, passing the uniform map for in-place updates.
76
+ */
77
+ function applyUniformUpdate(boundary: string, uniforms: Record<string, number>): void {
78
+ const event = new CustomEvent('czap:uniform-update', {
79
+ detail: { boundary, uniforms },
80
+ bubbles: true,
81
+ });
82
+ document.dispatchEvent(event);
83
+
84
+ // Direct update: find canvas elements with matching boundary data attribute
85
+ const canvases = Array.from(document.querySelectorAll<HTMLCanvasElement>(`canvas[data-czap-boundary="${boundary}"]`));
86
+ for (const canvas of canvases) {
87
+ const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
88
+ if (!gl) continue;
89
+
90
+ // Look up the program stored on the canvas element via a custom property
91
+ const program = canvas.__czapProgram;
92
+ if (!program) continue;
93
+
94
+ for (const [name, value] of Object.entries(uniforms)) {
95
+ const location = gl.getUniformLocation(program, name);
96
+ if (location !== null) {
97
+ gl.uniform1f(location, value);
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Public API
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /**
108
+ * Handle a czap:update HMR payload.
109
+ * Dispatches to CSS replacement or shader uniform updates based on payload content.
110
+ */
111
+ export function handleHMR(payload: HMRPayload): void {
112
+ if (typeof document === 'undefined') return;
113
+
114
+ if (payload.css !== undefined) {
115
+ applyCSSUpdate(payload.boundary, payload.css);
116
+ }
117
+
118
+ if (payload.uniforms !== undefined) {
119
+ applyUniformUpdate(payload.boundary, payload.uniforms);
120
+ }
121
+ }
@@ -0,0 +1,61 @@
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
+
11
+ import { Diagnostics } from '@czap/core';
12
+ import { resolvePrimitive } from './primitive-resolve.js';
13
+
14
+ // Match data-czap="boundaryName" (not data-czap-* which are other attrs)
15
+ const DATA_CZAP_PATTERN = /data-czap="([^"]+)"/g;
16
+
17
+ /**
18
+ * Transform HTML source, replacing `data-czap="name"` with resolved boundary JSON.
19
+ *
20
+ * @param source - The HTML/Astro source string
21
+ * @param fromFile - The file path of the source (for resolution context)
22
+ * @param projectRoot - The project root directory
23
+ * @returns The transformed source, or the original if no transforms needed
24
+ */
25
+ export async function transformHTML(source: string, fromFile: string, projectRoot: string): Promise<string> {
26
+ const matches = [...source.matchAll(DATA_CZAP_PATTERN)];
27
+ if (matches.length === 0) return source;
28
+
29
+ let result = source;
30
+
31
+ for (const match of matches) {
32
+ const fullMatch = match[0]!;
33
+ const boundaryName = match[1]!;
34
+
35
+ const resolution = await resolvePrimitive('boundary', boundaryName, fromFile, projectRoot);
36
+ if (!resolution) {
37
+ Diagnostics.warn({
38
+ source: 'czap/vite.html-transform',
39
+ code: 'boundary-not-found',
40
+ message: `Boundary "${boundaryName}" could not be resolved for HTML transform.`,
41
+ detail: { fromFile },
42
+ });
43
+ continue;
44
+ }
45
+
46
+ const boundary = resolution.primitive;
47
+ const serialized = JSON.stringify({
48
+ id: boundary.id,
49
+ input: boundary.input,
50
+ thresholds: boundary.thresholds,
51
+ states: boundary.states,
52
+ hysteresis: boundary.hysteresis,
53
+ });
54
+
55
+ // Replace data-czap="name" with data-czap-boundary='...'
56
+ const replacement = `data-czap-boundary='${serialized.replace(/'/g, '&#39;')}'`;
57
+ result = result.replace(fullMatch, replacement);
58
+ }
59
+
60
+ return result;
61
+ }
package/src/index.ts ADDED
@@ -0,0 +1,71 @@
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
+
33
+ // Plugin
34
+ export type { PluginConfig } from './plugin.js';
35
+ export { plugin } from './plugin.js';
36
+ export { resolveWASM } from './wasm-resolve.js';
37
+ export type { WASMResolution } from './wasm-resolve.js';
38
+
39
+ // @quantize
40
+ export type { QuantizeBlock } from './css-quantize.js';
41
+ export { parseQuantizeBlocks, compileQuantizeBlock } from './css-quantize.js';
42
+
43
+ // @token
44
+ export type { TokenBlock } from './token-transform.js';
45
+ export { parseTokenBlocks, compileTokenBlock } from './token-transform.js';
46
+
47
+ // @theme
48
+ export type { ThemeBlock } from './theme-transform.js';
49
+ export { parseThemeBlocks, compileThemeBlock } from './theme-transform.js';
50
+
51
+ // @style
52
+ export type { StyleBlock } from './style-transform.js';
53
+ export { parseStyleBlocks, compileStyleBlock } from './style-transform.js';
54
+
55
+ // HTML transform
56
+ export { transformHTML } from './html-transform.js';
57
+
58
+ // Virtual modules
59
+ export type { VirtualModuleId } from './virtual-modules.js';
60
+ export { resolveVirtualId, isVirtualId, loadVirtualModule } from './virtual-modules.js';
61
+
62
+ // HMR
63
+ export type { HMRPayload } from './hmr.js';
64
+ export { handleHMR } from './hmr.js';
65
+
66
+ // Generic primitive resolution. `KIND_META` is intentionally not exported —
67
+ // it's the internal static lookup table that powers `resolvePrimitive`.
68
+ // Consumers building custom Vite plugin layers use `resolvePrimitive`;
69
+ // they don't need the internal config map.
70
+ export type { PrimitiveKind, PrimitiveResolution, PrimitiveShape } from './primitive-resolve.js';
71
+ export { resolvePrimitive } from './primitive-resolve.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Normalize CSS newlines to LF for line-based parsing of CSS at-rules.
3
+ *
4
+ * @module
5
+ */
6
+ export function normalizeCssLineEndings(css: string): string {
7
+ return css.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
8
+ }