@weave-framework/cli 0.2.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/bin/weave-dist.mjs +8 -0
- package/bin/weave.mjs +31 -0
- package/dist/build.d.ts +23 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +3086 -0
- package/dist/config.d.ts +71 -0
- package/dist/dev.d.ts +42 -0
- package/dist/entry.d.ts +36 -0
- package/dist/html.d.ts +20 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/routes.d.ts +11 -0
- package/dist/styles.d.ts +26 -0
- package/package.json +41 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3086 @@
|
|
|
1
|
+
// packages/cli/src/build.ts
|
|
2
|
+
import { build as esbuild } from "esbuild";
|
|
3
|
+
import { mkdir, writeFile, readFile as readFile3, rm, cp } from "node:fs/promises";
|
|
4
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
5
|
+
import { join as join2 } from "node:path";
|
|
6
|
+
|
|
7
|
+
// packages/cli/src/plugin.ts
|
|
8
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
// packages/compiler/src/parser.ts
|
|
13
|
+
var BLOCK_KW = /^@(if|else|for|empty|switch|case|default|let|defer|placeholder|await|then|catch|snippet|render|key)\b/;
|
|
14
|
+
var VOID = /* @__PURE__ */ new Set([
|
|
15
|
+
"area",
|
|
16
|
+
"base",
|
|
17
|
+
"br",
|
|
18
|
+
"col",
|
|
19
|
+
"embed",
|
|
20
|
+
"hr",
|
|
21
|
+
"img",
|
|
22
|
+
"input",
|
|
23
|
+
"link",
|
|
24
|
+
"meta",
|
|
25
|
+
"param",
|
|
26
|
+
"source",
|
|
27
|
+
"track",
|
|
28
|
+
"wbr"
|
|
29
|
+
]);
|
|
30
|
+
var ParseError = class extends Error {
|
|
31
|
+
};
|
|
32
|
+
function parseTemplate(input) {
|
|
33
|
+
const p = new Parser(input);
|
|
34
|
+
const nodes = p.parseChildren(null);
|
|
35
|
+
return nodes;
|
|
36
|
+
}
|
|
37
|
+
var Parser = class {
|
|
38
|
+
constructor(src) {
|
|
39
|
+
this.src = src;
|
|
40
|
+
}
|
|
41
|
+
pos = 0;
|
|
42
|
+
/** Inner start offset of the most recent {@link readParen} — for block-head expr offsets. */
|
|
43
|
+
parenStart = 0;
|
|
44
|
+
/** Offset of `sub`'s first non-whitespace char within `parent` (which starts at `parentStart`). */
|
|
45
|
+
exprOffset(parentStart, parent, sub) {
|
|
46
|
+
const at = parent.indexOf(sub);
|
|
47
|
+
return parentStart + (at < 0 ? 0 : at) + (sub.length - sub.trimStart().length);
|
|
48
|
+
}
|
|
49
|
+
eof() {
|
|
50
|
+
return this.pos >= this.src.length;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse children until `</closeTag>` (element), `}` (block body when
|
|
54
|
+
* `stopAtBrace`), or EOF. Does not consume the terminator.
|
|
55
|
+
*/
|
|
56
|
+
parseChildren(closeTag, stopAtBrace = false) {
|
|
57
|
+
const out = [];
|
|
58
|
+
while (!this.eof()) {
|
|
59
|
+
if (stopAtBrace && this.peek() === "}") return out;
|
|
60
|
+
if (this.src.startsWith("</", this.pos)) {
|
|
61
|
+
if (closeTag === null) throw new ParseError(`Unexpected closing tag at ${this.pos}`);
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
if (this.src.startsWith("{{", this.pos)) {
|
|
65
|
+
const it = this.readInterp();
|
|
66
|
+
out.push({ type: "interp", expr: it.expr, offset: it.offset });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (this.src.startsWith("<!--", this.pos)) {
|
|
70
|
+
this.skipComment();
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (this.peek() === "@" && BLOCK_KW.test(this.src.slice(this.pos))) {
|
|
74
|
+
out.push(this.parseBlock());
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (this.peek() === "<") {
|
|
78
|
+
out.push(this.parseElement());
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const text = this.readText(stopAtBrace);
|
|
82
|
+
if (text) {
|
|
83
|
+
const last = out[out.length - 1];
|
|
84
|
+
if (last && last.type === "text") last.value += text;
|
|
85
|
+
else out.push({ type: "text", value: text });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (closeTag !== null) throw new ParseError(`Unclosed <${closeTag}>`);
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/* ──────────── control-flow blocks ──────────── */
|
|
92
|
+
parseBlock() {
|
|
93
|
+
const kw = BLOCK_KW.exec(this.src.slice(this.pos))[1];
|
|
94
|
+
switch (kw) {
|
|
95
|
+
case "if":
|
|
96
|
+
return this.parseIf();
|
|
97
|
+
case "for":
|
|
98
|
+
return this.parseFor();
|
|
99
|
+
case "switch":
|
|
100
|
+
return this.parseSwitch();
|
|
101
|
+
case "let":
|
|
102
|
+
return this.parseLet();
|
|
103
|
+
case "defer":
|
|
104
|
+
return this.parseDefer();
|
|
105
|
+
case "await":
|
|
106
|
+
return this.parseAwait();
|
|
107
|
+
case "snippet":
|
|
108
|
+
return this.parseSnippet();
|
|
109
|
+
case "render":
|
|
110
|
+
return this.parseRender();
|
|
111
|
+
case "key":
|
|
112
|
+
return this.parseKey();
|
|
113
|
+
default:
|
|
114
|
+
throw new ParseError(`Unexpected @${kw} at ${this.pos} (no matching block)`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
parseIf() {
|
|
118
|
+
this.pos += 3;
|
|
119
|
+
this.skipWs();
|
|
120
|
+
const head = this.readParen();
|
|
121
|
+
const headStart = this.parenStart;
|
|
122
|
+
const branches = [];
|
|
123
|
+
let cond = head;
|
|
124
|
+
let alias;
|
|
125
|
+
const semi = splitTopLevel(head, ";");
|
|
126
|
+
if (semi.length === 2) {
|
|
127
|
+
cond = semi[0].trim();
|
|
128
|
+
const m = /^as\s+([A-Za-z_$][\w$]*)$/.exec(semi[1].trim());
|
|
129
|
+
if (!m) throw new ParseError(`Expected 'as <name>' in @if, got '${semi[1].trim()}'`);
|
|
130
|
+
alias = m[1];
|
|
131
|
+
}
|
|
132
|
+
const condOffset = this.exprOffset(headStart, head, cond);
|
|
133
|
+
branches.push({ cond, condOffset, alias, children: this.readBlockBody() });
|
|
134
|
+
for (; ; ) {
|
|
135
|
+
const save = this.pos;
|
|
136
|
+
this.skipWs();
|
|
137
|
+
if (this.src.startsWith("@else if", this.pos)) {
|
|
138
|
+
this.pos += "@else if".length;
|
|
139
|
+
this.skipWs();
|
|
140
|
+
const raw = this.readParen();
|
|
141
|
+
const off = this.exprOffset(this.parenStart, raw, raw.trim());
|
|
142
|
+
branches.push({ cond: raw.trim(), condOffset: off, children: this.readBlockBody() });
|
|
143
|
+
} else if (this.src.startsWith("@else", this.pos)) {
|
|
144
|
+
this.pos += "@else".length;
|
|
145
|
+
branches.push({ children: this.readBlockBody() });
|
|
146
|
+
break;
|
|
147
|
+
} else {
|
|
148
|
+
this.pos = save;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return { type: "if", branches };
|
|
153
|
+
}
|
|
154
|
+
parseFor() {
|
|
155
|
+
this.pos += 4;
|
|
156
|
+
this.skipWs();
|
|
157
|
+
const head = this.readParen();
|
|
158
|
+
const headStart = this.parenStart;
|
|
159
|
+
const parts = splitTopLevel(head, ";").map((s) => s.trim());
|
|
160
|
+
const m = /^([A-Za-z_$][\w$]*)\s+of\s+([\s\S]+)$/.exec(parts[0]);
|
|
161
|
+
if (!m) throw new ParseError(`Expected '@for (item of list)', got '${parts[0]}'`);
|
|
162
|
+
const item = m[1];
|
|
163
|
+
const list = m[2].trim();
|
|
164
|
+
let track;
|
|
165
|
+
for (const extra of parts.slice(1)) {
|
|
166
|
+
const t = /^track\s+([\s\S]+)$/.exec(extra);
|
|
167
|
+
if (t) track = t[1].trim();
|
|
168
|
+
}
|
|
169
|
+
const listOffset = this.exprOffset(headStart, head, list);
|
|
170
|
+
const trackOffset = track ? this.exprOffset(headStart, head, track) : void 0;
|
|
171
|
+
const children = this.readBlockBody();
|
|
172
|
+
let empty;
|
|
173
|
+
const save = this.pos;
|
|
174
|
+
this.skipWs();
|
|
175
|
+
if (this.src.startsWith("@empty", this.pos)) {
|
|
176
|
+
this.pos += "@empty".length;
|
|
177
|
+
empty = this.readBlockBody();
|
|
178
|
+
} else {
|
|
179
|
+
this.pos = save;
|
|
180
|
+
}
|
|
181
|
+
return { type: "for", item, list, listOffset, track, trackOffset, children, empty };
|
|
182
|
+
}
|
|
183
|
+
parseSwitch() {
|
|
184
|
+
this.pos += 7;
|
|
185
|
+
this.skipWs();
|
|
186
|
+
const rawExpr = this.readParen();
|
|
187
|
+
const exprOffset = this.exprOffset(this.parenStart, rawExpr, rawExpr.trim());
|
|
188
|
+
const expr = rawExpr.trim();
|
|
189
|
+
this.skipWs();
|
|
190
|
+
if (this.peek() !== "{") throw new ParseError(`Expected '{' after @switch at ${this.pos}`);
|
|
191
|
+
this.pos++;
|
|
192
|
+
const cases = [];
|
|
193
|
+
for (; ; ) {
|
|
194
|
+
this.skipWs();
|
|
195
|
+
if (this.peek() === "}") {
|
|
196
|
+
this.pos++;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
if (this.src.startsWith("@case", this.pos)) {
|
|
200
|
+
this.pos += "@case".length;
|
|
201
|
+
this.skipWs();
|
|
202
|
+
const rawTest = this.readParen();
|
|
203
|
+
const testOffset = this.exprOffset(this.parenStart, rawTest, rawTest.trim());
|
|
204
|
+
cases.push({ test: rawTest.trim(), testOffset, children: this.readBlockBody() });
|
|
205
|
+
} else if (this.src.startsWith("@default", this.pos)) {
|
|
206
|
+
this.pos += "@default".length;
|
|
207
|
+
cases.push({ children: this.readBlockBody() });
|
|
208
|
+
} else {
|
|
209
|
+
throw new ParseError(`Expected @case/@default or '}' in @switch at ${this.pos}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { type: "switch", expr, exprOffset, cases };
|
|
213
|
+
}
|
|
214
|
+
parseLet() {
|
|
215
|
+
this.pos += 4;
|
|
216
|
+
this.skipWs();
|
|
217
|
+
const name = this.readName();
|
|
218
|
+
if (!name) throw new ParseError(`Expected name after @let at ${this.pos}`);
|
|
219
|
+
this.skipWs();
|
|
220
|
+
if (this.peek() !== "=") throw new ParseError(`Expected '=' in @let ${name}`);
|
|
221
|
+
this.pos++;
|
|
222
|
+
const rawStart = this.pos;
|
|
223
|
+
const raw = this.readUntilSemicolon();
|
|
224
|
+
const expr = raw.trim();
|
|
225
|
+
const exprOffset = rawStart + (raw.length - raw.trimStart().length);
|
|
226
|
+
if (this.peek() !== ";") throw new ParseError(`Expected ';' ending @let ${name}`);
|
|
227
|
+
this.pos++;
|
|
228
|
+
return { type: "let", name, expr, exprOffset };
|
|
229
|
+
}
|
|
230
|
+
parseDefer() {
|
|
231
|
+
this.pos += 6;
|
|
232
|
+
this.skipWs();
|
|
233
|
+
const head = this.readParen();
|
|
234
|
+
const trigger = this.parseDeferTrigger(head, this.parenStart);
|
|
235
|
+
const children = this.readBlockBody();
|
|
236
|
+
let placeholder;
|
|
237
|
+
const save = this.pos;
|
|
238
|
+
this.skipWs();
|
|
239
|
+
if (this.src.startsWith("@placeholder", this.pos)) {
|
|
240
|
+
this.pos += "@placeholder".length;
|
|
241
|
+
placeholder = this.readBlockBody();
|
|
242
|
+
} else {
|
|
243
|
+
this.pos = save;
|
|
244
|
+
}
|
|
245
|
+
return { type: "defer", trigger, children, placeholder };
|
|
246
|
+
}
|
|
247
|
+
parseAwait() {
|
|
248
|
+
this.pos += 6;
|
|
249
|
+
this.skipWs();
|
|
250
|
+
const rawExpr = this.readParen();
|
|
251
|
+
const expr = rawExpr.trim();
|
|
252
|
+
const exprOffset = this.exprOffset(this.parenStart, rawExpr, expr);
|
|
253
|
+
let pending;
|
|
254
|
+
const save = this.pos;
|
|
255
|
+
this.skipWs();
|
|
256
|
+
if (this.peek() === "{") {
|
|
257
|
+
this.pos = save;
|
|
258
|
+
pending = this.readBlockBody();
|
|
259
|
+
} else {
|
|
260
|
+
this.pos = save;
|
|
261
|
+
}
|
|
262
|
+
const branch = (kw) => {
|
|
263
|
+
const s = this.pos;
|
|
264
|
+
this.skipWs();
|
|
265
|
+
if (this.src.startsWith(kw, this.pos)) {
|
|
266
|
+
this.pos += kw.length;
|
|
267
|
+
const alias = this.maybeAlias();
|
|
268
|
+
return { alias, children: this.readBlockBody() };
|
|
269
|
+
}
|
|
270
|
+
this.pos = s;
|
|
271
|
+
return void 0;
|
|
272
|
+
};
|
|
273
|
+
const thenBranch = branch("@then");
|
|
274
|
+
const catchBranch = branch("@catch");
|
|
275
|
+
return { type: "await", expr, exprOffset, pending, then: thenBranch, catch: catchBranch };
|
|
276
|
+
}
|
|
277
|
+
parseSnippet() {
|
|
278
|
+
this.pos += "@snippet".length;
|
|
279
|
+
this.skipWs();
|
|
280
|
+
const name = this.readIdent();
|
|
281
|
+
if (!name) throw new ParseError(`Expected a snippet name after @snippet at ${this.pos}`);
|
|
282
|
+
this.skipWs();
|
|
283
|
+
if (this.peek() !== "(") throw new ParseError(`Expected '(' after @snippet ${name}`);
|
|
284
|
+
const rawParams = this.readParen();
|
|
285
|
+
const params = splitTopLevel(rawParams, ",").map((s) => s.trim()).filter(Boolean);
|
|
286
|
+
for (const p of params) {
|
|
287
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(p)) {
|
|
288
|
+
throw new ParseError(`Invalid @snippet parameter '${p}' (identifiers only)`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const children = this.readBlockBody();
|
|
292
|
+
return { type: "snippet", name, params, children };
|
|
293
|
+
}
|
|
294
|
+
parseKey() {
|
|
295
|
+
this.pos += "@key".length;
|
|
296
|
+
this.skipWs();
|
|
297
|
+
const raw = this.readParen();
|
|
298
|
+
const expr = raw.trim();
|
|
299
|
+
if (!expr) throw new ParseError(`@key () needs an expression`);
|
|
300
|
+
const exprOffset = this.exprOffset(this.parenStart, raw, expr);
|
|
301
|
+
return { type: "key", expr, exprOffset, children: this.readBlockBody() };
|
|
302
|
+
}
|
|
303
|
+
parseRender() {
|
|
304
|
+
this.pos += "@render".length;
|
|
305
|
+
this.skipWs();
|
|
306
|
+
if (this.peek() !== "(") throw new ParseError(`Expected '(' after @render at ${this.pos}`);
|
|
307
|
+
const raw = this.readParen();
|
|
308
|
+
const expr = raw.trim();
|
|
309
|
+
if (!expr) throw new ParseError(`@render () needs an expression`);
|
|
310
|
+
return { type: "render", expr, exprOffset: this.exprOffset(this.parenStart, raw, expr) };
|
|
311
|
+
}
|
|
312
|
+
/** Read a JS identifier (`[A-Za-z_$][\w$]*`); '' if none at the cursor. */
|
|
313
|
+
readIdent() {
|
|
314
|
+
const start = this.pos;
|
|
315
|
+
if (this.eof() || !/[A-Za-z_$]/.test(this.peek())) return "";
|
|
316
|
+
this.pos++;
|
|
317
|
+
while (!this.eof() && /[\w$]/.test(this.peek())) this.pos++;
|
|
318
|
+
return this.src.slice(start, this.pos);
|
|
319
|
+
}
|
|
320
|
+
/** Optional `(name)` alias after `@then`/`@catch`. */
|
|
321
|
+
maybeAlias() {
|
|
322
|
+
const save = this.pos;
|
|
323
|
+
this.skipWs();
|
|
324
|
+
if (this.peek() === "(") {
|
|
325
|
+
const inner = this.readParen().trim();
|
|
326
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(inner)) {
|
|
327
|
+
throw new ParseError(`Expected an identifier alias in @then/@catch, got '${inner}'`);
|
|
328
|
+
}
|
|
329
|
+
return inner;
|
|
330
|
+
}
|
|
331
|
+
this.pos = save;
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
parseDeferTrigger(head, headStart) {
|
|
335
|
+
const h = head.trim();
|
|
336
|
+
const whenM = /^when\s+([\s\S]+)$/.exec(h);
|
|
337
|
+
if (whenM) {
|
|
338
|
+
const expr = whenM[1].trim();
|
|
339
|
+
return { kind: "when", expr, exprOffset: this.exprOffset(headStart, head, expr) };
|
|
340
|
+
}
|
|
341
|
+
if (h === "immediate") return { kind: "immediate" };
|
|
342
|
+
const onM = /^on\s+([\s\S]+)$/.exec(h);
|
|
343
|
+
if (onM) {
|
|
344
|
+
const on = onM[1].trim();
|
|
345
|
+
const timerM = /^timer\s*\(\s*([\s\S]+?)\s*\)$/.exec(on);
|
|
346
|
+
if (timerM) {
|
|
347
|
+
const ms = timerM[1].trim();
|
|
348
|
+
return { kind: "timer", ms, msOffset: this.exprOffset(headStart, head, ms) };
|
|
349
|
+
}
|
|
350
|
+
if (on === "idle") return { kind: "idle" };
|
|
351
|
+
if (on === "viewport") return { kind: "viewport" };
|
|
352
|
+
if (on === "interaction") return { kind: "interaction" };
|
|
353
|
+
if (on === "hover") return { kind: "hover" };
|
|
354
|
+
throw new ParseError(`Unknown @defer trigger 'on ${on}'`);
|
|
355
|
+
}
|
|
356
|
+
throw new ParseError(`Invalid @defer trigger '${h}' (use 'when <expr>', 'on idle|viewport|interaction|hover', 'on timer(ms)', or 'immediate')`);
|
|
357
|
+
}
|
|
358
|
+
/** `{ children }` */
|
|
359
|
+
readBlockBody() {
|
|
360
|
+
this.skipWs();
|
|
361
|
+
if (this.peek() !== "{") throw new ParseError(`Expected '{' at ${this.pos}`);
|
|
362
|
+
this.pos++;
|
|
363
|
+
const children = this.parseChildren(null, true);
|
|
364
|
+
if (this.peek() !== "}") throw new ParseError(`Expected '}' closing block at ${this.pos}`);
|
|
365
|
+
this.pos++;
|
|
366
|
+
return children;
|
|
367
|
+
}
|
|
368
|
+
/** Read a balanced `( … )` and return the inner text (parens consumed). */
|
|
369
|
+
readParen() {
|
|
370
|
+
if (this.peek() !== "(") throw new ParseError(`Expected '(' at ${this.pos}`);
|
|
371
|
+
this.pos++;
|
|
372
|
+
const start = this.pos;
|
|
373
|
+
let depth = 1;
|
|
374
|
+
while (!this.eof()) {
|
|
375
|
+
const c = this.peek();
|
|
376
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
377
|
+
this.skipString(c);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (c === "(") depth++;
|
|
381
|
+
else if (c === ")") {
|
|
382
|
+
depth--;
|
|
383
|
+
if (depth === 0) break;
|
|
384
|
+
}
|
|
385
|
+
this.pos++;
|
|
386
|
+
}
|
|
387
|
+
if (depth !== 0) throw new ParseError("Unclosed ( in block head");
|
|
388
|
+
const inner = this.src.slice(start, this.pos);
|
|
389
|
+
this.parenStart = start;
|
|
390
|
+
this.pos++;
|
|
391
|
+
return inner;
|
|
392
|
+
}
|
|
393
|
+
/** Read an expression up to a top-level `;` (for @let). */
|
|
394
|
+
readUntilSemicolon() {
|
|
395
|
+
const start = this.pos;
|
|
396
|
+
let depth = 0;
|
|
397
|
+
while (!this.eof()) {
|
|
398
|
+
const c = this.peek();
|
|
399
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
400
|
+
this.skipString(c);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
404
|
+
else if (c === ")" || c === "]" || c === "}") depth--;
|
|
405
|
+
else if (c === ";" && depth === 0) break;
|
|
406
|
+
this.pos++;
|
|
407
|
+
}
|
|
408
|
+
return this.src.slice(start, this.pos);
|
|
409
|
+
}
|
|
410
|
+
peek() {
|
|
411
|
+
return this.src[this.pos];
|
|
412
|
+
}
|
|
413
|
+
readInterp() {
|
|
414
|
+
this.pos += 2;
|
|
415
|
+
const start = this.pos;
|
|
416
|
+
const end = this.src.indexOf("}}", this.pos);
|
|
417
|
+
if (end === -1) throw new ParseError("Unclosed {{ interpolation");
|
|
418
|
+
this.pos = end + 2;
|
|
419
|
+
const raw = this.src.slice(start, end);
|
|
420
|
+
return { expr: raw.trim(), offset: start + (raw.length - raw.trimStart().length) };
|
|
421
|
+
}
|
|
422
|
+
skipComment() {
|
|
423
|
+
const end = this.src.indexOf("-->", this.pos);
|
|
424
|
+
this.pos = end === -1 ? this.src.length : end + 3;
|
|
425
|
+
}
|
|
426
|
+
readText(stopAtBrace) {
|
|
427
|
+
let out = "";
|
|
428
|
+
while (!this.eof()) {
|
|
429
|
+
const c = this.peek();
|
|
430
|
+
if (c === "<" || this.src.startsWith("{{", this.pos)) break;
|
|
431
|
+
if (stopAtBrace && c === "}") break;
|
|
432
|
+
if (c === "@") {
|
|
433
|
+
if (this.src[this.pos + 1] === "@") {
|
|
434
|
+
out += "@";
|
|
435
|
+
this.pos += 2;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (BLOCK_KW.test(this.src.slice(this.pos))) break;
|
|
439
|
+
}
|
|
440
|
+
out += c;
|
|
441
|
+
this.pos++;
|
|
442
|
+
}
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
parseElement() {
|
|
446
|
+
this.pos++;
|
|
447
|
+
const tagOffset = this.pos;
|
|
448
|
+
const tag = this.readName();
|
|
449
|
+
if (!tag) throw new ParseError(`Expected tag name at ${this.pos}`);
|
|
450
|
+
const attrs = this.parseAttrs();
|
|
451
|
+
this.skipWs();
|
|
452
|
+
let selfClosing = false;
|
|
453
|
+
if (this.peek() === "/") {
|
|
454
|
+
selfClosing = true;
|
|
455
|
+
this.pos++;
|
|
456
|
+
}
|
|
457
|
+
if (this.peek() !== ">") throw new ParseError(`Expected '>' for <${tag}> at ${this.pos}`);
|
|
458
|
+
this.pos++;
|
|
459
|
+
const isVoid = !/^[A-Z]/.test(tag) && VOID.has(tag.toLowerCase());
|
|
460
|
+
if (selfClosing || isVoid) {
|
|
461
|
+
return { type: "element", tag, tagOffset, attrs, children: [], selfClosing: true };
|
|
462
|
+
}
|
|
463
|
+
const children = this.parseChildren(tag);
|
|
464
|
+
if (!this.src.startsWith("</", this.pos)) throw new ParseError(`Unclosed <${tag}>`);
|
|
465
|
+
this.pos += 2;
|
|
466
|
+
const closeName = this.readName();
|
|
467
|
+
if (closeName !== tag) throw new ParseError(`Mismatched </${closeName}>, expected </${tag}>`);
|
|
468
|
+
this.skipWs();
|
|
469
|
+
if (this.peek() !== ">") throw new ParseError(`Expected '>' closing </${tag}>`);
|
|
470
|
+
this.pos++;
|
|
471
|
+
return { type: "element", tag, tagOffset, attrs, children, selfClosing: false };
|
|
472
|
+
}
|
|
473
|
+
parseAttrs() {
|
|
474
|
+
const attrs = [];
|
|
475
|
+
while (!this.eof()) {
|
|
476
|
+
this.skipWs();
|
|
477
|
+
const c = this.peek();
|
|
478
|
+
if (c === ">" || c === "/" || c === void 0) break;
|
|
479
|
+
attrs.push(this.parseAttr());
|
|
480
|
+
}
|
|
481
|
+
return attrs;
|
|
482
|
+
}
|
|
483
|
+
parseAttr() {
|
|
484
|
+
const nameStart = this.pos;
|
|
485
|
+
const rawName = this.readAttrName();
|
|
486
|
+
let value = null;
|
|
487
|
+
if (this.peek() === "=") {
|
|
488
|
+
this.pos++;
|
|
489
|
+
value = this.readAttrValue();
|
|
490
|
+
}
|
|
491
|
+
const attr = this.classifyAttr(rawName, value);
|
|
492
|
+
if (attr.type === "use") attr.nameOffset = nameStart + "use:".length;
|
|
493
|
+
if (attr.type === "transition") {
|
|
494
|
+
const prefix = attr.mode === "both" ? "transition:" : attr.mode === "in" ? "in:" : "out:";
|
|
495
|
+
attr.nameOffset = nameStart + prefix.length;
|
|
496
|
+
}
|
|
497
|
+
return attr;
|
|
498
|
+
}
|
|
499
|
+
classifyAttr(rawName, value) {
|
|
500
|
+
const exprOf = () => {
|
|
501
|
+
if (!value) throw new ParseError(`Binding '${rawName}' requires a value`);
|
|
502
|
+
if (value.kind !== "expr") throw new ParseError(`Binding '${rawName}' needs {expr}, got a string`);
|
|
503
|
+
return value.expr;
|
|
504
|
+
};
|
|
505
|
+
const offset = value && value.kind === "expr" ? value.offset : void 0;
|
|
506
|
+
if (rawName === "ref" || rawName === "bind:this") {
|
|
507
|
+
return { type: "ref", expr: exprOf(), offset };
|
|
508
|
+
}
|
|
509
|
+
if (rawName.startsWith("on:")) {
|
|
510
|
+
const [name, ...modifiers] = rawName.slice(3).split("|");
|
|
511
|
+
return { type: "event", name, modifiers, expr: exprOf(), offset };
|
|
512
|
+
}
|
|
513
|
+
if (rawName.startsWith("class:")) {
|
|
514
|
+
return { type: "class", name: rawName.slice(6), expr: exprOf(), offset };
|
|
515
|
+
}
|
|
516
|
+
if (rawName.startsWith("bind:")) {
|
|
517
|
+
return { type: "bind", name: rawName.slice(5), expr: exprOf(), offset };
|
|
518
|
+
}
|
|
519
|
+
if (rawName.startsWith("use:")) {
|
|
520
|
+
const name = rawName.slice(4);
|
|
521
|
+
if (!name) throw new ParseError(`'use:' requires an action name, e.g. use:tooltip`);
|
|
522
|
+
const expr = value && value.kind === "expr" ? value.expr : void 0;
|
|
523
|
+
if (value && value.kind === "static") {
|
|
524
|
+
throw new ParseError(`use:${name} needs {expr}, got a string`);
|
|
525
|
+
}
|
|
526
|
+
return { type: "use", name, expr, offset };
|
|
527
|
+
}
|
|
528
|
+
if (rawName === "show") {
|
|
529
|
+
return { type: "show", expr: exprOf(), offset };
|
|
530
|
+
}
|
|
531
|
+
if (rawName.startsWith("transition:") || rawName.startsWith("in:") || rawName.startsWith("out:")) {
|
|
532
|
+
const mode = rawName.startsWith("transition:") ? "both" : rawName.startsWith("in:") ? "in" : "out";
|
|
533
|
+
const prefix = mode === "both" ? "transition:" : mode === "in" ? "in:" : "out:";
|
|
534
|
+
const name = rawName.slice(prefix.length);
|
|
535
|
+
if (!name) throw new ParseError(`'${prefix}' requires a transition function, e.g. ${prefix}fade`);
|
|
536
|
+
if (value && value.kind === "static") throw new ParseError(`${rawName} needs {expr} params, got a string`);
|
|
537
|
+
const expr = value && value.kind === "expr" ? value.expr : void 0;
|
|
538
|
+
return { type: "transition", name, mode, expr, offset };
|
|
539
|
+
}
|
|
540
|
+
if (rawName.startsWith(".")) {
|
|
541
|
+
return { type: "prop", name: rawName.slice(1), expr: exprOf(), offset };
|
|
542
|
+
}
|
|
543
|
+
if (value && value.kind === "expr") {
|
|
544
|
+
return { type: "attr", name: rawName, expr: value.expr, offset };
|
|
545
|
+
}
|
|
546
|
+
return { type: "static", name: rawName, value: value ? value.text : "" };
|
|
547
|
+
}
|
|
548
|
+
readName() {
|
|
549
|
+
const start = this.pos;
|
|
550
|
+
while (!this.eof() && /[A-Za-z0-9\-:]/.test(this.peek())) this.pos++;
|
|
551
|
+
return this.src.slice(start, this.pos);
|
|
552
|
+
}
|
|
553
|
+
readAttrName() {
|
|
554
|
+
const start = this.pos;
|
|
555
|
+
while (!this.eof() && /[A-Za-z0-9_\-:.|@]/.test(this.peek())) this.pos++;
|
|
556
|
+
return this.src.slice(start, this.pos);
|
|
557
|
+
}
|
|
558
|
+
readAttrValue() {
|
|
559
|
+
const c = this.peek();
|
|
560
|
+
if (c === "{") {
|
|
561
|
+
if (!this.src.startsWith("{{", this.pos)) {
|
|
562
|
+
throw new ParseError(`Attribute bindings use double braces: write {{ expr }}, not { expr } (at ${this.pos})`);
|
|
563
|
+
}
|
|
564
|
+
const b = this.readDoubleBracedExpr();
|
|
565
|
+
return { kind: "expr", expr: b.expr, offset: b.offset };
|
|
566
|
+
}
|
|
567
|
+
if (c === '"' || c === "'") return { kind: "static", text: this.readQuoted(c) };
|
|
568
|
+
const start = this.pos;
|
|
569
|
+
while (!this.eof() && !/[\s>/]/.test(this.peek())) this.pos++;
|
|
570
|
+
return { kind: "static", text: this.src.slice(start, this.pos) };
|
|
571
|
+
}
|
|
572
|
+
readQuoted(quote) {
|
|
573
|
+
this.pos++;
|
|
574
|
+
const start = this.pos;
|
|
575
|
+
while (!this.eof() && this.peek() !== quote) this.pos++;
|
|
576
|
+
const text = this.src.slice(start, this.pos);
|
|
577
|
+
this.pos++;
|
|
578
|
+
return text;
|
|
579
|
+
}
|
|
580
|
+
/** Read a `{{ ... }}` expression, balancing inner braces and skipping string literals. */
|
|
581
|
+
readDoubleBracedExpr() {
|
|
582
|
+
this.pos += 2;
|
|
583
|
+
const start = this.pos;
|
|
584
|
+
let depth = 0;
|
|
585
|
+
while (!this.eof()) {
|
|
586
|
+
const c = this.peek();
|
|
587
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
588
|
+
this.skipString(c);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (c === "{") depth++;
|
|
592
|
+
else if (c === "}") {
|
|
593
|
+
if (depth > 0) depth--;
|
|
594
|
+
else if (this.src[this.pos + 1] === "}") break;
|
|
595
|
+
}
|
|
596
|
+
this.pos++;
|
|
597
|
+
}
|
|
598
|
+
if (this.eof()) throw new ParseError("Unclosed {{ expression");
|
|
599
|
+
const raw = this.src.slice(start, this.pos);
|
|
600
|
+
this.pos += 2;
|
|
601
|
+
return { expr: raw.trim(), offset: start + (raw.length - raw.trimStart().length) };
|
|
602
|
+
}
|
|
603
|
+
skipString(quote) {
|
|
604
|
+
this.pos++;
|
|
605
|
+
while (!this.eof()) {
|
|
606
|
+
const c = this.peek();
|
|
607
|
+
if (c === "\\") {
|
|
608
|
+
this.pos += 2;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
this.pos++;
|
|
612
|
+
if (c === quote) return;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
skipWs() {
|
|
616
|
+
while (!this.eof() && /\s/.test(this.peek())) this.pos++;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
function splitTopLevel(s, sep4) {
|
|
620
|
+
const out = [];
|
|
621
|
+
let depth = 0;
|
|
622
|
+
let last = 0;
|
|
623
|
+
for (let i = 0; i < s.length; i++) {
|
|
624
|
+
const c = s[i];
|
|
625
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
626
|
+
i = skipStr(s, i);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (c === "(" || c === "[" || c === "{") depth++;
|
|
630
|
+
else if (c === ")" || c === "]" || c === "}") depth--;
|
|
631
|
+
else if (c === sep4 && depth === 0) {
|
|
632
|
+
out.push(s.slice(last, i));
|
|
633
|
+
last = i + 1;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
out.push(s.slice(last));
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
function skipStr(s, start) {
|
|
640
|
+
const q2 = s[start];
|
|
641
|
+
let i = start + 1;
|
|
642
|
+
while (i < s.length) {
|
|
643
|
+
if (s[i] === "\\") {
|
|
644
|
+
i += 2;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (s[i] === q2) return i;
|
|
648
|
+
i++;
|
|
649
|
+
}
|
|
650
|
+
return s.length;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// packages/compiler/src/scope.ts
|
|
654
|
+
var ID_START = /[A-Za-z_$]/;
|
|
655
|
+
var ID_CHAR = /[A-Za-z0-9_$]/;
|
|
656
|
+
function rewrite(expr, scope, ctxRef = "ctx") {
|
|
657
|
+
let out = "";
|
|
658
|
+
let reactive = false;
|
|
659
|
+
let i = 0;
|
|
660
|
+
const n = expr.length;
|
|
661
|
+
const segments = [];
|
|
662
|
+
let runSrc = -1;
|
|
663
|
+
let runGen = -1;
|
|
664
|
+
let runLen = 0;
|
|
665
|
+
const flush = () => {
|
|
666
|
+
if (runLen > 0) segments.push({ src: runSrc, gen: runGen, len: runLen });
|
|
667
|
+
runLen = 0;
|
|
668
|
+
};
|
|
669
|
+
const copy = (srcPos, text) => {
|
|
670
|
+
const genPos = out.length;
|
|
671
|
+
if (runLen > 0 && runSrc + runLen === srcPos && runGen + runLen === genPos) {
|
|
672
|
+
runLen += text.length;
|
|
673
|
+
} else {
|
|
674
|
+
flush();
|
|
675
|
+
runSrc = srcPos;
|
|
676
|
+
runGen = genPos;
|
|
677
|
+
runLen = text.length;
|
|
678
|
+
}
|
|
679
|
+
out += text;
|
|
680
|
+
};
|
|
681
|
+
const insert = (text) => {
|
|
682
|
+
flush();
|
|
683
|
+
out += text;
|
|
684
|
+
};
|
|
685
|
+
while (i < n) {
|
|
686
|
+
const c = expr[i];
|
|
687
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
688
|
+
const end = scanString(expr, i);
|
|
689
|
+
copy(i, expr.slice(i, end));
|
|
690
|
+
i = end;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (ID_START.test(c)) {
|
|
694
|
+
let j = i + 1;
|
|
695
|
+
while (j < n && ID_CHAR.test(expr[j])) j++;
|
|
696
|
+
const name = expr.slice(i, j);
|
|
697
|
+
const isProperty = lastNonSpace(out) === ".";
|
|
698
|
+
const binding = scope.get(name);
|
|
699
|
+
if (binding && !isProperty) {
|
|
700
|
+
if (binding.kind === "ctx") {
|
|
701
|
+
insert(`${ctxRef}.`);
|
|
702
|
+
copy(i, name);
|
|
703
|
+
} else if (binding.kind === "local") {
|
|
704
|
+
copy(i, name);
|
|
705
|
+
} else {
|
|
706
|
+
insert(`${binding.accessor}()`);
|
|
707
|
+
}
|
|
708
|
+
reactive = true;
|
|
709
|
+
} else {
|
|
710
|
+
copy(i, name);
|
|
711
|
+
}
|
|
712
|
+
i = j;
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
copy(i, c);
|
|
716
|
+
i++;
|
|
717
|
+
}
|
|
718
|
+
flush();
|
|
719
|
+
return { code: out, reactive, segments };
|
|
720
|
+
}
|
|
721
|
+
function ctxScope(names) {
|
|
722
|
+
const scope = /* @__PURE__ */ new Map();
|
|
723
|
+
for (const name of names) scope.set(name, { kind: "ctx" });
|
|
724
|
+
return scope;
|
|
725
|
+
}
|
|
726
|
+
function childScope(parent, locals) {
|
|
727
|
+
const scope = new Map(parent);
|
|
728
|
+
for (const [name, accessor] of Object.entries(locals)) {
|
|
729
|
+
scope.set(name, { kind: "call", accessor });
|
|
730
|
+
}
|
|
731
|
+
return scope;
|
|
732
|
+
}
|
|
733
|
+
function lastNonSpace(s) {
|
|
734
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
735
|
+
if (!/\s/.test(s[i])) return s[i];
|
|
736
|
+
}
|
|
737
|
+
return "";
|
|
738
|
+
}
|
|
739
|
+
var NON_CTX = /* @__PURE__ */ new Set([
|
|
740
|
+
// literals / keywords
|
|
741
|
+
"true",
|
|
742
|
+
"false",
|
|
743
|
+
"null",
|
|
744
|
+
"undefined",
|
|
745
|
+
"this",
|
|
746
|
+
"NaN",
|
|
747
|
+
"Infinity",
|
|
748
|
+
"typeof",
|
|
749
|
+
"instanceof",
|
|
750
|
+
"in",
|
|
751
|
+
"of",
|
|
752
|
+
"new",
|
|
753
|
+
"void",
|
|
754
|
+
"delete",
|
|
755
|
+
"await",
|
|
756
|
+
"yield",
|
|
757
|
+
"return",
|
|
758
|
+
"function",
|
|
759
|
+
"class",
|
|
760
|
+
"super",
|
|
761
|
+
"if",
|
|
762
|
+
"else",
|
|
763
|
+
"switch",
|
|
764
|
+
"case",
|
|
765
|
+
// built-in objects / functions
|
|
766
|
+
"Math",
|
|
767
|
+
"JSON",
|
|
768
|
+
"Object",
|
|
769
|
+
"Array",
|
|
770
|
+
"String",
|
|
771
|
+
"Number",
|
|
772
|
+
"Boolean",
|
|
773
|
+
"Date",
|
|
774
|
+
"RegExp",
|
|
775
|
+
"Map",
|
|
776
|
+
"Set",
|
|
777
|
+
"WeakMap",
|
|
778
|
+
"WeakSet",
|
|
779
|
+
"Symbol",
|
|
780
|
+
"Promise",
|
|
781
|
+
"BigInt",
|
|
782
|
+
"Error",
|
|
783
|
+
"TypeError",
|
|
784
|
+
"Intl",
|
|
785
|
+
"console",
|
|
786
|
+
"window",
|
|
787
|
+
"document",
|
|
788
|
+
"globalThis",
|
|
789
|
+
"parseInt",
|
|
790
|
+
"parseFloat",
|
|
791
|
+
"isNaN",
|
|
792
|
+
"isFinite",
|
|
793
|
+
"structuredClone",
|
|
794
|
+
"encodeURIComponent",
|
|
795
|
+
"decodeURIComponent",
|
|
796
|
+
"encodeURI",
|
|
797
|
+
"decodeURI",
|
|
798
|
+
"navigator",
|
|
799
|
+
"location",
|
|
800
|
+
"history",
|
|
801
|
+
"localStorage",
|
|
802
|
+
"sessionStorage",
|
|
803
|
+
"fetch",
|
|
804
|
+
"URL",
|
|
805
|
+
"URLSearchParams"
|
|
806
|
+
]);
|
|
807
|
+
function arrowParams(expr) {
|
|
808
|
+
const params = /* @__PURE__ */ new Set();
|
|
809
|
+
const ID = /^[A-Za-z_$][\w$]*$/;
|
|
810
|
+
let at = expr.indexOf("=>");
|
|
811
|
+
while (at !== -1) {
|
|
812
|
+
let k = at - 1;
|
|
813
|
+
while (k >= 0 && /\s/.test(expr[k])) k--;
|
|
814
|
+
if (expr[k] === ")") {
|
|
815
|
+
let depth = 1;
|
|
816
|
+
let m = k - 1;
|
|
817
|
+
for (; m >= 0; m--) {
|
|
818
|
+
if (expr[m] === ")") depth++;
|
|
819
|
+
else if (expr[m] === "(") {
|
|
820
|
+
depth--;
|
|
821
|
+
if (depth === 0) break;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
for (const raw of expr.slice(m + 1, k).split(",")) {
|
|
825
|
+
const name = raw.trim().split(/[\s=:]/)[0].replace(/[{}[\]().]/g, "");
|
|
826
|
+
if (ID.test(name)) params.add(name);
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
let m = k;
|
|
830
|
+
while (m >= 0 && ID_CHAR.test(expr[m])) m--;
|
|
831
|
+
const name = expr.slice(m + 1, k + 1);
|
|
832
|
+
if (ID.test(name)) params.add(name);
|
|
833
|
+
}
|
|
834
|
+
at = expr.indexOf("=>", at + 2);
|
|
835
|
+
}
|
|
836
|
+
return params;
|
|
837
|
+
}
|
|
838
|
+
function freeIdentifiers(expr) {
|
|
839
|
+
const out = /* @__PURE__ */ new Set();
|
|
840
|
+
const params = arrowParams(expr);
|
|
841
|
+
let i = 0;
|
|
842
|
+
const n = expr.length;
|
|
843
|
+
while (i < n) {
|
|
844
|
+
const c = expr[i];
|
|
845
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
846
|
+
i = scanString(expr, i);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (ID_START.test(c)) {
|
|
850
|
+
let j = i + 1;
|
|
851
|
+
while (j < n && ID_CHAR.test(expr[j])) j++;
|
|
852
|
+
const name = expr.slice(i, j);
|
|
853
|
+
const isProperty = lastNonSpace(expr.slice(0, i)) === ".";
|
|
854
|
+
if (!isProperty && !NON_CTX.has(name) && !params.has(name)) out.add(name);
|
|
855
|
+
i = j;
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
i++;
|
|
859
|
+
}
|
|
860
|
+
return [...out];
|
|
861
|
+
}
|
|
862
|
+
function scanString(s, start) {
|
|
863
|
+
const quote = s[start];
|
|
864
|
+
let i = start + 1;
|
|
865
|
+
while (i < s.length) {
|
|
866
|
+
const c = s[i];
|
|
867
|
+
if (c === "\\") {
|
|
868
|
+
i += 2;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (c === quote) return i + 1;
|
|
872
|
+
i++;
|
|
873
|
+
}
|
|
874
|
+
return s.length;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// packages/compiler/src/codegen.ts
|
|
878
|
+
var Gen = class {
|
|
879
|
+
constructor(mode, scopeAttr2, hostAttr2) {
|
|
880
|
+
this.mode = mode;
|
|
881
|
+
this.scopeAttr = scopeAttr2;
|
|
882
|
+
this.hostAttr = hostAttr2;
|
|
883
|
+
}
|
|
884
|
+
used = /* @__PURE__ */ new Set();
|
|
885
|
+
// @weave-framework/runtime/dom helpers
|
|
886
|
+
usedCore = /* @__PURE__ */ new Set();
|
|
887
|
+
// @weave-framework/runtime primitives (computed, …)
|
|
888
|
+
templates = [];
|
|
889
|
+
tplN = 0;
|
|
890
|
+
fnN = 0;
|
|
891
|
+
H(name) {
|
|
892
|
+
this.used.add(name);
|
|
893
|
+
return this.mode === "function" ? `rt.${name}` : name;
|
|
894
|
+
}
|
|
895
|
+
Hc(name) {
|
|
896
|
+
this.usedCore.add(name);
|
|
897
|
+
return this.mode === "function" ? `rt.${name}` : name;
|
|
898
|
+
}
|
|
899
|
+
/** Reference a child component: from the `_c` map in function mode, bare (imported) in module mode. */
|
|
900
|
+
Comp(name) {
|
|
901
|
+
return this.mode === "function" ? `_c.${name}` : name;
|
|
902
|
+
}
|
|
903
|
+
tpl(html) {
|
|
904
|
+
const v = `_t${this.tplN++}`;
|
|
905
|
+
this.templates.push(`const ${v} = ${this.H("template")}(${JSON.stringify(html)});`);
|
|
906
|
+
return v;
|
|
907
|
+
}
|
|
908
|
+
fn(prefix = "_b") {
|
|
909
|
+
return `${prefix}${this.fnN++}`;
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
function compileTemplate(input, options = {}) {
|
|
913
|
+
const mode = options.mode ?? "module";
|
|
914
|
+
const runtimeImport = options.runtimeImport ?? "@weave-framework/runtime/dom";
|
|
915
|
+
const gen = new Gen(mode, options.scopeAttr, options.hostAttr);
|
|
916
|
+
const ast = parseTemplate(input);
|
|
917
|
+
const render = compileFragment(gen, ast, ctxScope(options.scope ?? []), "render", "ctx, slots", true);
|
|
918
|
+
if (mode === "function") {
|
|
919
|
+
const body = [...gen.templates, render, "return render(ctx, {});"].join("\n");
|
|
920
|
+
return { code: body };
|
|
921
|
+
}
|
|
922
|
+
const domImport = `import { ${[...gen.used].sort().join(", ")} } from ${JSON.stringify(runtimeImport)};`;
|
|
923
|
+
const coreImport = gen.usedCore.size ? `import { ${[...gen.usedCore].sort().join(", ")} } from "@weave-framework/runtime";
|
|
924
|
+
` : "";
|
|
925
|
+
const code = [domImport + "\n" + coreImport, ...gen.templates, `export default ${render}`].join("\n");
|
|
926
|
+
return { code };
|
|
927
|
+
}
|
|
928
|
+
function compileFragment(gen, nodes, scope, name, param = "", isHost = false) {
|
|
929
|
+
const top = trimTop(nodes);
|
|
930
|
+
if (top.length === 0) throw new Error("Empty template fragment");
|
|
931
|
+
const sole = top.length === 1 && top[0].type === "element" ? top[0] : null;
|
|
932
|
+
const singleRoot = !!sole && !/^[A-Z]/.test(sole.tag) && sole.tag !== "slot";
|
|
933
|
+
let html = "";
|
|
934
|
+
const stmts = [];
|
|
935
|
+
const childDecls = [];
|
|
936
|
+
const nodeDecls = [];
|
|
937
|
+
const nodeVars = /* @__PURE__ */ new Map();
|
|
938
|
+
let nodeVarN = 0;
|
|
939
|
+
const nodeExpr = (path) => {
|
|
940
|
+
if (path.length === 0) return "_r";
|
|
941
|
+
const key = path.join(",");
|
|
942
|
+
let v = nodeVars.get(key);
|
|
943
|
+
if (!v) {
|
|
944
|
+
v = `_n${nodeVarN++}`;
|
|
945
|
+
nodeVars.set(key, v);
|
|
946
|
+
nodeDecls.push(`const ${v} = ${gen.H("child")}(_r, ${path.join(", ")});`);
|
|
947
|
+
}
|
|
948
|
+
return v;
|
|
949
|
+
};
|
|
950
|
+
function emitChildren(children, basePath, sc, isHost2 = false) {
|
|
951
|
+
let dom = 0;
|
|
952
|
+
let cur = sc;
|
|
953
|
+
const snippetNames = children.filter((n) => n.type === "snippet").map((n) => n.name);
|
|
954
|
+
if (snippetNames.length) {
|
|
955
|
+
cur = new Map(cur);
|
|
956
|
+
for (const nm of snippetNames) cur.set(nm, { kind: "local" });
|
|
957
|
+
}
|
|
958
|
+
for (const node of children) {
|
|
959
|
+
if (node.type === "let") {
|
|
960
|
+
html += "<!---->";
|
|
961
|
+
stmts.push(`const ${node.name} = ${gen.Hc("computed")}(() => ${rewrite(node.expr, cur).code});`);
|
|
962
|
+
cur = childScope(cur, { [node.name]: node.name });
|
|
963
|
+
dom++;
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
if (node.type === "snippet") {
|
|
967
|
+
emitSnippet(node, cur);
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
emitNode(node, [...basePath, dom], cur, isHost2);
|
|
971
|
+
dom++;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function emitSnippet(node, sc) {
|
|
975
|
+
const bodyScope = new Map(sc);
|
|
976
|
+
for (const p of node.params) bodyScope.set(p, { kind: "local" });
|
|
977
|
+
childDecls.push(compileFragment(gen, node.children, bodyScope, node.name, node.params.join(", ")));
|
|
978
|
+
}
|
|
979
|
+
function emitRender(node, path, sc) {
|
|
980
|
+
html += "<!---->";
|
|
981
|
+
stmts.push(`${gen.H("mountChild")}(${nodeExpr(path)}, ${rewrite(node.expr, sc).code});`);
|
|
982
|
+
}
|
|
983
|
+
function emitKey(node, path, sc) {
|
|
984
|
+
html += "<!---->";
|
|
985
|
+
const contentFn = gen.fn();
|
|
986
|
+
childDecls.push(compileFragment(gen, node.children, sc, contentFn));
|
|
987
|
+
stmts.push(`${gen.H("keyBlock")}(${nodeExpr(path)}, () => ${rewrite(node.expr, sc).code}, ${contentFn});`);
|
|
988
|
+
}
|
|
989
|
+
function emitNode(node, path, sc, isHost2 = false) {
|
|
990
|
+
switch (node.type) {
|
|
991
|
+
case "text":
|
|
992
|
+
html += escapeText(node.value);
|
|
993
|
+
return;
|
|
994
|
+
case "interp": {
|
|
995
|
+
html += "<!---->";
|
|
996
|
+
const { code, reactive } = rewrite(node.expr, sc);
|
|
997
|
+
stmts.push(
|
|
998
|
+
reactive ? `${gen.H("bindText")}(${nodeExpr(path)}, () => ${code});` : `${gen.H("setText")}(${nodeExpr(path)}, ${code});`
|
|
999
|
+
);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
case "element":
|
|
1003
|
+
emitElement(node, path, sc, isHost2);
|
|
1004
|
+
return;
|
|
1005
|
+
case "if":
|
|
1006
|
+
emitIf(node, path, sc);
|
|
1007
|
+
return;
|
|
1008
|
+
case "for":
|
|
1009
|
+
emitFor(node, path, sc);
|
|
1010
|
+
return;
|
|
1011
|
+
case "switch":
|
|
1012
|
+
emitSwitch(node, path, sc);
|
|
1013
|
+
return;
|
|
1014
|
+
case "defer":
|
|
1015
|
+
emitDefer(node, path, sc);
|
|
1016
|
+
return;
|
|
1017
|
+
case "await":
|
|
1018
|
+
emitAwait(node, path, sc);
|
|
1019
|
+
return;
|
|
1020
|
+
case "render":
|
|
1021
|
+
emitRender(node, path, sc);
|
|
1022
|
+
return;
|
|
1023
|
+
case "key":
|
|
1024
|
+
emitKey(node, path, sc);
|
|
1025
|
+
return;
|
|
1026
|
+
case "snippet":
|
|
1027
|
+
throw new Error("@snippet is a declaration, handled in emitChildren");
|
|
1028
|
+
case "let":
|
|
1029
|
+
throw new Error("@let cannot be a single dynamic node here");
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function emitElement(node, path, sc, isHost2 = false) {
|
|
1033
|
+
if (node.tag === "slot") return emitSlot(node, path, sc);
|
|
1034
|
+
if (node.tag === "w:element") return emitDynamicElement(node, path, sc);
|
|
1035
|
+
if (/^[A-Z]/.test(node.tag)) return emitComponent(node, path, sc);
|
|
1036
|
+
html += `<${node.tag}`;
|
|
1037
|
+
if (gen.scopeAttr) html += ` ${gen.scopeAttr}`;
|
|
1038
|
+
if (isHost2 && gen.hostAttr) html += ` ${gen.hostAttr}`;
|
|
1039
|
+
for (const attr of node.attrs) {
|
|
1040
|
+
if (attr.type === "static") {
|
|
1041
|
+
html += attr.value === "" ? ` ${attr.name}` : ` ${attr.name}="${escapeAttr(attr.value)}"`;
|
|
1042
|
+
} else {
|
|
1043
|
+
emitBinding(attr, nodeExpr(path), sc);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
html += ">";
|
|
1047
|
+
if (!node.selfClosing) {
|
|
1048
|
+
emitChildren(node.children, path, sc);
|
|
1049
|
+
html += `</${node.tag}>`;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function emitDynamicElement(node, path, sc) {
|
|
1053
|
+
html += "<!---->";
|
|
1054
|
+
const anchor = nodeExpr(path);
|
|
1055
|
+
let tagExpr = '""';
|
|
1056
|
+
const build2 = [];
|
|
1057
|
+
for (const attr of node.attrs) {
|
|
1058
|
+
if ((attr.type === "attr" || attr.type === "static") && attr.name === "this") {
|
|
1059
|
+
tagExpr = attr.type === "attr" ? rewrite(attr.expr, sc).code : q(attr.value);
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
if (attr.type === "static") {
|
|
1063
|
+
build2.push(`${gen.H("setAttr")}(_el, ${q(attr.name)}, ${q(attr.value)});`);
|
|
1064
|
+
} else {
|
|
1065
|
+
emitBinding(attr, "_el", sc, build2);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (trimTop(node.children).length > 0) {
|
|
1069
|
+
const contentFn = gen.fn();
|
|
1070
|
+
childDecls.push(compileFragment(gen, node.children, sc, contentFn));
|
|
1071
|
+
build2.push(`_el.append(${contentFn}());`);
|
|
1072
|
+
}
|
|
1073
|
+
stmts.push(`${gen.H("dynElement")}(${anchor}, () => ${tagExpr}, (_el) => { ${build2.join(" ")} });`);
|
|
1074
|
+
}
|
|
1075
|
+
function emitBinding(attr, n, sc, sink = stmts) {
|
|
1076
|
+
switch (attr.type) {
|
|
1077
|
+
case "attr": {
|
|
1078
|
+
const { code, reactive } = rewrite(attr.expr, sc);
|
|
1079
|
+
sink.push(
|
|
1080
|
+
reactive ? `${gen.H("bindAttr")}(${n}, ${q(attr.name)}, () => ${code});` : `${gen.H("setAttr")}(${n}, ${q(attr.name)}, ${code});`
|
|
1081
|
+
);
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
case "prop":
|
|
1085
|
+
sink.push(`${gen.H("bindProp")}(${n}, ${q(attr.name)}, () => ${rewrite(attr.expr, sc).code});`);
|
|
1086
|
+
break;
|
|
1087
|
+
case "class":
|
|
1088
|
+
sink.push(`${gen.H("bindClass")}(${n}, ${q(attr.name)}, () => ${rewrite(attr.expr, sc).code});`);
|
|
1089
|
+
break;
|
|
1090
|
+
case "show":
|
|
1091
|
+
sink.push(`${gen.H("bindShow")}(${n}, () => ${rewrite(attr.expr, sc).code});`);
|
|
1092
|
+
break;
|
|
1093
|
+
case "transition": {
|
|
1094
|
+
const fn = rewrite(attr.name, sc).code;
|
|
1095
|
+
const params = attr.expr !== void 0 ? rewrite(attr.expr, sc).code : "undefined";
|
|
1096
|
+
sink.push(`${gen.H("transition")}(${n}, ${fn}, ${params}, ${q(attr.mode)});`);
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
case "event": {
|
|
1100
|
+
const handler = wrapHandler(attr, sc);
|
|
1101
|
+
const opts = eventOpts(attr.modifiers);
|
|
1102
|
+
sink.push(`${gen.H("listen")}(${n}, ${q(attr.name)}, ${handler}${opts ? `, ${opts}` : ""});`);
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
case "ref":
|
|
1106
|
+
sink.push(`${gen.H("setRef")}(${rewrite(attr.expr, sc).code}, ${n});`);
|
|
1107
|
+
break;
|
|
1108
|
+
case "use": {
|
|
1109
|
+
const action = rewrite(attr.name, sc).code;
|
|
1110
|
+
sink.push(
|
|
1111
|
+
attr.expr !== void 0 ? `${gen.H("applyAction")}(${n}, ${action}, ${rewrite(attr.expr, sc).code});` : `${gen.H("applyAction")}(${n}, ${action});`
|
|
1112
|
+
);
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
case "bind": {
|
|
1116
|
+
const kind = attr.name === "checked" ? "checked" : attr.name === "group" ? "group" : "value";
|
|
1117
|
+
sink.push(`${gen.H("bindValue")}(${n}, ${rewrite(attr.expr, sc).code}, ${q(kind)});`);
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function emitComponent(node, path, sc) {
|
|
1123
|
+
html += "<!---->";
|
|
1124
|
+
const anchorVar = nodeExpr(path);
|
|
1125
|
+
const props = [];
|
|
1126
|
+
for (const attr of node.attrs) {
|
|
1127
|
+
switch (attr.type) {
|
|
1128
|
+
case "static":
|
|
1129
|
+
props.push(`${propKey(attr.name)}: ${q(attr.value)}`);
|
|
1130
|
+
break;
|
|
1131
|
+
case "attr":
|
|
1132
|
+
props.push(`get ${propKey(attr.name)}() { return ${rewrite(attr.expr, sc).code}; }`);
|
|
1133
|
+
break;
|
|
1134
|
+
case "event":
|
|
1135
|
+
props.push(`${propKey(onProp(attr.name))}: ${rewrite(attr.expr, sc).code}`);
|
|
1136
|
+
break;
|
|
1137
|
+
default:
|
|
1138
|
+
throw new Error(`'${attr.type}' binding on <${node.tag}> is not supported yet (M5: props + on:event only)`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1142
|
+
for (const child of node.children) {
|
|
1143
|
+
let target = child;
|
|
1144
|
+
let slotName = "default";
|
|
1145
|
+
if (child.type === "element") {
|
|
1146
|
+
const i = child.attrs.findIndex((a) => a.type === "static" && a.name === "slot");
|
|
1147
|
+
if (i >= 0) {
|
|
1148
|
+
slotName = child.attrs[i].value;
|
|
1149
|
+
target = { ...child, attrs: child.attrs.filter((_, k) => k !== i) };
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
let arr = groups.get(slotName);
|
|
1153
|
+
if (!arr) {
|
|
1154
|
+
arr = [];
|
|
1155
|
+
groups.set(slotName, arr);
|
|
1156
|
+
}
|
|
1157
|
+
arr.push(target);
|
|
1158
|
+
}
|
|
1159
|
+
const slots = [];
|
|
1160
|
+
for (const [name2, children] of groups) {
|
|
1161
|
+
if (trimTop(children).length === 0) continue;
|
|
1162
|
+
const slotFn = gen.fn("_s");
|
|
1163
|
+
childDecls.push(compileFragment(gen, children, sc, slotFn));
|
|
1164
|
+
slots.push(`${propKey(name2)}: ${slotFn}`);
|
|
1165
|
+
}
|
|
1166
|
+
const propsObj = props.length ? `{ ${props.join(", ")} }` : "{}";
|
|
1167
|
+
const slotsObj = slots.length ? `{ ${slots.join(", ")} }` : "{}";
|
|
1168
|
+
stmts.push(`${gen.H("mountChild")}(${anchorVar}, ${gen.Comp(node.tag)}(${propsObj}, ${slotsObj}));`);
|
|
1169
|
+
}
|
|
1170
|
+
function emitSlot(node, path, sc) {
|
|
1171
|
+
html += "<!---->";
|
|
1172
|
+
const anchorVar = nodeExpr(path);
|
|
1173
|
+
const nameAttr = node.attrs.find((a) => a.type === "static" && a.name === "name");
|
|
1174
|
+
const name2 = nameAttr ? nameAttr.value : "default";
|
|
1175
|
+
let fallback = "null";
|
|
1176
|
+
if (trimTop(node.children).length > 0) {
|
|
1177
|
+
const fbFn = gen.fn("_f");
|
|
1178
|
+
childDecls.push(compileFragment(gen, node.children, sc, fbFn));
|
|
1179
|
+
fallback = `${fbFn}()`;
|
|
1180
|
+
}
|
|
1181
|
+
stmts.push(
|
|
1182
|
+
`{ const _sf = slots[${q(name2)}]; ${gen.H("mountChild")}(${anchorVar}, _sf ? _sf() : ${fallback}); }`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
function emitIf(node, path, sc) {
|
|
1186
|
+
html += "<!---->";
|
|
1187
|
+
const head = node.branches[0];
|
|
1188
|
+
let aliasVar;
|
|
1189
|
+
if (head.alias) {
|
|
1190
|
+
aliasVar = gen.fn("_a");
|
|
1191
|
+
stmts.push(`const ${aliasVar} = ${gen.Hc("computed")}(() => ${rewrite(head.cond ?? "undefined", sc).code});`);
|
|
1192
|
+
}
|
|
1193
|
+
const branchNames = node.branches.map(() => gen.fn());
|
|
1194
|
+
node.branches.forEach((br, i) => {
|
|
1195
|
+
const bScope = i === 0 && head.alias && aliasVar ? childScope(sc, { [head.alias]: aliasVar }) : sc;
|
|
1196
|
+
childDecls.push(compileFragment(gen, br.children, bScope, branchNames[i]));
|
|
1197
|
+
});
|
|
1198
|
+
const lines = [];
|
|
1199
|
+
node.branches.forEach((br, i) => {
|
|
1200
|
+
if (i === 0 && aliasVar) lines.push(`if (${aliasVar}()) return ${branchNames[i]};`);
|
|
1201
|
+
else if (br.cond !== void 0) lines.push(`if (${rewrite(br.cond, sc).code}) return ${branchNames[i]};`);
|
|
1202
|
+
else lines.push(`return ${branchNames[i]};`);
|
|
1203
|
+
});
|
|
1204
|
+
const hasElse = node.branches[node.branches.length - 1].cond === void 0;
|
|
1205
|
+
if (!hasElse) lines.push("return null;");
|
|
1206
|
+
stmts.push(`${gen.H("ifBlock")}(${nodeExpr(path)}, () => { ${lines.join(" ")} });`);
|
|
1207
|
+
}
|
|
1208
|
+
function emitSwitch(node, path, sc) {
|
|
1209
|
+
html += "<!---->";
|
|
1210
|
+
const names = node.cases.map(() => gen.fn());
|
|
1211
|
+
node.cases.forEach((c, i) => childDecls.push(compileFragment(gen, c.children, sc, names[i])));
|
|
1212
|
+
const lines = [`const _v = ${rewrite(node.expr, sc).code};`];
|
|
1213
|
+
node.cases.forEach((c, i) => {
|
|
1214
|
+
if (c.test !== void 0) lines.push(`if (_v === ${rewrite(c.test, sc).code}) return ${names[i]};`);
|
|
1215
|
+
else lines.push(`return ${names[i]};`);
|
|
1216
|
+
});
|
|
1217
|
+
if (!node.cases.some((c) => c.test === void 0)) lines.push("return null;");
|
|
1218
|
+
stmts.push(`${gen.H("ifBlock")}(${nodeExpr(path)}, () => { ${lines.join(" ")} });`);
|
|
1219
|
+
}
|
|
1220
|
+
function emitDefer(node, path, sc) {
|
|
1221
|
+
html += "<!---->";
|
|
1222
|
+
const contentFn = gen.fn();
|
|
1223
|
+
childDecls.push(compileFragment(gen, node.children, sc, contentFn));
|
|
1224
|
+
let phArg = "undefined";
|
|
1225
|
+
if (node.placeholder && trimTop(node.placeholder).length > 0) {
|
|
1226
|
+
const phFn = gen.fn();
|
|
1227
|
+
childDecls.push(compileFragment(gen, node.placeholder, sc, phFn));
|
|
1228
|
+
phArg = phFn;
|
|
1229
|
+
}
|
|
1230
|
+
const trig = deferTriggerExpr(node.trigger, sc);
|
|
1231
|
+
stmts.push(`${gen.H("deferBlock")}(${nodeExpr(path)}, ${trig}, ${contentFn}, ${phArg});`);
|
|
1232
|
+
}
|
|
1233
|
+
function emitAwait(node, path, sc) {
|
|
1234
|
+
html += "<!---->";
|
|
1235
|
+
const anchorVar = nodeExpr(path);
|
|
1236
|
+
const source = `() => (${rewrite(node.expr, sc).code})`;
|
|
1237
|
+
const branchFn = (children, alias) => {
|
|
1238
|
+
if (!children || trimTop(children).length === 0) return "undefined";
|
|
1239
|
+
const fn = gen.fn();
|
|
1240
|
+
const scope2 = alias ? new Map(sc).set(alias, { kind: "local" }) : sc;
|
|
1241
|
+
childDecls.push(compileFragment(gen, children, scope2, fn, alias ?? ""));
|
|
1242
|
+
return fn;
|
|
1243
|
+
};
|
|
1244
|
+
const pendingArg = branchFn(node.pending);
|
|
1245
|
+
const thenArg = branchFn(node.then?.children, node.then?.alias);
|
|
1246
|
+
const catchArg = branchFn(node.catch?.children, node.catch?.alias);
|
|
1247
|
+
stmts.push(`${gen.H("awaitBlock")}(${anchorVar}, ${source}, ${pendingArg}, ${thenArg}, ${catchArg});`);
|
|
1248
|
+
}
|
|
1249
|
+
function deferTriggerExpr(t, sc) {
|
|
1250
|
+
switch (t.kind) {
|
|
1251
|
+
case "when":
|
|
1252
|
+
return `{ on: "when", when: () => ${rewrite(t.expr, sc).code} }`;
|
|
1253
|
+
case "timer":
|
|
1254
|
+
return `{ on: "timer", ms: ${rewrite(t.ms, sc).code} }`;
|
|
1255
|
+
case "idle":
|
|
1256
|
+
case "viewport":
|
|
1257
|
+
case "interaction":
|
|
1258
|
+
case "hover":
|
|
1259
|
+
case "immediate":
|
|
1260
|
+
return `{ on: ${q(t.kind)} }`;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function emitFor(node, path, sc) {
|
|
1264
|
+
html += "<!---->";
|
|
1265
|
+
const rowFn = gen.fn();
|
|
1266
|
+
const forScope = childScope(sc, {
|
|
1267
|
+
[node.item]: "_row.item",
|
|
1268
|
+
$index: "_row.index",
|
|
1269
|
+
$count: "_row.count",
|
|
1270
|
+
$first: "_row.first",
|
|
1271
|
+
$last: "_row.last",
|
|
1272
|
+
$even: "_row.even",
|
|
1273
|
+
$odd: "_row.odd"
|
|
1274
|
+
});
|
|
1275
|
+
childDecls.push(compileFragment(gen, node.children, forScope, rowFn, "_row"));
|
|
1276
|
+
let emptyArg = "";
|
|
1277
|
+
if (node.empty) {
|
|
1278
|
+
const emptyFn = gen.fn();
|
|
1279
|
+
childDecls.push(compileFragment(gen, node.empty, sc, emptyFn));
|
|
1280
|
+
emptyArg = `, ${emptyFn}`;
|
|
1281
|
+
}
|
|
1282
|
+
const list = rewrite(node.list, sc).code;
|
|
1283
|
+
const track = node.track ? rewrite(node.track, sc).code : "$index";
|
|
1284
|
+
const keyFn = `(${node.item}, $index) => ${track}`;
|
|
1285
|
+
stmts.push(`${gen.H("eachBlock")}(${nodeExpr(path)}, () => ${list}, ${keyFn}, ${rowFn}${emptyArg});`);
|
|
1286
|
+
}
|
|
1287
|
+
if (singleRoot) emitElement(sole, [], scope, isHost);
|
|
1288
|
+
else emitChildren(top, [], scope, isHost);
|
|
1289
|
+
const ctor = singleRoot ? gen.H("clone") : gen.H("cloneFragment");
|
|
1290
|
+
const tplVar = gen.tpl(html);
|
|
1291
|
+
const body = [
|
|
1292
|
+
`const _r = ${ctor}(${tplVar});`,
|
|
1293
|
+
...nodeDecls,
|
|
1294
|
+
...stmts,
|
|
1295
|
+
"return _r;",
|
|
1296
|
+
...childDecls
|
|
1297
|
+
];
|
|
1298
|
+
return `function ${name}(${param}) {
|
|
1299
|
+
${body.map((l) => " " + l).join("\n")}
|
|
1300
|
+
}`;
|
|
1301
|
+
}
|
|
1302
|
+
function wrapHandler(attr, scope) {
|
|
1303
|
+
const expr = rewrite(attr.expr, scope).code;
|
|
1304
|
+
const guards = [];
|
|
1305
|
+
for (const m of attr.modifiers) {
|
|
1306
|
+
if (m === "preventDefault") guards.push("$e.preventDefault();");
|
|
1307
|
+
else if (m === "stopPropagation") guards.push("$e.stopPropagation();");
|
|
1308
|
+
else if (m === "self") guards.push("if ($e.target !== $e.currentTarget) return;");
|
|
1309
|
+
}
|
|
1310
|
+
if (guards.length === 0) return expr;
|
|
1311
|
+
return `($e) => { ${guards.join(" ")} (${expr})($e); }`;
|
|
1312
|
+
}
|
|
1313
|
+
function eventOpts(modifiers) {
|
|
1314
|
+
const opts = [];
|
|
1315
|
+
if (modifiers.includes("once")) opts.push("once: true");
|
|
1316
|
+
if (modifiers.includes("capture")) opts.push("capture: true");
|
|
1317
|
+
if (modifiers.includes("passive")) opts.push("passive: true");
|
|
1318
|
+
return opts.length ? `{ ${opts.join(", ")} }` : "";
|
|
1319
|
+
}
|
|
1320
|
+
function trimTop(nodes) {
|
|
1321
|
+
return nodes.filter((n) => !(n.type === "text" && n.value.trim() === ""));
|
|
1322
|
+
}
|
|
1323
|
+
function q(s) {
|
|
1324
|
+
return JSON.stringify(s);
|
|
1325
|
+
}
|
|
1326
|
+
function propKey(name) {
|
|
1327
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
1328
|
+
}
|
|
1329
|
+
function onProp(event) {
|
|
1330
|
+
return "on" + event.charAt(0).toUpperCase() + event.slice(1);
|
|
1331
|
+
}
|
|
1332
|
+
function escapeText(s) {
|
|
1333
|
+
return s.replace(/&/g, "&").replace(/</g, "<");
|
|
1334
|
+
}
|
|
1335
|
+
function escapeAttr(s) {
|
|
1336
|
+
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// packages/compiler/src/css.ts
|
|
1340
|
+
function scopeAttr(hash) {
|
|
1341
|
+
return `data-w-${hash}`;
|
|
1342
|
+
}
|
|
1343
|
+
function hostAttr(hash) {
|
|
1344
|
+
return `data-w-${hash}-h`;
|
|
1345
|
+
}
|
|
1346
|
+
function hashCss(input) {
|
|
1347
|
+
let h = 2166136261;
|
|
1348
|
+
for (let i = 0; i < input.length; i++) {
|
|
1349
|
+
h ^= input.charCodeAt(i);
|
|
1350
|
+
h = Math.imul(h, 16777619);
|
|
1351
|
+
}
|
|
1352
|
+
return (h >>> 0).toString(36).padStart(6, "0").slice(0, 6);
|
|
1353
|
+
}
|
|
1354
|
+
function scopeCss(css, hash) {
|
|
1355
|
+
return transformBlock(css, scopeAttr(hash), hostAttr(hash), false);
|
|
1356
|
+
}
|
|
1357
|
+
function transformBlock(css, attr, host, keyframes) {
|
|
1358
|
+
let out = "";
|
|
1359
|
+
let i = 0;
|
|
1360
|
+
const n = css.length;
|
|
1361
|
+
while (i < n) {
|
|
1362
|
+
const c = css[i];
|
|
1363
|
+
if (/\s/.test(c)) {
|
|
1364
|
+
out += c;
|
|
1365
|
+
i++;
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
if (css.startsWith("/*", i)) {
|
|
1369
|
+
const end = css.indexOf("*/", i + 2);
|
|
1370
|
+
const stop = end === -1 ? n : end + 2;
|
|
1371
|
+
out += css.slice(i, stop);
|
|
1372
|
+
i = stop;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
let j = i;
|
|
1376
|
+
let depth = 0;
|
|
1377
|
+
let kind = "";
|
|
1378
|
+
while (j < n) {
|
|
1379
|
+
const ch = css[j];
|
|
1380
|
+
if (ch === '"' || ch === "'") {
|
|
1381
|
+
j = skipString(css, j);
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
if (css.startsWith("/*", j)) {
|
|
1385
|
+
const e = css.indexOf("*/", j + 2);
|
|
1386
|
+
j = e === -1 ? n : e + 2;
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (ch === "(" || ch === "[") depth++;
|
|
1390
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
1391
|
+
else if (depth === 0 && ch === "{") {
|
|
1392
|
+
kind = "block";
|
|
1393
|
+
break;
|
|
1394
|
+
} else if (depth === 0 && ch === ";") {
|
|
1395
|
+
kind = "decl";
|
|
1396
|
+
break;
|
|
1397
|
+
}
|
|
1398
|
+
j++;
|
|
1399
|
+
}
|
|
1400
|
+
if (kind === "") {
|
|
1401
|
+
out += css.slice(i);
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
const prelude = css.slice(i, j);
|
|
1405
|
+
if (kind === "decl") {
|
|
1406
|
+
out += prelude + ";";
|
|
1407
|
+
i = j + 1;
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
const bodyStart = j + 1;
|
|
1411
|
+
let k = bodyStart;
|
|
1412
|
+
let bd = 1;
|
|
1413
|
+
while (k < n) {
|
|
1414
|
+
const ch = css[k];
|
|
1415
|
+
if (ch === '"' || ch === "'") {
|
|
1416
|
+
k = skipString(css, k);
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if (css.startsWith("/*", k)) {
|
|
1420
|
+
const e = css.indexOf("*/", k + 2);
|
|
1421
|
+
k = e === -1 ? n : e + 2;
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
if (ch === "{") bd++;
|
|
1425
|
+
else if (ch === "}") {
|
|
1426
|
+
bd--;
|
|
1427
|
+
if (bd === 0) break;
|
|
1428
|
+
}
|
|
1429
|
+
k++;
|
|
1430
|
+
}
|
|
1431
|
+
const body = css.slice(bodyStart, k);
|
|
1432
|
+
const after = k < n ? k + 1 : n;
|
|
1433
|
+
const trimmed = prelude.trim();
|
|
1434
|
+
if (keyframes) {
|
|
1435
|
+
out += prelude + "{" + body + "}";
|
|
1436
|
+
} else if (trimmed.startsWith("@")) {
|
|
1437
|
+
const kw = (/^@-?\w[\w-]*/.exec(trimmed)?.[0] ?? "").toLowerCase();
|
|
1438
|
+
if (kw.endsWith("keyframes")) {
|
|
1439
|
+
out += prelude + "{" + transformBlock(body, attr, host, true) + "}";
|
|
1440
|
+
} else if (kw === "@font-face" || kw === "@page" || kw === "@property" || kw === "@counter-style") {
|
|
1441
|
+
out += prelude + "{" + body + "}";
|
|
1442
|
+
} else {
|
|
1443
|
+
out += prelude + "{" + transformBlock(body, attr, host, false) + "}";
|
|
1444
|
+
}
|
|
1445
|
+
} else {
|
|
1446
|
+
out += scopeSelectorList(prelude, attr, host) + "{" + transformBlock(body, attr, host, false) + "}";
|
|
1447
|
+
}
|
|
1448
|
+
i = after;
|
|
1449
|
+
}
|
|
1450
|
+
return out;
|
|
1451
|
+
}
|
|
1452
|
+
function scopeSelectorList(prelude, attr, host) {
|
|
1453
|
+
return splitTopLevel2(prelude, ",").map((s) => scopeSelector(s, attr, host)).join(", ");
|
|
1454
|
+
}
|
|
1455
|
+
function scopeSelector(raw, attr, host) {
|
|
1456
|
+
const sel = raw.trim();
|
|
1457
|
+
if (!sel) return sel;
|
|
1458
|
+
const start = rightmostCompoundStart(sel);
|
|
1459
|
+
const prefix = rewriteHost(unwrapGlobal(sel.slice(0, start)), host);
|
|
1460
|
+
const right = sel.slice(start);
|
|
1461
|
+
const rightTrim = right.trim();
|
|
1462
|
+
if (rightTrim.startsWith(":host")) {
|
|
1463
|
+
return prefix + rewriteHost(unwrapGlobal(right), host);
|
|
1464
|
+
}
|
|
1465
|
+
if (rightTrim.startsWith(":global(") || right.includes("&")) {
|
|
1466
|
+
return prefix + unwrapGlobal(right);
|
|
1467
|
+
}
|
|
1468
|
+
return prefix + insertAttr(unwrapGlobal(right), attr);
|
|
1469
|
+
}
|
|
1470
|
+
function rewriteHost(s, host) {
|
|
1471
|
+
let out = "";
|
|
1472
|
+
let i = 0;
|
|
1473
|
+
while (i < s.length) {
|
|
1474
|
+
if (s.startsWith(":host", i)) {
|
|
1475
|
+
const after = s[i + 5];
|
|
1476
|
+
if (after === "(") {
|
|
1477
|
+
let depth = 1;
|
|
1478
|
+
let j = i + 6;
|
|
1479
|
+
let inner = "";
|
|
1480
|
+
while (j < s.length && depth > 0) {
|
|
1481
|
+
const c = s[j];
|
|
1482
|
+
if (c === "(") depth++;
|
|
1483
|
+
else if (c === ")" && --depth === 0) {
|
|
1484
|
+
j++;
|
|
1485
|
+
break;
|
|
1486
|
+
}
|
|
1487
|
+
inner += c;
|
|
1488
|
+
j++;
|
|
1489
|
+
}
|
|
1490
|
+
out += inner.trim() + `[${host}]`;
|
|
1491
|
+
i = j;
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
if (after === void 0 || !/[-\w]/.test(after)) {
|
|
1495
|
+
out += `[${host}]`;
|
|
1496
|
+
i += 5;
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
out += s[i++];
|
|
1501
|
+
}
|
|
1502
|
+
return out;
|
|
1503
|
+
}
|
|
1504
|
+
function rightmostCompoundStart(sel) {
|
|
1505
|
+
let depth = 0;
|
|
1506
|
+
let start = 0;
|
|
1507
|
+
let i = 0;
|
|
1508
|
+
while (i < sel.length) {
|
|
1509
|
+
const c = sel[i];
|
|
1510
|
+
if (c === "(" || c === "[") {
|
|
1511
|
+
depth++;
|
|
1512
|
+
i++;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (c === ")" || c === "]") {
|
|
1516
|
+
depth--;
|
|
1517
|
+
i++;
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
if (depth === 0) {
|
|
1521
|
+
if (c === ">" || c === "+" || c === "~") {
|
|
1522
|
+
i++;
|
|
1523
|
+
while (i < sel.length && /\s/.test(sel[i])) i++;
|
|
1524
|
+
start = i;
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
if (/\s/.test(c)) {
|
|
1528
|
+
let j = i;
|
|
1529
|
+
while (j < sel.length && /\s/.test(sel[j])) j++;
|
|
1530
|
+
const next = sel[j];
|
|
1531
|
+
if (j >= sel.length) {
|
|
1532
|
+
i = j;
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (next === ">" || next === "+" || next === "~") {
|
|
1536
|
+
i = j;
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
start = j;
|
|
1540
|
+
i = j;
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
i++;
|
|
1545
|
+
}
|
|
1546
|
+
return start;
|
|
1547
|
+
}
|
|
1548
|
+
function insertAttr(compound, attr) {
|
|
1549
|
+
let depth = 0;
|
|
1550
|
+
for (let i = 0; i < compound.length; i++) {
|
|
1551
|
+
const c = compound[i];
|
|
1552
|
+
if (c === "(" || c === "[") depth++;
|
|
1553
|
+
else if (c === ")" || c === "]") depth--;
|
|
1554
|
+
else if (depth === 0 && c === ":") {
|
|
1555
|
+
return compound.slice(0, i) + `[${attr}]` + compound.slice(i);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return compound + `[${attr}]`;
|
|
1559
|
+
}
|
|
1560
|
+
function unwrapGlobal(s) {
|
|
1561
|
+
let out = "";
|
|
1562
|
+
let i = 0;
|
|
1563
|
+
while (i < s.length) {
|
|
1564
|
+
if (s.startsWith(":global(", i)) {
|
|
1565
|
+
i += ":global(".length;
|
|
1566
|
+
let depth = 1;
|
|
1567
|
+
while (i < s.length && depth > 0) {
|
|
1568
|
+
const c = s[i];
|
|
1569
|
+
if (c === "(") depth++;
|
|
1570
|
+
else if (c === ")") {
|
|
1571
|
+
depth--;
|
|
1572
|
+
if (depth === 0) {
|
|
1573
|
+
i++;
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
out += c;
|
|
1578
|
+
i++;
|
|
1579
|
+
}
|
|
1580
|
+
} else {
|
|
1581
|
+
out += s[i++];
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return out;
|
|
1585
|
+
}
|
|
1586
|
+
function splitTopLevel2(s, sep4) {
|
|
1587
|
+
const out = [];
|
|
1588
|
+
let depth = 0;
|
|
1589
|
+
let last = 0;
|
|
1590
|
+
for (let i = 0; i < s.length; i++) {
|
|
1591
|
+
const c = s[i];
|
|
1592
|
+
if (c === '"' || c === "'") {
|
|
1593
|
+
i = skipString(s, i) - 1;
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
if (c === "(" || c === "[") depth++;
|
|
1597
|
+
else if (c === ")" || c === "]") depth--;
|
|
1598
|
+
else if (c === sep4 && depth === 0) {
|
|
1599
|
+
out.push(s.slice(last, i));
|
|
1600
|
+
last = i + 1;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
out.push(s.slice(last));
|
|
1604
|
+
return out;
|
|
1605
|
+
}
|
|
1606
|
+
function skipString(s, start) {
|
|
1607
|
+
const q2 = s[start];
|
|
1608
|
+
let i = start + 1;
|
|
1609
|
+
while (i < s.length) {
|
|
1610
|
+
const c = s[i];
|
|
1611
|
+
if (c === "\\") {
|
|
1612
|
+
i += 2;
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
if (c === q2) return i + 1;
|
|
1616
|
+
i++;
|
|
1617
|
+
}
|
|
1618
|
+
return s.length;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// packages/compiler/src/infer.ts
|
|
1622
|
+
var FOR_VARS = ["$index", "$count", "$first", "$last", "$even", "$odd"];
|
|
1623
|
+
function inferCtxNames(nodes) {
|
|
1624
|
+
const used = /* @__PURE__ */ new Set();
|
|
1625
|
+
const declared = /* @__PURE__ */ new Set();
|
|
1626
|
+
const add = (expr) => {
|
|
1627
|
+
if (expr) for (const id of freeIdentifiers(expr)) used.add(id);
|
|
1628
|
+
};
|
|
1629
|
+
const walk2 = (list) => {
|
|
1630
|
+
for (const node of list) {
|
|
1631
|
+
switch (node.type) {
|
|
1632
|
+
case "text":
|
|
1633
|
+
break;
|
|
1634
|
+
case "interp":
|
|
1635
|
+
add(node.expr);
|
|
1636
|
+
break;
|
|
1637
|
+
case "let":
|
|
1638
|
+
add(node.expr);
|
|
1639
|
+
declared.add(node.name);
|
|
1640
|
+
break;
|
|
1641
|
+
case "element":
|
|
1642
|
+
for (const attr of node.attrs) {
|
|
1643
|
+
if (attr.type === "use") add(attr.name);
|
|
1644
|
+
if (attr.type === "transition") add(attr.name);
|
|
1645
|
+
if (attr.type !== "static") add(attr.expr);
|
|
1646
|
+
}
|
|
1647
|
+
walk2(node.children);
|
|
1648
|
+
break;
|
|
1649
|
+
case "if":
|
|
1650
|
+
for (const br of node.branches) {
|
|
1651
|
+
add(br.cond);
|
|
1652
|
+
if (br.alias) declared.add(br.alias);
|
|
1653
|
+
walk2(br.children);
|
|
1654
|
+
}
|
|
1655
|
+
break;
|
|
1656
|
+
case "for":
|
|
1657
|
+
add(node.list);
|
|
1658
|
+
add(node.track);
|
|
1659
|
+
declared.add(node.item);
|
|
1660
|
+
for (const v of FOR_VARS) declared.add(v);
|
|
1661
|
+
walk2(node.children);
|
|
1662
|
+
if (node.empty) walk2(node.empty);
|
|
1663
|
+
break;
|
|
1664
|
+
case "switch":
|
|
1665
|
+
add(node.expr);
|
|
1666
|
+
for (const c of node.cases) {
|
|
1667
|
+
add(c.test);
|
|
1668
|
+
walk2(c.children);
|
|
1669
|
+
}
|
|
1670
|
+
break;
|
|
1671
|
+
case "defer":
|
|
1672
|
+
if (node.trigger.kind === "when") add(node.trigger.expr);
|
|
1673
|
+
if (node.trigger.kind === "timer") add(node.trigger.ms);
|
|
1674
|
+
walk2(node.children);
|
|
1675
|
+
if (node.placeholder) walk2(node.placeholder);
|
|
1676
|
+
break;
|
|
1677
|
+
case "await":
|
|
1678
|
+
add(node.expr);
|
|
1679
|
+
if (node.pending) walk2(node.pending);
|
|
1680
|
+
if (node.then) {
|
|
1681
|
+
if (node.then.alias) declared.add(node.then.alias);
|
|
1682
|
+
walk2(node.then.children);
|
|
1683
|
+
}
|
|
1684
|
+
if (node.catch) {
|
|
1685
|
+
if (node.catch.alias) declared.add(node.catch.alias);
|
|
1686
|
+
walk2(node.catch.children);
|
|
1687
|
+
}
|
|
1688
|
+
break;
|
|
1689
|
+
case "snippet":
|
|
1690
|
+
declared.add(node.name);
|
|
1691
|
+
for (const p of node.params) declared.add(p);
|
|
1692
|
+
walk2(node.children);
|
|
1693
|
+
break;
|
|
1694
|
+
case "render":
|
|
1695
|
+
add(node.expr);
|
|
1696
|
+
break;
|
|
1697
|
+
case "key":
|
|
1698
|
+
add(node.expr);
|
|
1699
|
+
walk2(node.children);
|
|
1700
|
+
break;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
walk2(nodes);
|
|
1705
|
+
return [...used].filter((n) => !declared.has(n)).sort();
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// packages/compiler/src/component.ts
|
|
1709
|
+
var HAS_SETUP = /export\s+(?:async\s+)?function\s+setup\b|export\s+(?:const|let|var)\s+setup\b/;
|
|
1710
|
+
function compileComponent(src, opts = {}) {
|
|
1711
|
+
const hash = opts.hash ?? hashCss(opts.filename ?? src.template);
|
|
1712
|
+
const attr = scopeAttr(hash);
|
|
1713
|
+
const scope = inferCtxNames(parseTemplate(src.template));
|
|
1714
|
+
const host = src.styles && /:host\b/.test(src.styles) ? hostAttr(hash) : void 0;
|
|
1715
|
+
const compiled = compileTemplate(src.template, { mode: "module", scope, scopeAttr: attr, hostAttr: host });
|
|
1716
|
+
const renderBody = compiled.code.replace("export default function render", "function render");
|
|
1717
|
+
const css = src.styles ? scopeCss(src.styles, hash) : "";
|
|
1718
|
+
const script = src.script ?? "";
|
|
1719
|
+
const setupArg = HAS_SETUP.test(script) ? "render, setup" : "render";
|
|
1720
|
+
const code = [
|
|
1721
|
+
script.trim(),
|
|
1722
|
+
'import { defineComponent } from "@weave-framework/runtime/dom";',
|
|
1723
|
+
renderBody,
|
|
1724
|
+
`export default defineComponent(${setupArg});`
|
|
1725
|
+
].filter(Boolean).join("\n\n");
|
|
1726
|
+
return { code, css, hash };
|
|
1727
|
+
}
|
|
1728
|
+
function parseSfcLoc(source) {
|
|
1729
|
+
const script = locateBlock(source, "script");
|
|
1730
|
+
const style = locateBlock(source, "style");
|
|
1731
|
+
let template = source;
|
|
1732
|
+
for (const b of [script, style]) {
|
|
1733
|
+
if (b) template = blankRange(template, b.rawStart, b.rawEnd);
|
|
1734
|
+
}
|
|
1735
|
+
return {
|
|
1736
|
+
script: script?.inner || void 0,
|
|
1737
|
+
scriptLine: script ? lineAt(source, script.innerStart) : 0,
|
|
1738
|
+
scriptOffset: script ? script.innerStart : 0,
|
|
1739
|
+
template,
|
|
1740
|
+
styleOffset: style ? style.innerStart : 0,
|
|
1741
|
+
styles: style?.inner || void 0
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function locateBlock(source, tag) {
|
|
1745
|
+
const open = source.search(new RegExp(`<${tag}(\\s[^>]*)?>`, "i"));
|
|
1746
|
+
if (open === -1) return null;
|
|
1747
|
+
const gt = source.indexOf(">", open);
|
|
1748
|
+
const close = source.toLowerCase().indexOf(`</${tag}>`, gt);
|
|
1749
|
+
if (close === -1) return null;
|
|
1750
|
+
const rawInner = source.slice(gt + 1, close);
|
|
1751
|
+
const lead = rawInner.length - rawInner.trimStart().length;
|
|
1752
|
+
return {
|
|
1753
|
+
rawStart: open,
|
|
1754
|
+
rawEnd: close + `</${tag}>`.length,
|
|
1755
|
+
innerStart: gt + 1 + lead,
|
|
1756
|
+
inner: rawInner.trim()
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
function blankRange(s, start, end) {
|
|
1760
|
+
let mid = "";
|
|
1761
|
+
for (let i = start; i < end; i++) mid += s[i] === "\n" ? "\n" : " ";
|
|
1762
|
+
return s.slice(0, start) + mid + s.slice(end);
|
|
1763
|
+
}
|
|
1764
|
+
function lineAt(s, offset) {
|
|
1765
|
+
let line = 0;
|
|
1766
|
+
for (let i = 0; i < offset && i < s.length; i++) if (s[i] === "\n") line++;
|
|
1767
|
+
return line;
|
|
1768
|
+
}
|
|
1769
|
+
function parseSfc(source) {
|
|
1770
|
+
const script = extractBlock(source, "script");
|
|
1771
|
+
const style = extractBlock(source, "style");
|
|
1772
|
+
const template = source.replace(script.raw, "").replace(style.raw, "").trim();
|
|
1773
|
+
return {
|
|
1774
|
+
script: script.inner || void 0,
|
|
1775
|
+
template,
|
|
1776
|
+
styles: style.inner || void 0
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
function extractBlock(source, tag) {
|
|
1780
|
+
const open = source.search(new RegExp(`<${tag}(\\s[^>]*)?>`, "i"));
|
|
1781
|
+
if (open === -1) return { raw: "", inner: "" };
|
|
1782
|
+
const gt = source.indexOf(">", open);
|
|
1783
|
+
const close = source.toLowerCase().indexOf(`</${tag}>`, gt);
|
|
1784
|
+
if (close === -1) return { raw: "", inner: "" };
|
|
1785
|
+
const end = close + `</${tag}>`.length;
|
|
1786
|
+
return { raw: source.slice(open, end), inner: source.slice(gt + 1, close).trim() };
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// packages/compiler/src/sources.ts
|
|
1790
|
+
var DECL = /export\s+const\s+(template|styles)\s*(?::[^=]+)?=\s*/g;
|
|
1791
|
+
function extractSources(script) {
|
|
1792
|
+
let template;
|
|
1793
|
+
let templateRange;
|
|
1794
|
+
let styles;
|
|
1795
|
+
const blanks = [];
|
|
1796
|
+
DECL.lastIndex = 0;
|
|
1797
|
+
let m;
|
|
1798
|
+
while ((m = DECL.exec(script)) !== null) {
|
|
1799
|
+
const kind = m[1];
|
|
1800
|
+
const valueStart = m.index + m[0].length;
|
|
1801
|
+
const parsed = parseLiteral(script, valueStart, kind);
|
|
1802
|
+
if (kind === "template") {
|
|
1803
|
+
if (Array.isArray(parsed.value)) throw new Error("weave: `template` must be a single string, not an array");
|
|
1804
|
+
template = parsed.value;
|
|
1805
|
+
if (parsed.innerStart !== void 0 && parsed.innerEnd !== void 0) {
|
|
1806
|
+
templateRange = [parsed.innerStart, parsed.innerEnd];
|
|
1807
|
+
}
|
|
1808
|
+
} else {
|
|
1809
|
+
styles = Array.isArray(parsed.value) ? parsed.value : [parsed.value];
|
|
1810
|
+
}
|
|
1811
|
+
let end = parsed.end;
|
|
1812
|
+
while (end < script.length && /\s/.test(script[end]) && script[end] !== "\n") end++;
|
|
1813
|
+
if (script[end] === ";") end++;
|
|
1814
|
+
blanks.push([m.index, end]);
|
|
1815
|
+
DECL.lastIndex = end;
|
|
1816
|
+
}
|
|
1817
|
+
return { template, templateRange, styles, script: blanks.length ? blank(script, blanks) : script };
|
|
1818
|
+
}
|
|
1819
|
+
function parseLiteral(src, i, kind) {
|
|
1820
|
+
i = skipWs(src, i);
|
|
1821
|
+
const c = src[i];
|
|
1822
|
+
if (c === '"' || c === "'" || c === "`") return parseString(src, i);
|
|
1823
|
+
if (c === "[") return parseArray(src, i);
|
|
1824
|
+
throw new Error(`weave: \`${kind}\` must be a static string${kind === "styles" ? " or array of strings" : ""}`);
|
|
1825
|
+
}
|
|
1826
|
+
function parseString(src, i) {
|
|
1827
|
+
const quote = src[i];
|
|
1828
|
+
const innerStart = i + 1;
|
|
1829
|
+
let out = "";
|
|
1830
|
+
let j = innerStart;
|
|
1831
|
+
for (; j < src.length; j++) {
|
|
1832
|
+
const ch = src[j];
|
|
1833
|
+
if (ch === "\\") {
|
|
1834
|
+
out += src[j + 1] ?? "";
|
|
1835
|
+
j++;
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (quote === "`" && ch === "$" && src[j + 1] === "{") {
|
|
1839
|
+
throw new Error("weave: inline template/styles cannot use ${\u2026} \u2014 Weave binds with {expr}, not JS interpolation");
|
|
1840
|
+
}
|
|
1841
|
+
if (ch === quote) return { value: out, end: j + 1, innerStart, innerEnd: j };
|
|
1842
|
+
out += ch;
|
|
1843
|
+
}
|
|
1844
|
+
throw new Error("weave: unterminated string literal in template/styles declaration");
|
|
1845
|
+
}
|
|
1846
|
+
function parseArray(src, i) {
|
|
1847
|
+
const items = [];
|
|
1848
|
+
let j = i + 1;
|
|
1849
|
+
for (; ; ) {
|
|
1850
|
+
j = skipWs(src, j);
|
|
1851
|
+
if (src[j] === "]") return { value: items, end: j + 1 };
|
|
1852
|
+
if (j >= src.length) throw new Error("weave: unterminated array in styles declaration");
|
|
1853
|
+
if (src[j] === ",") {
|
|
1854
|
+
j++;
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
const str = parseString(src, j);
|
|
1858
|
+
items.push(str.value);
|
|
1859
|
+
j = str.end;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
function skipWs(src, i) {
|
|
1863
|
+
while (i < src.length && /\s/.test(src[i])) i++;
|
|
1864
|
+
return i;
|
|
1865
|
+
}
|
|
1866
|
+
function blank(src, ranges) {
|
|
1867
|
+
let out = "";
|
|
1868
|
+
let cursor = 0;
|
|
1869
|
+
for (const [start, end] of ranges) {
|
|
1870
|
+
out += src.slice(cursor, start);
|
|
1871
|
+
for (let k = start; k < end; k++) out += src[k] === "\n" ? "\n" : " ";
|
|
1872
|
+
cursor = end;
|
|
1873
|
+
}
|
|
1874
|
+
return out + src.slice(cursor);
|
|
1875
|
+
}
|
|
1876
|
+
function faithfulTemplate(source, range) {
|
|
1877
|
+
let out = "";
|
|
1878
|
+
for (let i = 0; i < source.length; i++) {
|
|
1879
|
+
out += i >= range[0] && i < range[1] ? source[i] : source[i] === "\n" ? "\n" : " ";
|
|
1880
|
+
}
|
|
1881
|
+
return out;
|
|
1882
|
+
}
|
|
1883
|
+
function classifyTemplate(value) {
|
|
1884
|
+
if (/[<{}\n]/.test(value)) return "inline";
|
|
1885
|
+
if (/[\\/]/.test(value) || /\.html$/i.test(value)) return "file";
|
|
1886
|
+
return "inline";
|
|
1887
|
+
}
|
|
1888
|
+
function classifyStyle(value) {
|
|
1889
|
+
if (/[{}\n]/.test(value)) return "inline";
|
|
1890
|
+
if (/[\\/]/.test(value) || /\.(css|scss|sass)$/i.test(value)) return "file";
|
|
1891
|
+
return "inline";
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// packages/cli/src/styles.ts
|
|
1895
|
+
import { readFile } from "node:fs/promises";
|
|
1896
|
+
import { fileURLToPath } from "node:url";
|
|
1897
|
+
function langFromExt(file) {
|
|
1898
|
+
if (file.endsWith(".scss")) return "scss";
|
|
1899
|
+
if (file.endsWith(".sass")) return "sass";
|
|
1900
|
+
return "css";
|
|
1901
|
+
}
|
|
1902
|
+
async function compileStyleFile(path) {
|
|
1903
|
+
if (langFromExt(path) === "css") return readFile(path, "utf8");
|
|
1904
|
+
const sass = await import("sass");
|
|
1905
|
+
return sass.compile(path).css;
|
|
1906
|
+
}
|
|
1907
|
+
async function compileStyleFileTracked(path) {
|
|
1908
|
+
if (langFromExt(path) === "css") return { css: await readFile(path, "utf8"), files: [path] };
|
|
1909
|
+
const sass = await import("sass");
|
|
1910
|
+
const result = sass.compile(path);
|
|
1911
|
+
const files = result.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath(u));
|
|
1912
|
+
return { css: result.css, files };
|
|
1913
|
+
}
|
|
1914
|
+
async function compileStyleSource(source, lang, fromDir) {
|
|
1915
|
+
if (lang === "css" || !source.trim()) return source;
|
|
1916
|
+
const sass = await import("sass");
|
|
1917
|
+
return sass.compileString(source, {
|
|
1918
|
+
syntax: lang === "sass" ? "indented" : "scss",
|
|
1919
|
+
loadPaths: fromDir ? [fromDir] : []
|
|
1920
|
+
}).css;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// packages/cli/src/plugin.ts
|
|
1924
|
+
function cssInjector(css) {
|
|
1925
|
+
if (!css) return "";
|
|
1926
|
+
return `
|
|
1927
|
+
;(()=>{const s=document.createElement("style");s.textContent=${JSON.stringify(
|
|
1928
|
+
css
|
|
1929
|
+
)};document.head.appendChild(s);})();
|
|
1930
|
+
`;
|
|
1931
|
+
}
|
|
1932
|
+
async function resolveTemplate(decl, tsPath, siblingHtml, hasSiblingHtml) {
|
|
1933
|
+
if (decl.template !== void 0) {
|
|
1934
|
+
if (hasSiblingHtml) {
|
|
1935
|
+
throw new Error(
|
|
1936
|
+
`weave: ${tsPath} declares \`template\` and also has a sibling .html \u2014 remove one`
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
if (classifyTemplate(decl.template) === "inline") return { text: decl.template, files: [] };
|
|
1940
|
+
const file = resolve(dirname(tsPath), decl.template);
|
|
1941
|
+
if (!existsSync(file)) throw new Error(`weave: template file not found: ${file} (from ${tsPath})`);
|
|
1942
|
+
return { text: await readFile2(file, "utf8"), files: [file] };
|
|
1943
|
+
}
|
|
1944
|
+
return { text: await readFile2(siblingHtml, "utf8"), files: [siblingHtml] };
|
|
1945
|
+
}
|
|
1946
|
+
async function resolveStyles(decl, tsPath, dir, styleLang) {
|
|
1947
|
+
if (decl.styles !== void 0) {
|
|
1948
|
+
const siblingStyle2 = tsPath.replace(/\.ts$/, "." + styleLang);
|
|
1949
|
+
if (existsSync(siblingStyle2)) {
|
|
1950
|
+
throw new Error(
|
|
1951
|
+
`weave: ${tsPath} declares \`styles\` and also has a sibling .${styleLang} \u2014 remove one`
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
const parts = [];
|
|
1955
|
+
const files = [];
|
|
1956
|
+
for (const entry of decl.styles) {
|
|
1957
|
+
if (classifyStyle(entry) === "inline") {
|
|
1958
|
+
parts.push(await compileStyleSource(entry, styleLang, dir));
|
|
1959
|
+
} else {
|
|
1960
|
+
const file = resolve(dir, entry);
|
|
1961
|
+
if (!existsSync(file)) throw new Error(`weave: style file not found: ${file} (from ${tsPath})`);
|
|
1962
|
+
const compiled2 = await compileStyleFileTracked(file);
|
|
1963
|
+
parts.push(compiled2.css);
|
|
1964
|
+
files.push(...compiled2.files);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return { css: parts.join("\n"), files };
|
|
1968
|
+
}
|
|
1969
|
+
const siblingStyle = tsPath.replace(/\.ts$/, "." + styleLang);
|
|
1970
|
+
if (!existsSync(siblingStyle)) return { css: void 0, files: [] };
|
|
1971
|
+
const compiled = await compileStyleFileTracked(siblingStyle);
|
|
1972
|
+
return { css: compiled.css, files: compiled.files };
|
|
1973
|
+
}
|
|
1974
|
+
function weave(state, options = {}) {
|
|
1975
|
+
const styleLang = options.styleLang ?? "css";
|
|
1976
|
+
const dev2 = options.dev ?? false;
|
|
1977
|
+
const emit2 = (code, css, resolveDir) => {
|
|
1978
|
+
if (dev2) return { contents: code + cssInjector(css), loader: "ts", resolveDir };
|
|
1979
|
+
if (css) state.css.push(css);
|
|
1980
|
+
return { contents: code, loader: "ts", resolveDir };
|
|
1981
|
+
};
|
|
1982
|
+
return {
|
|
1983
|
+
name: "weave",
|
|
1984
|
+
setup(build2) {
|
|
1985
|
+
build2.onStart(() => {
|
|
1986
|
+
state.css.length = 0;
|
|
1987
|
+
});
|
|
1988
|
+
build2.onLoad({ filter: /\.weave$/ }, async (args) => {
|
|
1989
|
+
const source = await readFile2(args.path, "utf8");
|
|
1990
|
+
const src = parseSfc(source);
|
|
1991
|
+
const styles = src.styles ? await compileStyleSource(src.styles, styleLang, dirname(args.path)) : void 0;
|
|
1992
|
+
const { code, css } = compileComponent({ ...src, styles }, { filename: args.path });
|
|
1993
|
+
return emit2(code, css, dirname(args.path));
|
|
1994
|
+
});
|
|
1995
|
+
build2.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
1996
|
+
if (args.path.includes("node_modules")) return void 0;
|
|
1997
|
+
if (args.path.endsWith(".gen.ts")) return void 0;
|
|
1998
|
+
const source = await readFile2(args.path, "utf8");
|
|
1999
|
+
const decl = extractSources(source);
|
|
2000
|
+
const siblingHtml = args.path.replace(/\.ts$/, ".html");
|
|
2001
|
+
const hasSiblingHtml = existsSync(siblingHtml);
|
|
2002
|
+
if (decl.template === void 0 && !hasSiblingHtml) return void 0;
|
|
2003
|
+
const dir = dirname(args.path);
|
|
2004
|
+
const template = await resolveTemplate(
|
|
2005
|
+
decl,
|
|
2006
|
+
args.path,
|
|
2007
|
+
siblingHtml,
|
|
2008
|
+
hasSiblingHtml
|
|
2009
|
+
);
|
|
2010
|
+
const styles = await resolveStyles(
|
|
2011
|
+
decl,
|
|
2012
|
+
args.path,
|
|
2013
|
+
dir,
|
|
2014
|
+
styleLang
|
|
2015
|
+
);
|
|
2016
|
+
const { code, css } = compileComponent(
|
|
2017
|
+
{ script: decl.script, template: template.text, styles: styles.css },
|
|
2018
|
+
{ filename: args.path }
|
|
2019
|
+
);
|
|
2020
|
+
return { ...emit2(code, css, dir), watchFiles: [...template.files, ...styles.files] };
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// packages/cli/src/entry.ts
|
|
2027
|
+
import { readdirSync, readFileSync, statSync, existsSync as existsSync2 } from "node:fs";
|
|
2028
|
+
import { join, relative, sep } from "node:path";
|
|
2029
|
+
var VIRTUAL_ENTRY = "weave-virtual-entry";
|
|
2030
|
+
var SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".weave"]);
|
|
2031
|
+
var TAG_RE = /export\s+const\s+tag\s*(?::[^=]+)?=\s*(['"`])([^'"`]*)\1/;
|
|
2032
|
+
var PROPS_RE = /export\s+const\s+props\s*(?::[^=]+)?=\s*\[([^\]]*)\]/;
|
|
2033
|
+
var STR_RE = /(['"`])([^'"`]*)\1/g;
|
|
2034
|
+
function discoverCustomElements(rootDir) {
|
|
2035
|
+
const found = [];
|
|
2036
|
+
walk(rootDir, found);
|
|
2037
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2038
|
+
for (const ce of found) {
|
|
2039
|
+
const prev = seen.get(ce.tag);
|
|
2040
|
+
if (prev) {
|
|
2041
|
+
throw new Error(`weave: custom element tag "${ce.tag}" declared twice \u2014 ${prev} and ${ce.file}`);
|
|
2042
|
+
}
|
|
2043
|
+
seen.set(ce.tag, ce.file);
|
|
2044
|
+
}
|
|
2045
|
+
return found;
|
|
2046
|
+
}
|
|
2047
|
+
function walk(path, out) {
|
|
2048
|
+
if (!existsSync2(path)) return;
|
|
2049
|
+
const st = statSync(path);
|
|
2050
|
+
if (st.isDirectory()) {
|
|
2051
|
+
for (const entry of readdirSync(path)) {
|
|
2052
|
+
if (!SKIP.has(entry)) walk(join(path, entry), out);
|
|
2053
|
+
}
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (!path.endsWith(".ts") || path.endsWith(".d.ts") || path.endsWith(".gen.ts")) return;
|
|
2057
|
+
const src = readFileSync(path, "utf8");
|
|
2058
|
+
const m = src.match(TAG_RE);
|
|
2059
|
+
if (!m) return;
|
|
2060
|
+
const tag = m[2];
|
|
2061
|
+
if (!tag.includes("-")) {
|
|
2062
|
+
throw new Error(
|
|
2063
|
+
`weave: custom element tag "${tag}" in ${path} must contain a hyphen (Custom Elements spec)`
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
out.push({ tag, file: path, props: extractProps(src) });
|
|
2067
|
+
}
|
|
2068
|
+
function extractProps(src) {
|
|
2069
|
+
const m = src.match(PROPS_RE);
|
|
2070
|
+
if (!m) return [];
|
|
2071
|
+
const props = [];
|
|
2072
|
+
let s;
|
|
2073
|
+
STR_RE.lastIndex = 0;
|
|
2074
|
+
while ((s = STR_RE.exec(m[1])) !== null) props.push(s[2]);
|
|
2075
|
+
return props;
|
|
2076
|
+
}
|
|
2077
|
+
function generateEntry(rootComponent, mount, rootDir, elements) {
|
|
2078
|
+
const spec = (file) => {
|
|
2079
|
+
const r = relative(rootDir, file).split(sep).join("/").replace(/\.ts$/, "");
|
|
2080
|
+
return JSON.stringify(r.startsWith(".") ? r : "./" + r);
|
|
2081
|
+
};
|
|
2082
|
+
const lines = [`import Root from ${spec(rootComponent)};`];
|
|
2083
|
+
elements.forEach((ce, i) => lines.push(`import __ce${i} from ${spec(ce.file)};`));
|
|
2084
|
+
lines.push('import { mountComponent, defineCustomElement } from "@weave-framework/runtime/dom";');
|
|
2085
|
+
elements.forEach(
|
|
2086
|
+
(ce, i) => lines.push(`defineCustomElement(${JSON.stringify(ce.tag)}, __ce${i}, { props: ${JSON.stringify(ce.props)} });`)
|
|
2087
|
+
);
|
|
2088
|
+
lines.push(`mountComponent(Root, ${JSON.stringify(mount)});`);
|
|
2089
|
+
return lines.join("\n");
|
|
2090
|
+
}
|
|
2091
|
+
function entryPlugin(code, resolveDir) {
|
|
2092
|
+
return {
|
|
2093
|
+
name: "weave:entry",
|
|
2094
|
+
setup(build2) {
|
|
2095
|
+
build2.onResolve({ filter: new RegExp(`^${VIRTUAL_ENTRY}$`) }, (args) => ({
|
|
2096
|
+
path: args.path,
|
|
2097
|
+
namespace: "weave-entry"
|
|
2098
|
+
}));
|
|
2099
|
+
build2.onLoad({ filter: /.*/, namespace: "weave-entry" }, () => ({
|
|
2100
|
+
contents: code,
|
|
2101
|
+
loader: "ts",
|
|
2102
|
+
resolveDir
|
|
2103
|
+
}));
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// packages/cli/src/html.ts
|
|
2109
|
+
function escapeRe(s) {
|
|
2110
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2111
|
+
}
|
|
2112
|
+
function stripLiveReload(html) {
|
|
2113
|
+
return html.replace(
|
|
2114
|
+
/[ \t]*<script>(?:(?!<\/script>)[\s\S])*?EventSource\([^)]*reload[^)]*\)[\s\S]*?<\/script>\n?/gi,
|
|
2115
|
+
""
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
function injectHtml(html, opts) {
|
|
2119
|
+
let out = stripLiveReload(html);
|
|
2120
|
+
if (opts.css && !new RegExp(`<link[^>]+href=["']${escapeRe(opts.css)}["']`, "i").test(out)) {
|
|
2121
|
+
out = out.replace(/<\/head>/i, ` <link rel="stylesheet" href="${opts.css}" />
|
|
2122
|
+
</head>`);
|
|
2123
|
+
}
|
|
2124
|
+
if (!new RegExp(`<script[^>]+src=["']${escapeRe(opts.script)}["']`, "i").test(out)) {
|
|
2125
|
+
out = out.replace(
|
|
2126
|
+
/<\/body>/i,
|
|
2127
|
+
` <script type="module" src="${opts.script}"></script>
|
|
2128
|
+
</body>`
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
if (opts.liveReload) {
|
|
2132
|
+
const client = `<script>new EventSource(${JSON.stringify(
|
|
2133
|
+
opts.liveReload
|
|
2134
|
+
)}).addEventListener("message",()=>location.reload());</script>`;
|
|
2135
|
+
out = out.replace(/<\/body>/i, ` ${client}
|
|
2136
|
+
</body>`);
|
|
2137
|
+
}
|
|
2138
|
+
return out;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
// packages/cli/src/build.ts
|
|
2142
|
+
async function build(config) {
|
|
2143
|
+
const { outDir } = config;
|
|
2144
|
+
if (config.clean) await rm(outDir, { recursive: true, force: true });
|
|
2145
|
+
const state = { css: [] };
|
|
2146
|
+
const ve = config.virtualEntry;
|
|
2147
|
+
await esbuild({
|
|
2148
|
+
// A virtual entry (Level C) is emitted as `main.js`; else the hand-written entry.
|
|
2149
|
+
entryPoints: ve ? [{ in: VIRTUAL_ENTRY, out: "main" }] : [config.entry],
|
|
2150
|
+
bundle: true,
|
|
2151
|
+
format: "esm",
|
|
2152
|
+
// Code-split dynamic import()s into separate chunks, so `lazy()` routes are
|
|
2153
|
+
// actually their own files and <Link> prefetch (B.15) has something to warm.
|
|
2154
|
+
splitting: true,
|
|
2155
|
+
outdir: outDir,
|
|
2156
|
+
minify: config.minify ?? true,
|
|
2157
|
+
plugins: [
|
|
2158
|
+
weave(state, { styleLang: config.styleLang }),
|
|
2159
|
+
...ve ? [entryPlugin(ve.code, ve.resolveDir)] : []
|
|
2160
|
+
]
|
|
2161
|
+
});
|
|
2162
|
+
if (config.publicDir && existsSync3(config.publicDir)) {
|
|
2163
|
+
await cp(config.publicDir, outDir, { recursive: true });
|
|
2164
|
+
}
|
|
2165
|
+
const globalCss = (await Promise.all((config.styles ?? []).map(compileStyleFile))).join(
|
|
2166
|
+
"\n"
|
|
2167
|
+
);
|
|
2168
|
+
await mkdir(outDir, { recursive: true });
|
|
2169
|
+
await writeFile(join2(outDir, "app.css"), [globalCss, ...state.css].filter(Boolean).join("\n"));
|
|
2170
|
+
if (config.index) {
|
|
2171
|
+
const html = injectHtml(await readFile3(config.index, "utf8"), {
|
|
2172
|
+
script: "/main.js",
|
|
2173
|
+
css: "/app.css"
|
|
2174
|
+
});
|
|
2175
|
+
await writeFile(join2(outDir, "index.html"), html);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// packages/cli/src/dev.ts
|
|
2180
|
+
import { context } from "esbuild";
|
|
2181
|
+
import { createServer } from "node:http";
|
|
2182
|
+
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4 } from "node:fs/promises";
|
|
2183
|
+
import { join as join3, extname, relative as relative2, sep as sep2 } from "node:path";
|
|
2184
|
+
var RELOAD_PATH = "/__weave_reload";
|
|
2185
|
+
var MIME = {
|
|
2186
|
+
".html": "text/html; charset=utf-8",
|
|
2187
|
+
".js": "text/javascript; charset=utf-8",
|
|
2188
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
2189
|
+
".css": "text/css; charset=utf-8",
|
|
2190
|
+
".json": "application/json; charset=utf-8",
|
|
2191
|
+
".webmanifest": "application/manifest+json; charset=utf-8",
|
|
2192
|
+
".svg": "image/svg+xml",
|
|
2193
|
+
".png": "image/png",
|
|
2194
|
+
".ico": "image/x-icon",
|
|
2195
|
+
".woff2": "font/woff2"
|
|
2196
|
+
};
|
|
2197
|
+
function mime(path) {
|
|
2198
|
+
return MIME[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
2199
|
+
}
|
|
2200
|
+
async function dev(config) {
|
|
2201
|
+
return config.inMemory ?? false ? devInMemory(config) : devLegacy(config);
|
|
2202
|
+
}
|
|
2203
|
+
async function devInMemory(config) {
|
|
2204
|
+
const state = { css: [] };
|
|
2205
|
+
let banner;
|
|
2206
|
+
if (config.styles?.length) {
|
|
2207
|
+
const css = (await Promise.all(config.styles.map(compileStyleFile))).join("\n");
|
|
2208
|
+
if (css)
|
|
2209
|
+
banner = {
|
|
2210
|
+
js: `(()=>{const s=document.createElement("style");s.textContent=${JSON.stringify(
|
|
2211
|
+
css
|
|
2212
|
+
)};document.head.appendChild(s);})();`
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
2216
|
+
const clients = /* @__PURE__ */ new Set();
|
|
2217
|
+
const capture = {
|
|
2218
|
+
name: "weave:dev-capture",
|
|
2219
|
+
setup(build2) {
|
|
2220
|
+
build2.onEnd((result) => {
|
|
2221
|
+
outputs.clear();
|
|
2222
|
+
for (const file of result.outputFiles ?? []) {
|
|
2223
|
+
const rel = relative2(config.outdir, file.path).split(sep2).join("/");
|
|
2224
|
+
outputs.set("/" + rel, file.contents);
|
|
2225
|
+
}
|
|
2226
|
+
for (const res of clients) res.write("data: reload\n\n");
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
const ve = config.virtualEntry;
|
|
2231
|
+
const ctx = await context({
|
|
2232
|
+
entryPoints: ve ? [{ in: VIRTUAL_ENTRY, out: "main" }] : [config.entry],
|
|
2233
|
+
bundle: true,
|
|
2234
|
+
format: "esm",
|
|
2235
|
+
splitting: true,
|
|
2236
|
+
outdir: config.outdir,
|
|
2237
|
+
write: false,
|
|
2238
|
+
// everything stays in memory — dev creates no dist/
|
|
2239
|
+
banner,
|
|
2240
|
+
plugins: [
|
|
2241
|
+
weave(state, { styleLang: config.styleLang, dev: true }),
|
|
2242
|
+
...ve ? [entryPlugin(ve.code, ve.resolveDir)] : [],
|
|
2243
|
+
capture
|
|
2244
|
+
]
|
|
2245
|
+
});
|
|
2246
|
+
await ctx.watch();
|
|
2247
|
+
const server = createServer((req, res) => {
|
|
2248
|
+
void handleRequest(req, res, config, outputs, clients);
|
|
2249
|
+
});
|
|
2250
|
+
const port = await listen(server, config.port);
|
|
2251
|
+
return { ctx, url: `http://127.0.0.1:${port}` };
|
|
2252
|
+
}
|
|
2253
|
+
async function handleRequest(req, res, config, outputs, clients) {
|
|
2254
|
+
const url = (req.url ?? "/").split("?")[0];
|
|
2255
|
+
if (url === RELOAD_PATH) {
|
|
2256
|
+
res.writeHead(200, {
|
|
2257
|
+
"content-type": "text/event-stream",
|
|
2258
|
+
"cache-control": "no-cache",
|
|
2259
|
+
connection: "keep-alive"
|
|
2260
|
+
});
|
|
2261
|
+
res.write("\n");
|
|
2262
|
+
clients.add(res);
|
|
2263
|
+
req.on("close", () => {
|
|
2264
|
+
clients.delete(res);
|
|
2265
|
+
});
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
const built = outputs.get(url);
|
|
2269
|
+
if (built) {
|
|
2270
|
+
res.writeHead(200, { "content-type": mime(url) });
|
|
2271
|
+
res.end(Buffer.from(built));
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
if (extname(url)) {
|
|
2275
|
+
try {
|
|
2276
|
+
const buf = await readFile4(join3(config.servedir, url));
|
|
2277
|
+
res.writeHead(200, { "content-type": mime(url) });
|
|
2278
|
+
res.end(buf);
|
|
2279
|
+
} catch {
|
|
2280
|
+
res.writeHead(404);
|
|
2281
|
+
res.end("Not found");
|
|
2282
|
+
}
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
try {
|
|
2286
|
+
const shell = config.index ?? join3(config.servedir, "index.html");
|
|
2287
|
+
const html = injectHtml(await readFile4(shell, "utf8"), {
|
|
2288
|
+
script: "/main.js",
|
|
2289
|
+
liveReload: RELOAD_PATH
|
|
2290
|
+
});
|
|
2291
|
+
res.writeHead(200, { "content-type": MIME[".html"] });
|
|
2292
|
+
res.end(html);
|
|
2293
|
+
} catch {
|
|
2294
|
+
res.writeHead(500);
|
|
2295
|
+
res.end("No index.html");
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
function listen(server, port) {
|
|
2299
|
+
return new Promise((resolve4) => {
|
|
2300
|
+
server.listen(port ?? 0, "127.0.0.1", () => {
|
|
2301
|
+
const addr = server.address();
|
|
2302
|
+
resolve4(typeof addr === "object" && addr ? addr.port : port ?? 0);
|
|
2303
|
+
});
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2306
|
+
async function devLegacy(config) {
|
|
2307
|
+
const state = { css: [] };
|
|
2308
|
+
const plugins = [
|
|
2309
|
+
weave(state, { styleLang: config.styleLang }),
|
|
2310
|
+
{
|
|
2311
|
+
name: "weave:css",
|
|
2312
|
+
setup(build2) {
|
|
2313
|
+
build2.onEnd(async () => {
|
|
2314
|
+
await mkdir2(config.outdir, { recursive: true });
|
|
2315
|
+
await writeFile2(join3(config.outdir, "app.css"), state.css.join("\n"));
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
];
|
|
2320
|
+
const ctx = await context({
|
|
2321
|
+
entryPoints: [config.entry],
|
|
2322
|
+
// legacy mode always has a hand-written entry
|
|
2323
|
+
bundle: true,
|
|
2324
|
+
format: "esm",
|
|
2325
|
+
splitting: true,
|
|
2326
|
+
outdir: config.outdir,
|
|
2327
|
+
plugins
|
|
2328
|
+
});
|
|
2329
|
+
await ctx.watch();
|
|
2330
|
+
const { hosts, port } = await ctx.serve({
|
|
2331
|
+
servedir: config.servedir,
|
|
2332
|
+
fallback: join3(config.servedir, "index.html"),
|
|
2333
|
+
port: config.port,
|
|
2334
|
+
host: "127.0.0.1"
|
|
2335
|
+
});
|
|
2336
|
+
return { ctx, url: `http://${hosts[0] ?? "127.0.0.1"}:${port}` };
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// packages/cli/src/routes.ts
|
|
2340
|
+
import { readdirSync as readdirSync2, writeFileSync } from "node:fs";
|
|
2341
|
+
import { join as join4, relative as relative3, sep as sep3 } from "node:path";
|
|
2342
|
+
|
|
2343
|
+
// packages/router/src/files.ts
|
|
2344
|
+
var EXT = /\.(weave|tsx?|jsx?)$/;
|
|
2345
|
+
var baseName = (file) => {
|
|
2346
|
+
const slash = file.lastIndexOf("/");
|
|
2347
|
+
return file.slice(slash + 1).replace(EXT, "");
|
|
2348
|
+
};
|
|
2349
|
+
function segment(name) {
|
|
2350
|
+
if (name === "index") return "";
|
|
2351
|
+
const catchAll = /^\[\.\.\.(.+)]$/.exec(name);
|
|
2352
|
+
if (catchAll) return "*";
|
|
2353
|
+
const dynamic = /^\[(.+)]$/.exec(name);
|
|
2354
|
+
if (dynamic) return `:${dynamic[1]}`;
|
|
2355
|
+
return name;
|
|
2356
|
+
}
|
|
2357
|
+
var emptyTree = () => ({ files: {}, dirs: {} });
|
|
2358
|
+
function segSpecificity(seg) {
|
|
2359
|
+
if (seg === "*" || seg.startsWith("*")) return 2;
|
|
2360
|
+
if (seg.startsWith(":")) return 1;
|
|
2361
|
+
return 0;
|
|
2362
|
+
}
|
|
2363
|
+
function compareRoutes(a, b) {
|
|
2364
|
+
const as = a.split("/");
|
|
2365
|
+
const bs = b.split("/");
|
|
2366
|
+
const len = Math.max(as.length, bs.length);
|
|
2367
|
+
for (let i = 0; i < len; i++) {
|
|
2368
|
+
const sa = as[i] ?? "";
|
|
2369
|
+
const sb = bs[i] ?? "";
|
|
2370
|
+
const da = segSpecificity(sa);
|
|
2371
|
+
const db = segSpecificity(sb);
|
|
2372
|
+
if (da !== db) return da - db;
|
|
2373
|
+
if (sa !== sb) return sa.localeCompare(sb);
|
|
2374
|
+
}
|
|
2375
|
+
return 0;
|
|
2376
|
+
}
|
|
2377
|
+
function joinPath(dir, child) {
|
|
2378
|
+
return child === "" ? dir : `${dir}/${child}`;
|
|
2379
|
+
}
|
|
2380
|
+
function convert(tree) {
|
|
2381
|
+
const routes = [];
|
|
2382
|
+
for (const [name, file] of Object.entries(tree.files)) {
|
|
2383
|
+
if (name === "_layout") continue;
|
|
2384
|
+
routes.push({ path: segment(name), file });
|
|
2385
|
+
}
|
|
2386
|
+
for (const [dir, sub] of Object.entries(tree.dirs)) {
|
|
2387
|
+
const childRoutes = convert(sub);
|
|
2388
|
+
const layout = sub.files["_layout"];
|
|
2389
|
+
if (layout) {
|
|
2390
|
+
routes.push({ path: dir, file: layout, children: sortRoutes(childRoutes) });
|
|
2391
|
+
} else {
|
|
2392
|
+
for (const r of childRoutes) routes.push({ ...r, path: joinPath(dir, r.path) });
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
return sortRoutes(routes);
|
|
2396
|
+
}
|
|
2397
|
+
function sortRoutes(routes) {
|
|
2398
|
+
return routes.sort((a, b) => compareRoutes(a.path, b.path));
|
|
2399
|
+
}
|
|
2400
|
+
function fileToRoutes(files) {
|
|
2401
|
+
const root = emptyTree();
|
|
2402
|
+
for (const file of files) {
|
|
2403
|
+
const parts = file.split("/");
|
|
2404
|
+
let node = root;
|
|
2405
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2406
|
+
const dir = parts[i];
|
|
2407
|
+
node = node.dirs[dir] ??= emptyTree();
|
|
2408
|
+
}
|
|
2409
|
+
node.files[baseName(parts[parts.length - 1])] = file;
|
|
2410
|
+
}
|
|
2411
|
+
return convert(root);
|
|
2412
|
+
}
|
|
2413
|
+
function emitRoutesModule(routes, opts = {}) {
|
|
2414
|
+
const imports = [];
|
|
2415
|
+
let n = 0;
|
|
2416
|
+
const prefix = opts.importPrefix ?? "./";
|
|
2417
|
+
const spec = (file) => {
|
|
2418
|
+
const withPrefix = /^[./]/.test(file) ? file : prefix + file;
|
|
2419
|
+
return JSON.stringify(withPrefix.replace(/\.[mc]?[jt]sx?$/, ""));
|
|
2420
|
+
};
|
|
2421
|
+
const componentField = (file) => {
|
|
2422
|
+
if (opts.lazy) return `component: lazy(() => import(${spec(file)}))`;
|
|
2423
|
+
const id = `Page${n++}`;
|
|
2424
|
+
imports.push(`import ${id} from ${spec(file)};`);
|
|
2425
|
+
return `component: ${id}`;
|
|
2426
|
+
};
|
|
2427
|
+
const serialize = (list, indent) => {
|
|
2428
|
+
const inner = indent + " ";
|
|
2429
|
+
const items = list.map((r) => {
|
|
2430
|
+
const fields = [`path: ${JSON.stringify(r.path)}`];
|
|
2431
|
+
if (r.file) fields.push(componentField(r.file));
|
|
2432
|
+
if (r.children && r.children.length) {
|
|
2433
|
+
fields.push(`children: ${serialize(r.children, inner)}`);
|
|
2434
|
+
}
|
|
2435
|
+
return `${inner}{ ${fields.join(", ")} }`;
|
|
2436
|
+
});
|
|
2437
|
+
return `[
|
|
2438
|
+
${items.join(",\n")}
|
|
2439
|
+
${indent}]`;
|
|
2440
|
+
};
|
|
2441
|
+
const body = serialize(routes, "");
|
|
2442
|
+
const header = opts.lazy ? `import { lazy } from ${JSON.stringify(opts.runtimeImport ?? "@weave-framework/runtime/dom")};
|
|
2443
|
+
` : "";
|
|
2444
|
+
return `${header}${imports.join("\n")}${imports.length ? "\n" : ""}
|
|
2445
|
+
export const routes = ${body};
|
|
2446
|
+
`;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// packages/cli/src/routes.ts
|
|
2450
|
+
var PAGE = /\.(weave|tsx?|jsx?)$/;
|
|
2451
|
+
var NOT_A_PAGE = /\.(gen|d)\.[mc]?tsx?$/;
|
|
2452
|
+
function scanRoutes(dir) {
|
|
2453
|
+
const out = [];
|
|
2454
|
+
const walk2 = (cur) => {
|
|
2455
|
+
for (const entry of readdirSync2(cur, { withFileTypes: true })) {
|
|
2456
|
+
const full = join4(cur, entry.name);
|
|
2457
|
+
if (entry.isDirectory()) walk2(full);
|
|
2458
|
+
else if (PAGE.test(entry.name) && !NOT_A_PAGE.test(entry.name))
|
|
2459
|
+
out.push(relative3(dir, full).split(sep3).join("/"));
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
walk2(dir);
|
|
2463
|
+
return out.sort();
|
|
2464
|
+
}
|
|
2465
|
+
function generateRoutes(dir, opts = {}) {
|
|
2466
|
+
const files = scanRoutes(dir);
|
|
2467
|
+
const routes = fileToRoutes(files);
|
|
2468
|
+
const module = emitRoutesModule(routes, { lazy: opts.lazy ?? true });
|
|
2469
|
+
const out = opts.out ?? join4(dir, "routes.gen.ts");
|
|
2470
|
+
writeFileSync(out, module);
|
|
2471
|
+
return out;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// packages/cli/src/config.ts
|
|
2475
|
+
import { build as esbuildBuild } from "esbuild";
|
|
2476
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2477
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
2478
|
+
import { resolve as resolve2, dirname as dirname2, join as join5, isAbsolute } from "node:path";
|
|
2479
|
+
function defineConfig(config) {
|
|
2480
|
+
return config;
|
|
2481
|
+
}
|
|
2482
|
+
var CONFIG_NAMES = ["weave.config.ts", "weave.config.js", "weave.config.mjs", "weave.config.json"];
|
|
2483
|
+
async function loadConfig(cwd, explicit) {
|
|
2484
|
+
const file = explicit ? resolve2(cwd, explicit) : CONFIG_NAMES.map((n) => join5(cwd, n)).find((p) => existsSync4(p));
|
|
2485
|
+
if (!file || !existsSync4(file)) return null;
|
|
2486
|
+
const raw = file.endsWith(".json") ? JSON.parse(await readFile5(file, "utf8")) : await importConfigModule(file);
|
|
2487
|
+
return resolveConfig(raw, dirname2(file));
|
|
2488
|
+
}
|
|
2489
|
+
async function importConfigModule(file) {
|
|
2490
|
+
const out = await esbuildBuild({
|
|
2491
|
+
entryPoints: [file],
|
|
2492
|
+
bundle: true,
|
|
2493
|
+
format: "esm",
|
|
2494
|
+
platform: "node",
|
|
2495
|
+
write: false,
|
|
2496
|
+
packages: "external",
|
|
2497
|
+
// keep node_modules external (resolved at import time)
|
|
2498
|
+
plugins: [
|
|
2499
|
+
{
|
|
2500
|
+
// `import { defineConfig } from '@weave-framework/cli'` → a tiny inline identity, so
|
|
2501
|
+
// the config doesn't drag the whole CLI (and esbuild) into the bundle.
|
|
2502
|
+
name: "weave-config-shim",
|
|
2503
|
+
setup(b) {
|
|
2504
|
+
b.onResolve({ filter: /^@weave-framework\/cli$/ }, () => ({
|
|
2505
|
+
path: "@weave-framework/cli",
|
|
2506
|
+
namespace: "weave-cli-shim"
|
|
2507
|
+
}));
|
|
2508
|
+
b.onLoad({ filter: /.*/, namespace: "weave-cli-shim" }, () => ({
|
|
2509
|
+
contents: "export const defineConfig = (c) => c;",
|
|
2510
|
+
loader: "js"
|
|
2511
|
+
}));
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
]
|
|
2515
|
+
});
|
|
2516
|
+
const code = out.outputFiles[0].text;
|
|
2517
|
+
const url = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
|
|
2518
|
+
const mod = await import(url);
|
|
2519
|
+
return mod.default ?? mod;
|
|
2520
|
+
}
|
|
2521
|
+
function resolveConfig(raw, root) {
|
|
2522
|
+
const abs = (p) => isAbsolute(p) ? p : resolve2(root, p);
|
|
2523
|
+
if (!raw.root && !raw.entry) {
|
|
2524
|
+
throw new Error("weave: config must declare either `root` (generated bootstrap) or `entry` (hand-written)");
|
|
2525
|
+
}
|
|
2526
|
+
if (raw.root && raw.entry) {
|
|
2527
|
+
throw new Error("weave: config declares both `root` and `entry` \u2014 pick one");
|
|
2528
|
+
}
|
|
2529
|
+
return {
|
|
2530
|
+
root,
|
|
2531
|
+
entry: raw.entry ? abs(raw.entry) : void 0,
|
|
2532
|
+
rootComponent: raw.root ? abs(raw.root) : void 0,
|
|
2533
|
+
mount: raw.mount ?? "#app",
|
|
2534
|
+
publicDir: raw.publicDir ? abs(raw.publicDir) : root,
|
|
2535
|
+
index: raw.index ? abs(raw.index) : void 0,
|
|
2536
|
+
outDir: abs(raw.outDir ?? "dist"),
|
|
2537
|
+
styleLang: raw.styleLang ?? "css",
|
|
2538
|
+
routesDir: raw.routesDir ? abs(raw.routesDir) : void 0,
|
|
2539
|
+
styles: (raw.styles ?? []).map(abs),
|
|
2540
|
+
port: raw.dev?.port,
|
|
2541
|
+
minify: raw.build?.minify ?? true
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
// packages/check/src/emit.ts
|
|
2546
|
+
var FOR_VARS2 = ["$index", "$count", "$first", "$last", "$even", "$odd"];
|
|
2547
|
+
var HAS_SETUP2 = /export\s+(?:async\s+)?function\s+setup\b|export\s+(?:const|let|var)\s+setup\b/;
|
|
2548
|
+
var isComponentTag = (tag) => /^[A-Z]/.test(tag);
|
|
2549
|
+
var propKey2 = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
2550
|
+
function buildVirtualSfc(filePath, source) {
|
|
2551
|
+
const loc = parseSfcLoc(source);
|
|
2552
|
+
const nodes = parseTemplate(loc.template);
|
|
2553
|
+
const body = emit(nodes, new Set(inferCtxNames(nodes)));
|
|
2554
|
+
const hasSetup = HAS_SETUP2.test(loc.script ?? "");
|
|
2555
|
+
const asm = assemble(loc.script, hasSetup, body, loc.scriptOffset);
|
|
2556
|
+
return {
|
|
2557
|
+
path: filePath + ".ts",
|
|
2558
|
+
text: asm.text,
|
|
2559
|
+
templateFile: filePath,
|
|
2560
|
+
templateText: loc.template,
|
|
2561
|
+
templateMap: asm.templateMap,
|
|
2562
|
+
scriptFile: filePath,
|
|
2563
|
+
scriptText: source,
|
|
2564
|
+
scriptLine: loc.scriptLine,
|
|
2565
|
+
scriptLineCount: asm.scriptLineCount,
|
|
2566
|
+
mappings: asm.mappings
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
function buildVirtualSeparate(tsPath, tsSource, htmlPath, htmlSource) {
|
|
2570
|
+
const nodes = parseTemplate(htmlSource);
|
|
2571
|
+
const body = emit(nodes, new Set(inferCtxNames(nodes)));
|
|
2572
|
+
const asm = assemble(tsSource, HAS_SETUP2.test(tsSource), body, 0);
|
|
2573
|
+
return {
|
|
2574
|
+
// Live at the real `.ts` path (shadowing disk) so a parent's `import Foo from
|
|
2575
|
+
// './foo'` resolves to this virtual — which carries the synthesized typed
|
|
2576
|
+
// default export — instead of the on-disk source (which has only `setup`).
|
|
2577
|
+
path: tsPath,
|
|
2578
|
+
text: asm.text,
|
|
2579
|
+
templateFile: htmlPath,
|
|
2580
|
+
templateText: htmlSource,
|
|
2581
|
+
templateMap: asm.templateMap,
|
|
2582
|
+
scriptFile: tsPath,
|
|
2583
|
+
scriptText: tsSource,
|
|
2584
|
+
scriptLine: 0,
|
|
2585
|
+
scriptLineCount: asm.scriptLineCount,
|
|
2586
|
+
mappings: asm.mappings
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function emit(nodes, ctx) {
|
|
2590
|
+
const lines = [];
|
|
2591
|
+
let awaitN = 0;
|
|
2592
|
+
let propsN = 0;
|
|
2593
|
+
const push = (text, offset) => {
|
|
2594
|
+
lines.push({ text, offset });
|
|
2595
|
+
};
|
|
2596
|
+
const scopeOf = (locals) => {
|
|
2597
|
+
const s = /* @__PURE__ */ new Map();
|
|
2598
|
+
for (const n of ctx) s.set(n, { kind: "ctx" });
|
|
2599
|
+
for (const n of locals) s.set(n, { kind: "local" });
|
|
2600
|
+
return s;
|
|
2601
|
+
};
|
|
2602
|
+
const mk = () => {
|
|
2603
|
+
let text = "";
|
|
2604
|
+
const segs = [];
|
|
2605
|
+
const api = {
|
|
2606
|
+
lit(s) {
|
|
2607
|
+
text += s;
|
|
2608
|
+
return api;
|
|
2609
|
+
},
|
|
2610
|
+
expr(srcOffset, exprStr, locals) {
|
|
2611
|
+
const r = rewrite(exprStr, scopeOf(locals), "__ctx");
|
|
2612
|
+
const code = r.code.replace(/[\r\n]/g, " ");
|
|
2613
|
+
const base = text.length;
|
|
2614
|
+
if (srcOffset !== void 0) {
|
|
2615
|
+
for (const s of r.segments) segs.push({ col: base + s.gen, src: srcOffset + s.src, len: s.len });
|
|
2616
|
+
}
|
|
2617
|
+
text += code;
|
|
2618
|
+
return api;
|
|
2619
|
+
},
|
|
2620
|
+
push(offset) {
|
|
2621
|
+
lines.push({ text, offset, segs });
|
|
2622
|
+
}
|
|
2623
|
+
};
|
|
2624
|
+
return api;
|
|
2625
|
+
};
|
|
2626
|
+
const emitAttr = (attr, locals) => {
|
|
2627
|
+
if (attr.type === "static") return;
|
|
2628
|
+
if (attr.type === "use" || attr.type === "transition") {
|
|
2629
|
+
const at = attr.nameOffset ?? attr.offset;
|
|
2630
|
+
const b = mk().lit(" (").expr(at, attr.name, locals);
|
|
2631
|
+
if (attr.expr !== void 0) b.lit(")(null as any, ").expr(attr.offset, attr.expr, locals).lit(");");
|
|
2632
|
+
else b.lit(")(null as any);");
|
|
2633
|
+
b.push(at);
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
mk().lit(" void (").expr(attr.offset, attr.expr, locals).lit(");").push(attr.offset);
|
|
2637
|
+
};
|
|
2638
|
+
const emitComponent = (node, locals) => {
|
|
2639
|
+
const dataProps = [];
|
|
2640
|
+
for (const attr of node.attrs) {
|
|
2641
|
+
if (attr.type === "static") {
|
|
2642
|
+
if (attr.name === "slot") continue;
|
|
2643
|
+
dataProps.push({ key: attr.name, staticVal: JSON.stringify(attr.value) });
|
|
2644
|
+
} else if (attr.type === "attr") {
|
|
2645
|
+
dataProps.push({ key: attr.name, expr: attr.expr, srcOffset: attr.offset });
|
|
2646
|
+
} else {
|
|
2647
|
+
emitAttr(attr, locals);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
const id = `__props${propsN++}`;
|
|
2651
|
+
const anchor = dataProps.find((p) => p.srcOffset !== void 0)?.srcOffset;
|
|
2652
|
+
mk().lit(` const ${id}: NonNullable<Parameters<typeof `).expr(node.tagOffset, node.tag, locals).lit(`>[0]> = {`).push(anchor ?? node.tagOffset);
|
|
2653
|
+
for (const p of dataProps) {
|
|
2654
|
+
if (p.expr !== void 0) {
|
|
2655
|
+
mk().lit(` ${propKey2(p.key)}: (`).expr(p.srcOffset, p.expr, locals).lit("),").push(p.srcOffset);
|
|
2656
|
+
} else {
|
|
2657
|
+
push(` ${propKey2(p.key)}: (${p.staticVal}),`);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
push(` };`);
|
|
2661
|
+
push(` void ${id};`);
|
|
2662
|
+
};
|
|
2663
|
+
const walk2 = (list, locals) => {
|
|
2664
|
+
let scope = locals;
|
|
2665
|
+
const snippets = list.filter((n) => n.type === "snippet");
|
|
2666
|
+
if (snippets.length) {
|
|
2667
|
+
scope = new Set(scope);
|
|
2668
|
+
for (const s of snippets) scope.add(s.name);
|
|
2669
|
+
for (const s of snippets) {
|
|
2670
|
+
const params = s.params.map((p) => `${p}: any`).join(", ");
|
|
2671
|
+
push(` const ${s.name} = (${params}): void => {`);
|
|
2672
|
+
const inner = new Set(scope);
|
|
2673
|
+
for (const p of s.params) inner.add(p);
|
|
2674
|
+
walk2(s.children, inner);
|
|
2675
|
+
push(` };`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
for (const node of list) {
|
|
2679
|
+
switch (node.type) {
|
|
2680
|
+
case "snippet":
|
|
2681
|
+
break;
|
|
2682
|
+
// already emitted above
|
|
2683
|
+
case "render":
|
|
2684
|
+
mk().lit(" void (").expr(node.exprOffset, node.expr, scope).lit(");").push(node.exprOffset);
|
|
2685
|
+
break;
|
|
2686
|
+
case "key":
|
|
2687
|
+
mk().lit(" void (").expr(node.exprOffset, node.expr, scope).lit(");").push(node.exprOffset);
|
|
2688
|
+
walk2(node.children, scope);
|
|
2689
|
+
break;
|
|
2690
|
+
case "text":
|
|
2691
|
+
break;
|
|
2692
|
+
case "interp":
|
|
2693
|
+
mk().lit(" void (").expr(node.offset, node.expr, scope).lit(");").push(node.offset);
|
|
2694
|
+
break;
|
|
2695
|
+
case "let": {
|
|
2696
|
+
mk().lit(` const ${node.name} = (`).expr(node.exprOffset, node.expr, scope).lit(");").push(node.exprOffset);
|
|
2697
|
+
scope = new Set(scope).add(node.name);
|
|
2698
|
+
break;
|
|
2699
|
+
}
|
|
2700
|
+
case "element":
|
|
2701
|
+
if (isComponentTag(node.tag)) {
|
|
2702
|
+
emitComponent(node, scope);
|
|
2703
|
+
walk2(node.children, scope);
|
|
2704
|
+
break;
|
|
2705
|
+
}
|
|
2706
|
+
for (const attr of node.attrs) emitAttr(attr, scope);
|
|
2707
|
+
walk2(node.children, scope);
|
|
2708
|
+
break;
|
|
2709
|
+
case "if":
|
|
2710
|
+
for (const br of node.branches) {
|
|
2711
|
+
if (br.cond !== void 0) {
|
|
2712
|
+
mk().lit(" if (").expr(br.condOffset, br.cond, scope).lit(") {").push(br.condOffset);
|
|
2713
|
+
} else {
|
|
2714
|
+
push(` {`);
|
|
2715
|
+
}
|
|
2716
|
+
let inner = scope;
|
|
2717
|
+
if (br.alias && br.cond !== void 0) {
|
|
2718
|
+
mk().lit(` const ${br.alias} = (`).expr(br.condOffset, br.cond, scope).lit(");").push(br.condOffset);
|
|
2719
|
+
inner = new Set(scope).add(br.alias);
|
|
2720
|
+
}
|
|
2721
|
+
walk2(br.children, inner);
|
|
2722
|
+
push(` }`);
|
|
2723
|
+
}
|
|
2724
|
+
break;
|
|
2725
|
+
case "for": {
|
|
2726
|
+
mk().lit(` for (const ${node.item} of (`).expr(node.listOffset, node.list, scope).lit(")) {").push(node.listOffset);
|
|
2727
|
+
push(
|
|
2728
|
+
` const $index: number = 0, $count: number = 0, $first: boolean = true, $last: boolean = true, $even: boolean = true, $odd: boolean = true;`
|
|
2729
|
+
);
|
|
2730
|
+
const inner = new Set(scope).add(node.item);
|
|
2731
|
+
for (const v of FOR_VARS2) inner.add(v);
|
|
2732
|
+
if (node.track) mk().lit(" void (").expr(node.trackOffset, node.track, inner).lit(");").push(node.trackOffset);
|
|
2733
|
+
walk2(node.children, inner);
|
|
2734
|
+
push(` }`);
|
|
2735
|
+
if (node.empty) walk2(node.empty, scope);
|
|
2736
|
+
break;
|
|
2737
|
+
}
|
|
2738
|
+
case "switch": {
|
|
2739
|
+
mk().lit(" switch (").expr(node.exprOffset, node.expr, scope).lit(") {").push(node.exprOffset);
|
|
2740
|
+
for (const c of node.cases) {
|
|
2741
|
+
if (c.test !== void 0) {
|
|
2742
|
+
mk().lit(" case ").expr(c.testOffset, c.test, scope).lit(": {").push(c.testOffset);
|
|
2743
|
+
} else {
|
|
2744
|
+
push(` default: {`);
|
|
2745
|
+
}
|
|
2746
|
+
walk2(c.children, scope);
|
|
2747
|
+
push(` break; }`);
|
|
2748
|
+
}
|
|
2749
|
+
push(` }`);
|
|
2750
|
+
break;
|
|
2751
|
+
}
|
|
2752
|
+
case "defer": {
|
|
2753
|
+
if (node.trigger.kind === "when") {
|
|
2754
|
+
mk().lit(" void (").expr(node.trigger.exprOffset, node.trigger.expr, scope).lit(");").push(node.trigger.exprOffset);
|
|
2755
|
+
} else if (node.trigger.kind === "timer") {
|
|
2756
|
+
mk().lit(" void (").expr(node.trigger.msOffset, node.trigger.ms, scope).lit(");").push(node.trigger.msOffset);
|
|
2757
|
+
}
|
|
2758
|
+
walk2(node.children, scope);
|
|
2759
|
+
if (node.placeholder) walk2(node.placeholder, scope);
|
|
2760
|
+
break;
|
|
2761
|
+
}
|
|
2762
|
+
case "await": {
|
|
2763
|
+
let srcVar = "";
|
|
2764
|
+
if (node.then?.alias) {
|
|
2765
|
+
srcVar = `__await${awaitN++}`;
|
|
2766
|
+
mk().lit(` const ${srcVar} = (`).expr(node.exprOffset, node.expr, scope).lit(");").push(node.exprOffset);
|
|
2767
|
+
} else {
|
|
2768
|
+
mk().lit(" void (").expr(node.exprOffset, node.expr, scope).lit(");").push(node.exprOffset);
|
|
2769
|
+
}
|
|
2770
|
+
if (node.pending) walk2(node.pending, scope);
|
|
2771
|
+
if (node.then) {
|
|
2772
|
+
push(` {`);
|
|
2773
|
+
let inner = scope;
|
|
2774
|
+
if (node.then.alias) {
|
|
2775
|
+
push(
|
|
2776
|
+
` const ${node.then.alias}: __WeaveAwaited<typeof ${srcVar}> = undefined as any;`,
|
|
2777
|
+
node.exprOffset
|
|
2778
|
+
);
|
|
2779
|
+
inner = new Set(scope).add(node.then.alias);
|
|
2780
|
+
}
|
|
2781
|
+
walk2(node.then.children, inner);
|
|
2782
|
+
push(` }`);
|
|
2783
|
+
}
|
|
2784
|
+
if (node.catch) {
|
|
2785
|
+
push(` {`);
|
|
2786
|
+
let inner = scope;
|
|
2787
|
+
if (node.catch.alias) {
|
|
2788
|
+
push(` const ${node.catch.alias}: unknown = undefined;`);
|
|
2789
|
+
inner = new Set(scope).add(node.catch.alias);
|
|
2790
|
+
}
|
|
2791
|
+
walk2(node.catch.children, inner);
|
|
2792
|
+
push(` }`);
|
|
2793
|
+
}
|
|
2794
|
+
break;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
walk2(nodes, /* @__PURE__ */ new Set());
|
|
2800
|
+
return lines;
|
|
2801
|
+
}
|
|
2802
|
+
function assemble(script, hasSetup, body, scriptBaseOffset) {
|
|
2803
|
+
const out = [];
|
|
2804
|
+
const scriptLines = script ? script.split("\n") : [];
|
|
2805
|
+
for (const l of scriptLines) out.push(l);
|
|
2806
|
+
out.push("");
|
|
2807
|
+
out.push(
|
|
2808
|
+
hasSetup ? "type __WeaveCtx = ReturnType<typeof setup>;" : "type __WeaveCtx = Record<string, any>;"
|
|
2809
|
+
);
|
|
2810
|
+
out.push("declare const __ctx: __WeaveCtx;");
|
|
2811
|
+
out.push("type __WeaveAwaited<S> = S extends { data: () => infer D } ? NonNullable<D> : Awaited<S>;");
|
|
2812
|
+
out.push("type __WeavePropsOf<F> = F extends (props: infer P, ...rest: any[]) => any ? P : Record<string, never>;");
|
|
2813
|
+
out.push("function __weave__(): void {");
|
|
2814
|
+
const bodyBase = out.length;
|
|
2815
|
+
const templateMap = /* @__PURE__ */ new Map();
|
|
2816
|
+
body.forEach((ln, i) => {
|
|
2817
|
+
out.push(ln.text);
|
|
2818
|
+
if (ln.offset !== void 0) templateMap.set(bodyBase + i + 1, ln.offset);
|
|
2819
|
+
});
|
|
2820
|
+
out.push("}");
|
|
2821
|
+
const propsType = hasSetup ? "__WeavePropsOf<typeof setup>" : "Record<string, never>";
|
|
2822
|
+
out.push(`declare const __weaveDefault: (props: ${propsType}, slots?: Record<string, () => unknown>) => unknown;`);
|
|
2823
|
+
out.push("export default __weaveDefault;");
|
|
2824
|
+
const mappings = [];
|
|
2825
|
+
if (script && script.length) {
|
|
2826
|
+
mappings.push({ generatedOffset: 0, sourceOffset: scriptBaseOffset, length: script.length, source: "script" });
|
|
2827
|
+
}
|
|
2828
|
+
const lineGenOffset = new Array(out.length);
|
|
2829
|
+
let acc = 0;
|
|
2830
|
+
for (let k = 0; k < out.length; k++) {
|
|
2831
|
+
lineGenOffset[k] = acc;
|
|
2832
|
+
acc += out[k].length + 1;
|
|
2833
|
+
}
|
|
2834
|
+
body.forEach((ln, i) => {
|
|
2835
|
+
if (!ln.segs) return;
|
|
2836
|
+
const gBase = lineGenOffset[bodyBase + i];
|
|
2837
|
+
for (const s of ln.segs) {
|
|
2838
|
+
mappings.push({ generatedOffset: gBase + s.col, sourceOffset: s.src, length: s.len, source: "template" });
|
|
2839
|
+
}
|
|
2840
|
+
});
|
|
2841
|
+
return { text: out.join("\n"), scriptLineCount: scriptLines.length, templateMap, mappings };
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
// packages/check/src/check.ts
|
|
2845
|
+
import ts from "typescript";
|
|
2846
|
+
var OPTIONS = {
|
|
2847
|
+
target: ts.ScriptTarget.ES2022,
|
|
2848
|
+
module: ts.ModuleKind.ESNext,
|
|
2849
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
2850
|
+
lib: ["lib.es2022.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"],
|
|
2851
|
+
types: [],
|
|
2852
|
+
strict: true,
|
|
2853
|
+
noEmit: true,
|
|
2854
|
+
skipLibCheck: true,
|
|
2855
|
+
allowJs: false
|
|
2856
|
+
};
|
|
2857
|
+
var norm = (p) => p.replace(/\\/g, "/").toLowerCase();
|
|
2858
|
+
function runCheck(virtuals) {
|
|
2859
|
+
const byPath = new Map(virtuals.map((v) => [norm(v.path), v]));
|
|
2860
|
+
const host = ts.createCompilerHost(OPTIONS, true);
|
|
2861
|
+
const getSourceFile = host.getSourceFile.bind(host);
|
|
2862
|
+
const readFile6 = host.readFile.bind(host);
|
|
2863
|
+
const fileExists = host.fileExists.bind(host);
|
|
2864
|
+
host.getSourceFile = (fileName, languageVersion, onError, shouldCreate) => {
|
|
2865
|
+
const v = byPath.get(norm(fileName));
|
|
2866
|
+
if (v) return ts.createSourceFile(fileName, v.text, languageVersion, true);
|
|
2867
|
+
return getSourceFile(fileName, languageVersion, onError, shouldCreate);
|
|
2868
|
+
};
|
|
2869
|
+
host.readFile = (fileName) => byPath.get(norm(fileName))?.text ?? readFile6(fileName);
|
|
2870
|
+
host.fileExists = (fileName) => byPath.has(norm(fileName)) || fileExists(fileName);
|
|
2871
|
+
const program = ts.createProgram(
|
|
2872
|
+
virtuals.map((v) => v.path),
|
|
2873
|
+
OPTIONS,
|
|
2874
|
+
host
|
|
2875
|
+
);
|
|
2876
|
+
const raw = [];
|
|
2877
|
+
for (const v of virtuals) {
|
|
2878
|
+
const sf = program.getSourceFile(v.path);
|
|
2879
|
+
if (!sf) continue;
|
|
2880
|
+
raw.push(...program.getSyntacticDiagnostics(sf), ...program.getSemanticDiagnostics(sf));
|
|
2881
|
+
}
|
|
2882
|
+
return raw.map((d) => mapDiagnostic(d, byPath));
|
|
2883
|
+
}
|
|
2884
|
+
function mapDiagnostic(d, byPath) {
|
|
2885
|
+
const message = ts.flattenDiagnosticMessageText(d.messageText, "\n");
|
|
2886
|
+
const category = categoryName(d.category);
|
|
2887
|
+
if (!d.file || d.start === void 0) {
|
|
2888
|
+
return { file: "(global)", line: 0, col: 0, code: d.code, message, category };
|
|
2889
|
+
}
|
|
2890
|
+
const { line, character } = d.file.getLineAndCharacterOfPosition(d.start);
|
|
2891
|
+
const v = byPath.get(norm(d.file.fileName));
|
|
2892
|
+
if (!v) {
|
|
2893
|
+
return { file: d.file.fileName, line: line + 1, col: character + 1, code: d.code, message, category };
|
|
2894
|
+
}
|
|
2895
|
+
const vLine = line + 1;
|
|
2896
|
+
const offset = v.templateMap.get(vLine);
|
|
2897
|
+
if (offset !== void 0) {
|
|
2898
|
+
const { line: l, col } = offsetToLineCol(v.templateText, offset);
|
|
2899
|
+
return { file: v.templateFile, line: l, col, code: d.code, message, category };
|
|
2900
|
+
}
|
|
2901
|
+
if (vLine <= v.scriptLineCount) {
|
|
2902
|
+
return {
|
|
2903
|
+
file: v.scriptFile,
|
|
2904
|
+
line: v.scriptLine + (vLine - 1) + 1,
|
|
2905
|
+
col: character + 1,
|
|
2906
|
+
code: d.code,
|
|
2907
|
+
message,
|
|
2908
|
+
category
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
return { file: v.templateFile, line: 1, col: 1, code: d.code, message: `[generated] ${message}`, category };
|
|
2912
|
+
}
|
|
2913
|
+
function categoryName(c) {
|
|
2914
|
+
switch (c) {
|
|
2915
|
+
case ts.DiagnosticCategory.Error:
|
|
2916
|
+
return "error";
|
|
2917
|
+
case ts.DiagnosticCategory.Warning:
|
|
2918
|
+
return "warning";
|
|
2919
|
+
case ts.DiagnosticCategory.Suggestion:
|
|
2920
|
+
return "suggestion";
|
|
2921
|
+
default:
|
|
2922
|
+
return "message";
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
function offsetToLineCol(text, offset) {
|
|
2926
|
+
let line = 1;
|
|
2927
|
+
let col = 1;
|
|
2928
|
+
const end = Math.min(offset, text.length);
|
|
2929
|
+
for (let i = 0; i < end; i++) {
|
|
2930
|
+
if (text[i] === "\n") {
|
|
2931
|
+
line++;
|
|
2932
|
+
col = 1;
|
|
2933
|
+
} else {
|
|
2934
|
+
col++;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return { line, col };
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
// packages/check/src/project.ts
|
|
2941
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync3, existsSync as existsSync5, statSync as statSync2 } from "node:fs";
|
|
2942
|
+
import { join as join6, resolve as resolve3, dirname as dirname3 } from "node:path";
|
|
2943
|
+
var SKIP2 = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".weave"]);
|
|
2944
|
+
function checkProject(roots) {
|
|
2945
|
+
const virtuals = [];
|
|
2946
|
+
for (const root of roots) collect(root, virtuals);
|
|
2947
|
+
return virtuals.length ? runCheck(virtuals) : [];
|
|
2948
|
+
}
|
|
2949
|
+
function collect(path, out) {
|
|
2950
|
+
if (!existsSync5(path)) return;
|
|
2951
|
+
const st = statSync2(path);
|
|
2952
|
+
if (st.isDirectory()) {
|
|
2953
|
+
for (const entry of readdirSync3(path)) {
|
|
2954
|
+
if (SKIP2.has(entry)) continue;
|
|
2955
|
+
collect(join6(path, entry), out);
|
|
2956
|
+
}
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
if (path.endsWith(".weave")) {
|
|
2960
|
+
out.push(absolutize(buildVirtualSfc(path, readFileSync2(path, "utf8"))));
|
|
2961
|
+
} else if (path.endsWith(".ts") && !path.endsWith(".d.ts")) {
|
|
2962
|
+
const v = collectTs(path);
|
|
2963
|
+
if (v) out.push(absolutize(v));
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
function absolutize(v) {
|
|
2967
|
+
v.path = resolve3(v.path);
|
|
2968
|
+
return v;
|
|
2969
|
+
}
|
|
2970
|
+
function collectTs(tsPath) {
|
|
2971
|
+
const source = readFileSync2(tsPath, "utf8");
|
|
2972
|
+
const decl = extractSources(source);
|
|
2973
|
+
const siblingHtml = tsPath.replace(/\.ts$/, ".html");
|
|
2974
|
+
if (decl.template !== void 0) {
|
|
2975
|
+
if (classifyTemplate(decl.template) === "inline") {
|
|
2976
|
+
const faithful = decl.templateRange ? faithfulTemplate(source, decl.templateRange) : decl.template;
|
|
2977
|
+
return buildVirtualSeparate(tsPath, decl.script, tsPath, faithful);
|
|
2978
|
+
}
|
|
2979
|
+
const file = resolve3(dirname3(tsPath), decl.template);
|
|
2980
|
+
if (!existsSync5(file)) return null;
|
|
2981
|
+
return buildVirtualSeparate(tsPath, decl.script, file, readFileSync2(file, "utf8"));
|
|
2982
|
+
}
|
|
2983
|
+
if (existsSync5(siblingHtml)) {
|
|
2984
|
+
return buildVirtualSeparate(tsPath, decl.script, siblingHtml, readFileSync2(siblingHtml, "utf8"));
|
|
2985
|
+
}
|
|
2986
|
+
return null;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
// packages/cli/src/cli.ts
|
|
2990
|
+
function flag(args, name) {
|
|
2991
|
+
const i = args.indexOf(name);
|
|
2992
|
+
return i >= 0 ? args[i + 1] : void 0;
|
|
2993
|
+
}
|
|
2994
|
+
function syncRoutes(config) {
|
|
2995
|
+
if (!config.routesDir) return;
|
|
2996
|
+
const written = generateRoutes(config.routesDir, { lazy: true });
|
|
2997
|
+
console.log(`weave routes \u2192 ${written}`);
|
|
2998
|
+
}
|
|
2999
|
+
function virtualEntryFor(config) {
|
|
3000
|
+
if (!config.rootComponent) return void 0;
|
|
3001
|
+
const elements = discoverCustomElements(config.root);
|
|
3002
|
+
const code = generateEntry(config.rootComponent, config.mount, config.root, elements);
|
|
3003
|
+
return { code, resolveDir: config.root };
|
|
3004
|
+
}
|
|
3005
|
+
async function main(argv) {
|
|
3006
|
+
const [cmd, ...rest] = argv;
|
|
3007
|
+
const entry = rest.find((a) => !a.startsWith("-")) ?? "src/main.ts";
|
|
3008
|
+
const outdir = flag(rest, "--out") ?? "dist";
|
|
3009
|
+
const config = await loadConfig(process.cwd(), flag(rest, "--config"));
|
|
3010
|
+
if (cmd === "build") {
|
|
3011
|
+
if (config) {
|
|
3012
|
+
syncRoutes(config);
|
|
3013
|
+
await build({
|
|
3014
|
+
entry: config.entry,
|
|
3015
|
+
virtualEntry: virtualEntryFor(config),
|
|
3016
|
+
outDir: config.outDir,
|
|
3017
|
+
minify: config.minify,
|
|
3018
|
+
styleLang: config.styleLang,
|
|
3019
|
+
styles: config.styles,
|
|
3020
|
+
publicDir: config.publicDir,
|
|
3021
|
+
index: config.index,
|
|
3022
|
+
clean: true
|
|
3023
|
+
// a fresh, self-contained artifact each prod build
|
|
3024
|
+
});
|
|
3025
|
+
console.log(`weave build \u2192 ${config.outDir}/`);
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
await build({ entry, outDir: outdir, minify: !rest.includes("--no-minify") });
|
|
3029
|
+
console.log(`weave build \u2192 ${outdir}/`);
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
if (cmd === "dev") {
|
|
3033
|
+
if (config) {
|
|
3034
|
+
syncRoutes(config);
|
|
3035
|
+
const { url: url2 } = await dev({
|
|
3036
|
+
entry: config.entry,
|
|
3037
|
+
virtualEntry: virtualEntryFor(config),
|
|
3038
|
+
servedir: config.publicDir,
|
|
3039
|
+
outdir: config.publicDir,
|
|
3040
|
+
port: config.port,
|
|
3041
|
+
styleLang: config.styleLang,
|
|
3042
|
+
styles: config.styles,
|
|
3043
|
+
index: config.index,
|
|
3044
|
+
inMemory: true
|
|
3045
|
+
});
|
|
3046
|
+
console.log(`weave dev \u2192 ${url2}`);
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
const servedir = flag(rest, "--serve") ?? ".";
|
|
3050
|
+
const port = Number(flag(rest, "--port")) || void 0;
|
|
3051
|
+
const { url } = await dev({ entry, outdir, servedir, port });
|
|
3052
|
+
console.log(`weave dev \u2192 ${url}`);
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
if (cmd === "check") {
|
|
3056
|
+
const roots = rest.filter((a) => !a.startsWith("-"));
|
|
3057
|
+
const diags = checkProject(roots.length ? roots : ["src"]);
|
|
3058
|
+
for (const d of diags) console.error(formatDiagnostic(d));
|
|
3059
|
+
const errors = diags.filter((d) => d.category === "error").length;
|
|
3060
|
+
if (errors) {
|
|
3061
|
+
console.error(`
|
|
3062
|
+
weave check: ${errors} error${errors === 1 ? "" : "s"}`);
|
|
3063
|
+
process.exit(1);
|
|
3064
|
+
}
|
|
3065
|
+
console.log("weave check: no type errors");
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
if (cmd === "routes") {
|
|
3069
|
+
const dir = rest.find((a) => !a.startsWith("-")) ?? "src/routes";
|
|
3070
|
+
const out = flag(rest, "--out");
|
|
3071
|
+
const written = generateRoutes(dir, { out, lazy: !rest.includes("--eager") });
|
|
3072
|
+
console.log(`weave routes \u2192 ${written}`);
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
console.error(
|
|
3076
|
+
"usage: weave <build|dev|check|routes> [entry|paths\u2026] [--config file] [--out dir] [--serve dir] [--port n] [--no-minify] [--eager]"
|
|
3077
|
+
);
|
|
3078
|
+
process.exit(1);
|
|
3079
|
+
}
|
|
3080
|
+
function formatDiagnostic(d) {
|
|
3081
|
+
return `${d.file}:${d.line}:${d.col} - ${d.category} TS${d.code}: ${d.message}`;
|
|
3082
|
+
}
|
|
3083
|
+
export {
|
|
3084
|
+
defineConfig,
|
|
3085
|
+
main
|
|
3086
|
+
};
|