@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.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/css-quantize.d.ts +53 -0
- package/dist/css-quantize.d.ts.map +1 -0
- package/dist/css-quantize.js +247 -0
- package/dist/css-quantize.js.map +1 -0
- package/dist/environments.d.ts +36 -0
- package/dist/environments.d.ts.map +1 -0
- package/dist/environments.js +67 -0
- package/dist/environments.js.map +1 -0
- package/dist/hmr.d.ts +37 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +84 -0
- package/dist/hmr.js.map +1 -0
- package/dist/html-transform.d.ts +19 -0
- package/dist/html-transform.d.ts.map +1 -0
- package/dist/html-transform.js +54 -0
- package/dist/html-transform.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize-css-eol.d.ts +7 -0
- package/dist/normalize-css-eol.d.ts.map +1 -0
- package/dist/normalize-css-eol.js +9 -0
- package/dist/normalize-css-eol.js.map +1 -0
- package/dist/plugin.d.ts +48 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +404 -0
- package/dist/plugin.js.map +1 -0
- package/dist/primitive-resolve.d.ts +56 -0
- package/dist/primitive-resolve.d.ts.map +1 -0
- package/dist/primitive-resolve.js +71 -0
- package/dist/primitive-resolve.js.map +1 -0
- package/dist/resolve-fs.d.ts +13 -0
- package/dist/resolve-fs.d.ts.map +1 -0
- package/dist/resolve-fs.js +80 -0
- package/dist/resolve-fs.js.map +1 -0
- package/dist/resolve-utils.d.ts +20 -0
- package/dist/resolve-utils.d.ts.map +1 -0
- package/dist/resolve-utils.js +45 -0
- package/dist/resolve-utils.js.map +1 -0
- package/dist/style-transform.d.ts +49 -0
- package/dist/style-transform.d.ts.map +1 -0
- package/dist/style-transform.js +122 -0
- package/dist/style-transform.js.map +1 -0
- package/dist/theme-transform.d.ts +44 -0
- package/dist/theme-transform.d.ts.map +1 -0
- package/dist/theme-transform.js +85 -0
- package/dist/theme-transform.js.map +1 -0
- package/dist/token-transform.d.ts +42 -0
- package/dist/token-transform.d.ts.map +1 -0
- package/dist/token-transform.js +84 -0
- package/dist/token-transform.js.map +1 -0
- package/dist/virtual-modules.d.ts +55 -0
- package/dist/virtual-modules.d.ts.map +1 -0
- package/dist/virtual-modules.js +141 -0
- package/dist/virtual-modules.js.map +1 -0
- package/dist/wasm-resolve.d.ts +25 -0
- package/dist/wasm-resolve.d.ts.map +1 -0
- package/dist/wasm-resolve.js +36 -0
- package/dist/wasm-resolve.js.map +1 -0
- package/package.json +63 -0
- package/src/css-quantize.ts +294 -0
- package/src/environments.ts +98 -0
- package/src/hmr.ts +121 -0
- package/src/html-transform.ts +61 -0
- package/src/index.ts +71 -0
- package/src/normalize-css-eol.ts +8 -0
- package/src/plugin.ts +492 -0
- package/src/primitive-resolve.ts +106 -0
- package/src/resolve-fs.ts +82 -0
- package/src/resolve-utils.ts +54 -0
- package/src/style-transform.ts +157 -0
- package/src/theme-transform.ts +119 -0
- package/src/token-transform.ts +117 -0
- package/src/virtual-modules.ts +160 -0
- 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, ''')}'`;
|
|
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';
|