olova 2.0.60 → 2.0.62
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/CHANGELOG.md +5 -0
- package/README.md +42 -61
- package/dist/compiler.d.ts +44 -0
- package/dist/compiler.js +2139 -0
- package/dist/compiler.js.map +1 -0
- package/dist/core.d.ts +4 -0
- package/dist/core.js +859 -0
- package/dist/core.js.map +1 -0
- package/dist/global.d.ts +15 -0
- package/dist/global.js +226 -0
- package/dist/global.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2268 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +89 -0
- package/dist/runtime.js +633 -0
- package/dist/runtime.js.map +1 -0
- package/dist/signals-core-BdfWh1Yt.d.ts +43 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +2268 -0
- package/dist/vite.js.map +1 -0
- package/package.json +83 -65
- package/dist/chunk-D7SIC5TC.js +0 -367
- package/dist/chunk-D7SIC5TC.js.map +0 -1
- package/dist/entry-server.cjs +0 -120
- package/dist/entry-server.cjs.map +0 -1
- package/dist/entry-server.js +0 -115
- package/dist/entry-server.js.map +0 -1
- package/dist/entry-worker.cjs +0 -133
- package/dist/entry-worker.cjs.map +0 -1
- package/dist/entry-worker.js +0 -127
- package/dist/entry-worker.js.map +0 -1
- package/dist/main.cjs +0 -18
- package/dist/main.cjs.map +0 -1
- package/dist/main.js +0 -16
- package/dist/main.js.map +0 -1
- package/dist/olova.cjs +0 -1680
- package/dist/olova.cjs.map +0 -1
- package/dist/olova.d.cts +0 -72
- package/dist/olova.d.ts +0 -72
- package/dist/olova.js +0 -1321
- package/dist/olova.js.map +0 -1
- package/dist/performance.cjs +0 -386
- package/dist/performance.cjs.map +0 -1
- package/dist/performance.js +0 -3
- package/dist/performance.js.map +0 -1
- package/dist/router.cjs +0 -646
- package/dist/router.cjs.map +0 -1
- package/dist/router.d.cts +0 -113
- package/dist/router.d.ts +0 -113
- package/dist/router.js +0 -632
- package/dist/router.js.map +0 -1
- package/main.tsx +0 -76
- package/olova.ts +0 -619
- package/src/entry-server.tsx +0 -165
- package/src/entry-worker.tsx +0 -201
- package/src/generator/index.ts +0 -409
- package/src/hydration/flight.ts +0 -320
- package/src/hydration/index.ts +0 -12
- package/src/hydration/types.ts +0 -225
- package/src/logger.ts +0 -182
- package/src/main.tsx +0 -24
- package/src/performance.ts +0 -488
- package/src/plugin/index.ts +0 -204
- package/src/router/ErrorBoundary.tsx +0 -145
- package/src/router/Link.tsx +0 -117
- package/src/router/OlovaRouter.tsx +0 -354
- package/src/router/Outlet.tsx +0 -8
- package/src/router/context.ts +0 -117
- package/src/router/index.ts +0 -29
- package/src/router/matching.ts +0 -63
- package/src/router/router.tsx +0 -23
- package/src/router/search-params.ts +0 -29
- package/src/scanner/index.ts +0 -114
- package/src/types/index.ts +0 -190
- package/src/utils/export.ts +0 -85
- package/src/utils/index.ts +0 -4
- package/src/utils/naming.ts +0 -54
- package/src/utils/path.ts +0 -45
- package/tsup.config.ts +0 -35
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,2139 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import generatorModule from '@babel/generator';
|
|
3
|
+
import traverseModule from '@babel/traverse';
|
|
4
|
+
import * as t from '@babel/types';
|
|
5
|
+
import ts from 'typescript';
|
|
6
|
+
|
|
7
|
+
// compiler/compile.ts
|
|
8
|
+
|
|
9
|
+
// compiler/parse.ts
|
|
10
|
+
function isWhitespace(char) {
|
|
11
|
+
return /\s/.test(char);
|
|
12
|
+
}
|
|
13
|
+
function skipQuoted(source, start, quote) {
|
|
14
|
+
let index = start + 1;
|
|
15
|
+
while (index < source.length) {
|
|
16
|
+
if (source[index] === "\\" && index + 1 < source.length) {
|
|
17
|
+
index += 2;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (source[index] === quote) {
|
|
21
|
+
return index + 1;
|
|
22
|
+
}
|
|
23
|
+
index += 1;
|
|
24
|
+
}
|
|
25
|
+
return source.length;
|
|
26
|
+
}
|
|
27
|
+
function findTagEnd(source, start) {
|
|
28
|
+
let index = start;
|
|
29
|
+
while (index < source.length) {
|
|
30
|
+
const char = source[index];
|
|
31
|
+
if (char === '"' || char === "'") {
|
|
32
|
+
index = skipQuoted(source, index, char);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === ">") {
|
|
36
|
+
return index;
|
|
37
|
+
}
|
|
38
|
+
index += 1;
|
|
39
|
+
}
|
|
40
|
+
return -1;
|
|
41
|
+
}
|
|
42
|
+
function parseAttributes(source) {
|
|
43
|
+
const attrs = {};
|
|
44
|
+
const attrRegex = /([:@A-Za-z_][-A-Za-z0-9_:.@]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = attrRegex.exec(source)) !== null) {
|
|
47
|
+
const [, name, doubleQuoted, singleQuoted, bare] = match;
|
|
48
|
+
const value = doubleQuoted ?? singleQuoted ?? bare ?? true;
|
|
49
|
+
attrs[name] = value;
|
|
50
|
+
}
|
|
51
|
+
return attrs;
|
|
52
|
+
}
|
|
53
|
+
function readOpeningTag(source, start) {
|
|
54
|
+
if (source[start] !== "<") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const tagEnd = findTagEnd(source, start + 1);
|
|
58
|
+
if (tagEnd < 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const raw = source.slice(start + 1, tagEnd).trim();
|
|
62
|
+
if (!raw || raw.startsWith("/") || raw.startsWith("!")) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
let splitIndex = 0;
|
|
66
|
+
while (splitIndex < raw.length && !isWhitespace(raw[splitIndex]) && raw[splitIndex] !== "/") {
|
|
67
|
+
splitIndex += 1;
|
|
68
|
+
}
|
|
69
|
+
const tag = raw.slice(0, splitIndex);
|
|
70
|
+
const attrsSource = raw.slice(splitIndex).replace(/\/\s*$/, "").trim();
|
|
71
|
+
return {
|
|
72
|
+
tag,
|
|
73
|
+
openEnd: tagEnd + 1,
|
|
74
|
+
attrs: parseAttributes(attrsSource)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function findMatchingTag(source, start, tagName) {
|
|
78
|
+
const opening = readOpeningTag(source, start);
|
|
79
|
+
if (!opening || opening.tag.toLowerCase() !== tagName.toLowerCase()) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const selfClosing = /\/\s*>$/.test(source.slice(start, opening.openEnd));
|
|
83
|
+
if (selfClosing) {
|
|
84
|
+
return {
|
|
85
|
+
fullStart: start,
|
|
86
|
+
openEnd: opening.openEnd,
|
|
87
|
+
closeStart: opening.openEnd,
|
|
88
|
+
closeEnd: opening.openEnd,
|
|
89
|
+
inner: "",
|
|
90
|
+
attrs: opening.attrs
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
let depth = 1;
|
|
94
|
+
let index = opening.openEnd;
|
|
95
|
+
while (index < source.length) {
|
|
96
|
+
const next = source.indexOf("<", index);
|
|
97
|
+
if (next < 0) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (source.startsWith("<!--", next)) {
|
|
101
|
+
const commentEnd = source.indexOf("-->", next + 4);
|
|
102
|
+
index = commentEnd >= 0 ? commentEnd + 3 : source.length;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (source[next + 1] === "/") {
|
|
106
|
+
const closeEnd = findTagEnd(source, next + 2);
|
|
107
|
+
if (closeEnd < 0) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const closeTag = source.slice(next + 2, closeEnd).trim();
|
|
111
|
+
if (closeTag.toLowerCase() === tagName.toLowerCase()) {
|
|
112
|
+
depth -= 1;
|
|
113
|
+
if (depth === 0) {
|
|
114
|
+
return {
|
|
115
|
+
fullStart: start,
|
|
116
|
+
openEnd: opening.openEnd,
|
|
117
|
+
closeStart: next,
|
|
118
|
+
closeEnd: closeEnd + 1,
|
|
119
|
+
inner: source.slice(opening.openEnd, next),
|
|
120
|
+
attrs: opening.attrs
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
index = closeEnd + 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const nested = readOpeningTag(source, next);
|
|
128
|
+
if (!nested) {
|
|
129
|
+
index = next + 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (nested.tag.toLowerCase() === tagName.toLowerCase()) {
|
|
133
|
+
const nestedSelfClosing = /\/\s*>$/.test(source.slice(next, nested.openEnd));
|
|
134
|
+
if (!nestedSelfClosing) {
|
|
135
|
+
depth += 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
index = nested.openEnd;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function collectTopLevelBlocks(source, tagName) {
|
|
143
|
+
const blocks = [];
|
|
144
|
+
let index = 0;
|
|
145
|
+
while (index < source.length) {
|
|
146
|
+
const next = source.indexOf(`<${tagName}`, index);
|
|
147
|
+
if (next < 0) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
const previous = next > 0 ? source[next - 1] : "";
|
|
151
|
+
if (previous && /[A-Za-z0-9_:-]/.test(previous)) {
|
|
152
|
+
index = next + tagName.length + 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const block = findMatchingTag(source, next, tagName);
|
|
156
|
+
if (!block) {
|
|
157
|
+
index = next + tagName.length + 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
blocks.push(block);
|
|
161
|
+
index = block.closeEnd;
|
|
162
|
+
}
|
|
163
|
+
return blocks;
|
|
164
|
+
}
|
|
165
|
+
function removeRanges(source, ranges) {
|
|
166
|
+
if (ranges.length === 0) {
|
|
167
|
+
return source.trim();
|
|
168
|
+
}
|
|
169
|
+
const ordered = [...ranges].sort((a, b) => a.start - b.start);
|
|
170
|
+
let cursor = 0;
|
|
171
|
+
let output = "";
|
|
172
|
+
for (const range of ordered) {
|
|
173
|
+
output += source.slice(cursor, range.start);
|
|
174
|
+
cursor = range.end;
|
|
175
|
+
}
|
|
176
|
+
output += source.slice(cursor);
|
|
177
|
+
return output.trim();
|
|
178
|
+
}
|
|
179
|
+
function parseOlovaFile(source) {
|
|
180
|
+
const scriptBlock = collectTopLevelBlocks(source, "script")[0] ?? null;
|
|
181
|
+
const styleBlocks = collectTopLevelBlocks(source, "style");
|
|
182
|
+
const ranges = styleBlocks.map((block) => ({
|
|
183
|
+
start: block.fullStart,
|
|
184
|
+
end: block.closeEnd
|
|
185
|
+
}));
|
|
186
|
+
if (scriptBlock) {
|
|
187
|
+
ranges.push({ start: scriptBlock.fullStart, end: scriptBlock.closeEnd });
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
script: scriptBlock?.inner.trim() ?? "",
|
|
191
|
+
template: removeRanges(source, ranges),
|
|
192
|
+
styles: styleBlocks.map((block) => ({
|
|
193
|
+
content: block.inner.trim(),
|
|
194
|
+
attrs: block.attrs
|
|
195
|
+
}))
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// compiler/html-parser.ts
|
|
200
|
+
function isWhitespace2(char) {
|
|
201
|
+
return /\s/.test(char);
|
|
202
|
+
}
|
|
203
|
+
function findTagEnd2(source, start) {
|
|
204
|
+
let index = start;
|
|
205
|
+
while (index < source.length) {
|
|
206
|
+
const char = source[index];
|
|
207
|
+
if (char === '"' || char === "'") {
|
|
208
|
+
const quoted = readQuotedValue(source, index, char);
|
|
209
|
+
index = quoted.end;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (char === "{") {
|
|
213
|
+
const braced = readBraceValue(source, index);
|
|
214
|
+
index = braced.end;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (char === ">") {
|
|
218
|
+
return index;
|
|
219
|
+
}
|
|
220
|
+
index += 1;
|
|
221
|
+
}
|
|
222
|
+
return -1;
|
|
223
|
+
}
|
|
224
|
+
function readQuotedValue(source, start, quote) {
|
|
225
|
+
let index = start + 1;
|
|
226
|
+
let value = "";
|
|
227
|
+
while (index < source.length) {
|
|
228
|
+
const char = source[index];
|
|
229
|
+
if (char === "\\" && index + 1 < source.length) {
|
|
230
|
+
value += source.slice(index, index + 2);
|
|
231
|
+
index += 2;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (char === quote) {
|
|
235
|
+
return { value, end: index + 1 };
|
|
236
|
+
}
|
|
237
|
+
value += char;
|
|
238
|
+
index += 1;
|
|
239
|
+
}
|
|
240
|
+
return { value, end: source.length };
|
|
241
|
+
}
|
|
242
|
+
function readBraceValue(source, start) {
|
|
243
|
+
let index = start + 1;
|
|
244
|
+
let depth = 1;
|
|
245
|
+
let value = "{";
|
|
246
|
+
let quote = null;
|
|
247
|
+
while (index < source.length && depth > 0) {
|
|
248
|
+
const char = source[index];
|
|
249
|
+
const prev = source[index - 1];
|
|
250
|
+
value += char;
|
|
251
|
+
if (quote) {
|
|
252
|
+
if (char === quote && prev !== "\\") {
|
|
253
|
+
quote = null;
|
|
254
|
+
}
|
|
255
|
+
index += 1;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
259
|
+
quote = char;
|
|
260
|
+
index += 1;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (char === "{") {
|
|
264
|
+
depth += 1;
|
|
265
|
+
} else if (char === "}") {
|
|
266
|
+
depth -= 1;
|
|
267
|
+
}
|
|
268
|
+
index += 1;
|
|
269
|
+
}
|
|
270
|
+
return { value, end: index };
|
|
271
|
+
}
|
|
272
|
+
function parseAttributes2(source) {
|
|
273
|
+
const attrs = {};
|
|
274
|
+
let index = 0;
|
|
275
|
+
while (index < source.length) {
|
|
276
|
+
while (index < source.length && isWhitespace2(source[index])) {
|
|
277
|
+
index += 1;
|
|
278
|
+
}
|
|
279
|
+
if (index >= source.length) {
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
let nameEnd = index;
|
|
283
|
+
while (nameEnd < source.length && !isWhitespace2(source[nameEnd]) && source[nameEnd] !== "=") {
|
|
284
|
+
nameEnd += 1;
|
|
285
|
+
}
|
|
286
|
+
const name = source.slice(index, nameEnd).trim();
|
|
287
|
+
index = nameEnd;
|
|
288
|
+
if (!name) {
|
|
289
|
+
index += 1;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
while (index < source.length && isWhitespace2(source[index])) {
|
|
293
|
+
index += 1;
|
|
294
|
+
}
|
|
295
|
+
if (source[index] !== "=") {
|
|
296
|
+
attrs[name] = true;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
index += 1;
|
|
300
|
+
while (index < source.length && isWhitespace2(source[index])) {
|
|
301
|
+
index += 1;
|
|
302
|
+
}
|
|
303
|
+
if (index >= source.length) {
|
|
304
|
+
attrs[name] = true;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
const start = index;
|
|
308
|
+
const char = source[index];
|
|
309
|
+
if (char === '"' || char === "'") {
|
|
310
|
+
const quoted = readQuotedValue(source, index, char);
|
|
311
|
+
attrs[name] = quoted.value;
|
|
312
|
+
index = quoted.end;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (char === "{") {
|
|
316
|
+
const braced = readBraceValue(source, index);
|
|
317
|
+
attrs[name] = braced.value;
|
|
318
|
+
index = braced.end;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
while (index < source.length && !isWhitespace2(source[index])) {
|
|
322
|
+
index += 1;
|
|
323
|
+
}
|
|
324
|
+
attrs[name] = source.slice(start, index);
|
|
325
|
+
}
|
|
326
|
+
return attrs;
|
|
327
|
+
}
|
|
328
|
+
function parseHTML(html) {
|
|
329
|
+
const root = [];
|
|
330
|
+
const stack = [
|
|
331
|
+
{ node: null, children: root }
|
|
332
|
+
];
|
|
333
|
+
let i = 0;
|
|
334
|
+
while (i < html.length) {
|
|
335
|
+
const textStart = i;
|
|
336
|
+
const tagStart = html.indexOf("<", i);
|
|
337
|
+
if (tagStart === -1) {
|
|
338
|
+
if (textStart < html.length) {
|
|
339
|
+
stack[stack.length - 1].children.push({
|
|
340
|
+
type: "text",
|
|
341
|
+
content: html.slice(textStart)
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
if (tagStart > textStart) {
|
|
347
|
+
stack[stack.length - 1].children.push({
|
|
348
|
+
type: "text",
|
|
349
|
+
content: html.slice(textStart, tagStart)
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
i = tagStart;
|
|
353
|
+
if (html.startsWith("<!--", i)) {
|
|
354
|
+
const commentEnd = html.indexOf("-->", i + 4);
|
|
355
|
+
if (commentEnd === -1) {
|
|
356
|
+
stack[stack.length - 1].children.push({
|
|
357
|
+
type: "comment",
|
|
358
|
+
content: html.slice(i + 4)
|
|
359
|
+
});
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
stack[stack.length - 1].children.push({
|
|
363
|
+
type: "comment",
|
|
364
|
+
content: html.slice(i + 4, commentEnd)
|
|
365
|
+
});
|
|
366
|
+
i = commentEnd + 3;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (html.startsWith("</", i)) {
|
|
370
|
+
const tagEnd2 = html.indexOf(">", i + 2);
|
|
371
|
+
if (tagEnd2 === -1) {
|
|
372
|
+
i += 2;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const tag2 = html.slice(i + 2, tagEnd2).trim().toLowerCase();
|
|
376
|
+
let closeIdx = stack.length - 1;
|
|
377
|
+
while (closeIdx > 0 && stack[closeIdx].node?.tag.toLowerCase() !== tag2) {
|
|
378
|
+
closeIdx--;
|
|
379
|
+
}
|
|
380
|
+
if (closeIdx > 0) {
|
|
381
|
+
stack.length = closeIdx;
|
|
382
|
+
}
|
|
383
|
+
i = tagEnd2 + 1;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const tagEnd = findTagEnd2(html, i + 1);
|
|
387
|
+
if (tagEnd === -1) {
|
|
388
|
+
stack[stack.length - 1].children.push({ type: "text", content: "<" });
|
|
389
|
+
i++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const rawTag = html.slice(i + 1, tagEnd).trim();
|
|
393
|
+
let tagNameEnd = 0;
|
|
394
|
+
while (tagNameEnd < rawTag.length && !isWhitespace2(rawTag[tagNameEnd]) && rawTag[tagNameEnd] !== "/") {
|
|
395
|
+
tagNameEnd += 1;
|
|
396
|
+
}
|
|
397
|
+
const tag = rawTag.slice(0, tagNameEnd);
|
|
398
|
+
if (!tag) {
|
|
399
|
+
stack[stack.length - 1].children.push({ type: "text", content: "<" });
|
|
400
|
+
i++;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const attrsStr = rawTag.slice(tagNameEnd).replace(/\/\s*$/, "").trim();
|
|
404
|
+
const selfClose = /\/\s*$/.test(rawTag);
|
|
405
|
+
const attrs = parseAttributes2(attrsStr);
|
|
406
|
+
const isSelfClosing = !!selfClose || /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i.test(
|
|
407
|
+
tag
|
|
408
|
+
);
|
|
409
|
+
const element = {
|
|
410
|
+
type: "element",
|
|
411
|
+
tag,
|
|
412
|
+
attrs,
|
|
413
|
+
children: [],
|
|
414
|
+
isSelfClosing
|
|
415
|
+
};
|
|
416
|
+
stack[stack.length - 1].children.push(element);
|
|
417
|
+
if (!isSelfClosing) {
|
|
418
|
+
stack.push({ node: element, children: element.children });
|
|
419
|
+
}
|
|
420
|
+
i = tagEnd + 1;
|
|
421
|
+
}
|
|
422
|
+
return root;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// compiler/dom-generator.ts
|
|
426
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
427
|
+
function splitTextFragments(text) {
|
|
428
|
+
const parts = [];
|
|
429
|
+
const tokenRe = /__O_(TEXT|HTML|SLOT|COMP|IF)_([a-zA-Z0-9_]+)__/g;
|
|
430
|
+
let lastIndex = 0;
|
|
431
|
+
let match;
|
|
432
|
+
while ((match = tokenRe.exec(text)) !== null) {
|
|
433
|
+
if (match.index > lastIndex) {
|
|
434
|
+
parts.push({ type: "static", value: text.slice(lastIndex, match.index) });
|
|
435
|
+
}
|
|
436
|
+
parts.push({ type: "dynamic", tokenType: match[1], id: match[2] });
|
|
437
|
+
lastIndex = match.index + match[0].length;
|
|
438
|
+
}
|
|
439
|
+
if (lastIndex < text.length) {
|
|
440
|
+
parts.push({ type: "static", value: text.slice(lastIndex) });
|
|
441
|
+
}
|
|
442
|
+
return parts;
|
|
443
|
+
}
|
|
444
|
+
function isSvgContext(tag, inSvg) {
|
|
445
|
+
if (tag === "foreignObject") {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
return inSvg || tag === "svg";
|
|
449
|
+
}
|
|
450
|
+
function processNode(node, parentVar, stateNames, transformExpr, counters, buildCtx, componentNames, inSvg = false) {
|
|
451
|
+
if (node.type === "comment") {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (node.type === "text") {
|
|
455
|
+
const parts = splitTextFragments(node.content);
|
|
456
|
+
for (const part of parts) {
|
|
457
|
+
if (part.type === "static") {
|
|
458
|
+
if (!part.value) continue;
|
|
459
|
+
const varName = `_t${buildCtx.localCount++}`;
|
|
460
|
+
buildCtx.statements.push(
|
|
461
|
+
`const ${varName} = document.createTextNode(${JSON.stringify(part.value)});`
|
|
462
|
+
);
|
|
463
|
+
buildCtx.statements.push(`${parentVar}.appendChild(${varName});`);
|
|
464
|
+
} else {
|
|
465
|
+
const varName = `_d${buildCtx.localCount++}`;
|
|
466
|
+
buildCtx.statements.push(
|
|
467
|
+
`const ${varName} = document.createTextNode('');`
|
|
468
|
+
);
|
|
469
|
+
buildCtx.nodes.push(`'${part.id}': ${varName}`);
|
|
470
|
+
buildCtx.statements.push(`${parentVar}.appendChild(${varName});`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (node.type === "element") {
|
|
476
|
+
const tag = node.tag;
|
|
477
|
+
const varName = `_el${buildCtx.localCount++}`;
|
|
478
|
+
const useSvgNamespace = isSvgContext(tag, inSvg);
|
|
479
|
+
buildCtx.statements.push(
|
|
480
|
+
useSvgNamespace ? `const ${varName} = document.createElementNS(${JSON.stringify(SVG_NAMESPACE)}, "${tag}");` : `const ${varName} = document.createElement("${tag}");`
|
|
481
|
+
);
|
|
482
|
+
for (const [attr, val] of Object.entries(node.attrs)) {
|
|
483
|
+
if (attr.startsWith("data-o-on-")) {
|
|
484
|
+
if (typeof val === "string") {
|
|
485
|
+
buildCtx.nodes.push(`'${val}': ${varName}`);
|
|
486
|
+
}
|
|
487
|
+
} else if (typeof val === "string" && val.includes("__O_ATTR_")) {
|
|
488
|
+
const match = val.match(/__O_ATTR_([a-zA-Z0-9_]+)__/);
|
|
489
|
+
if (match) {
|
|
490
|
+
buildCtx.nodes.push(`'${match[1]}': ${varName}`);
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
if (val === true) {
|
|
494
|
+
buildCtx.statements.push(`${varName}.setAttribute("${attr}", "");`);
|
|
495
|
+
} else {
|
|
496
|
+
buildCtx.statements.push(
|
|
497
|
+
`${varName}.setAttribute("${attr}", ${JSON.stringify(val)});`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
for (const child of node.children) {
|
|
503
|
+
processNode(
|
|
504
|
+
child,
|
|
505
|
+
varName,
|
|
506
|
+
stateNames,
|
|
507
|
+
transformExpr,
|
|
508
|
+
counters,
|
|
509
|
+
buildCtx,
|
|
510
|
+
componentNames,
|
|
511
|
+
useSvgNamespace
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
buildCtx.statements.push(`${parentVar}.appendChild(${varName});`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function generateBuildFunction(html, stateNames, transformExpr, counters, componentNames) {
|
|
518
|
+
const ast = parseHTML(html);
|
|
519
|
+
const buildCtx = {
|
|
520
|
+
statements: [],
|
|
521
|
+
nodes: [],
|
|
522
|
+
localCount: 0
|
|
523
|
+
};
|
|
524
|
+
buildCtx.statements.push("const _f = document.createDocumentFragment();");
|
|
525
|
+
for (const child of ast) {
|
|
526
|
+
processNode(
|
|
527
|
+
child,
|
|
528
|
+
"_f",
|
|
529
|
+
stateNames,
|
|
530
|
+
transformExpr,
|
|
531
|
+
counters,
|
|
532
|
+
buildCtx,
|
|
533
|
+
componentNames
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
return `function() {
|
|
537
|
+
${buildCtx.statements.join("\n ")}
|
|
538
|
+
return { fragment: _f, nodes: { ${buildCtx.nodes.join(", ")} } };
|
|
539
|
+
}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// compiler/compile.ts
|
|
543
|
+
var generate = generatorModule.default ?? generatorModule;
|
|
544
|
+
var traverse = traverseModule.default ?? traverseModule;
|
|
545
|
+
var CORE_KNOWN_IMPORTS = /* @__PURE__ */ new Set([
|
|
546
|
+
"Signal",
|
|
547
|
+
"computed",
|
|
548
|
+
"createApp",
|
|
549
|
+
"defineComponent",
|
|
550
|
+
"derived",
|
|
551
|
+
"effect",
|
|
552
|
+
"getContext",
|
|
553
|
+
"global",
|
|
554
|
+
"hasContext",
|
|
555
|
+
"mount",
|
|
556
|
+
"setContext",
|
|
557
|
+
"state"
|
|
558
|
+
]);
|
|
559
|
+
function escapeTemplate(source) {
|
|
560
|
+
return source.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
|
561
|
+
}
|
|
562
|
+
function indexToLocation(source, index) {
|
|
563
|
+
const safeIndex = Math.max(0, Math.min(index, source.length));
|
|
564
|
+
const lines = source.slice(0, safeIndex).split("\n");
|
|
565
|
+
return {
|
|
566
|
+
line: lines.length,
|
|
567
|
+
column: lines[lines.length - 1]?.length ?? 0,
|
|
568
|
+
index: safeIndex
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function createCodeFrame(source, line, column, contextLines = 2) {
|
|
572
|
+
const lines = source.split("\n");
|
|
573
|
+
const start = Math.max(0, line - 1 - contextLines);
|
|
574
|
+
const end = Math.min(lines.length, line + contextLines);
|
|
575
|
+
return lines.slice(start, end).map((content, offset) => {
|
|
576
|
+
const lineNumber = start + offset + 1;
|
|
577
|
+
const prefix = `${String(lineNumber).padStart(4, " ")} | `;
|
|
578
|
+
if (lineNumber !== line) {
|
|
579
|
+
return `${prefix}${content}`;
|
|
580
|
+
}
|
|
581
|
+
return `${prefix}${content}
|
|
582
|
+
| ${" ".repeat(Math.max(column, 0))}^`;
|
|
583
|
+
}).join("\n");
|
|
584
|
+
}
|
|
585
|
+
function createCompileError(message, source, index) {
|
|
586
|
+
const loc = indexToLocation(source, index);
|
|
587
|
+
const error = new Error(message);
|
|
588
|
+
error.loc = { start: loc };
|
|
589
|
+
error.frame = createCodeFrame(source, loc.line, loc.column);
|
|
590
|
+
return error;
|
|
591
|
+
}
|
|
592
|
+
function normalizeCompileError(error, source, filename = "component.olova") {
|
|
593
|
+
const compileError = error instanceof Error ? error : new Error(String(error));
|
|
594
|
+
const start = compileError.loc?.start ?? compileError.loc;
|
|
595
|
+
const line = start?.line;
|
|
596
|
+
const column = start?.column;
|
|
597
|
+
const locationSuffix = line === void 0 || column === void 0 ? "" : ` (${filename}:${line}:${column + 1})`;
|
|
598
|
+
compileError.message = `[olova] ${compileError.message.replace(/^\[olova\]\s*/, "")}${locationSuffix}`;
|
|
599
|
+
if (line !== void 0 && column !== void 0 && !compileError.frame) {
|
|
600
|
+
compileError.frame = createCodeFrame(source, line, column);
|
|
601
|
+
}
|
|
602
|
+
throw compileError;
|
|
603
|
+
}
|
|
604
|
+
function parseModule(code) {
|
|
605
|
+
return parse(code, {
|
|
606
|
+
sourceType: "module",
|
|
607
|
+
plugins: ["typescript", "jsx"]
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function transpileTypeScript(code, filename = "component.ts") {
|
|
611
|
+
const result = ts.transpileModule(code, {
|
|
612
|
+
fileName: filename,
|
|
613
|
+
compilerOptions: {
|
|
614
|
+
target: ts.ScriptTarget.ES2022,
|
|
615
|
+
module: ts.ModuleKind.ESNext,
|
|
616
|
+
jsx: ts.JsxEmit.Preserve,
|
|
617
|
+
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove,
|
|
618
|
+
verbatimModuleSyntax: false,
|
|
619
|
+
sourceMap: true,
|
|
620
|
+
inlineSources: true
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
return {
|
|
624
|
+
code: result.outputText.trim(),
|
|
625
|
+
map: result.sourceMapText ? JSON.parse(result.sourceMapText) : null
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function compileModuleScript(script, filename = "module.olova.ts") {
|
|
629
|
+
const ast = parseModule(script || "");
|
|
630
|
+
return transpileTypeScript(
|
|
631
|
+
ast.program.body.map((node) => generate(node).code).join("\n"),
|
|
632
|
+
filename
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
function isModuleOnlyScript(script) {
|
|
636
|
+
if (!script.trim()) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
const ast = parseModule(script);
|
|
640
|
+
return ast.program.body.every(
|
|
641
|
+
(node) => t.isImportDeclaration(node) || t.isExportNamedDeclaration(node) || t.isExportAllDeclaration(node) || t.isExportDefaultDeclaration(node)
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
function isStateInit(node) {
|
|
645
|
+
return !!(node && t.isCallExpression(node) && (t.isIdentifier(node.callee) && node.callee.name === "state" || t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object) && node.callee.object.name === "__olovaGlobal" && t.isIdentifier(node.callee.property) && (node.callee.property.name === "state" || node.callee.property.name === "get")));
|
|
646
|
+
}
|
|
647
|
+
function isComputedInit(node) {
|
|
648
|
+
return !!(node && t.isCallExpression(node) && t.isIdentifier(node.callee) && (node.callee.name === "computed" || node.callee.name === "derived"));
|
|
649
|
+
}
|
|
650
|
+
function appendHmrStateKey(node, key) {
|
|
651
|
+
if (!node) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (t.isCallExpression(node) && t.isIdentifier(node.callee) && node.callee.name === "state") {
|
|
655
|
+
const existingKey = node.arguments[1];
|
|
656
|
+
if (!existingKey || !t.isStringLiteral(existingKey)) {
|
|
657
|
+
node.arguments = [node.arguments[0] ?? t.identifier("undefined"), t.stringLiteral(key)];
|
|
658
|
+
}
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (t.isConditionalExpression(node)) {
|
|
662
|
+
appendHmrStateKey(node.consequent, `${key}:then`);
|
|
663
|
+
appendHmrStateKey(node.alternate, `${key}:else`);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (t.isLogicalExpression(node)) {
|
|
667
|
+
appendHmrStateKey(node.left, `${key}:left`);
|
|
668
|
+
appendHmrStateKey(node.right, `${key}:right`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function containsStateInit(node) {
|
|
672
|
+
if (!node) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
if (isStateInit(node)) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
if (t.isConditionalExpression(node)) {
|
|
679
|
+
return containsStateInit(node.consequent) || containsStateInit(node.alternate);
|
|
680
|
+
}
|
|
681
|
+
if (t.isLogicalExpression(node)) {
|
|
682
|
+
return containsStateInit(node.left) || containsStateInit(node.right);
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
function containsSignalInit(node) {
|
|
687
|
+
if (!node) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
if (isStateInit(node) || isComputedInit(node)) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
if (t.isConditionalExpression(node)) {
|
|
694
|
+
return containsSignalInit(node.consequent) || containsSignalInit(node.alternate);
|
|
695
|
+
}
|
|
696
|
+
if (t.isLogicalExpression(node)) {
|
|
697
|
+
return containsSignalInit(node.left) || containsSignalInit(node.right);
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
function isGlobalStateCall(node) {
|
|
702
|
+
return t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object) && (node.callee.object.name === "global" || node.callee.object.name === "__olovaGlobal") && t.isIdentifier(node.callee.property) && node.callee.property.name === "state";
|
|
703
|
+
}
|
|
704
|
+
function shouldSkipIdentifierTransform(path) {
|
|
705
|
+
if (path.parentPath?.isVariableDeclarator() && path.parentPath.node.id === path.node) {
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
if (path.parentPath?.isAssignmentExpression() && path.parentPath.node.left === path.node) {
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
if (path.parentPath?.isUpdateExpression()) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
if (path.parentPath?.isCallExpression() && t.isIdentifier(path.parentPath.node.callee) && path.parentPath.node.callee.name === "setContext" && path.parentPath.node.arguments.includes(path.node)) {
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
if (path.parentPath?.isMemberExpression() && path.parentPath.node.property === path.node && !path.parentPath.node.computed) {
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
if (path.parentPath?.isMemberExpression() && path.parentPath.node.object === path.node && t.isIdentifier(path.parentPath.node.property) && (path.parentPath.node.property.name === "value" || path.parentPath.node.property.name === "notify")) {
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
if (path.parentPath?.isImportSpecifier() || path.parentPath?.isExportSpecifier()) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
if (path.parentPath?.isObjectProperty() && path.parentPath.node.key === path.node && !path.parentPath.node.computed && !path.parentPath.node.shorthand) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
if (path.parentPath?.isFunctionDeclaration() && path.parentPath.node.id === path.node) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
if (path.parentPath?.isClassDeclaration() && path.parentPath.node.id === path.node) {
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
function addNestedMutationNotify(ast, stateNames) {
|
|
738
|
+
traverse(ast, {
|
|
739
|
+
AssignmentExpression(path) {
|
|
740
|
+
const left = path.node.left;
|
|
741
|
+
if (!t.isMemberExpression(left)) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
let root = left;
|
|
745
|
+
while (t.isMemberExpression(root.object)) {
|
|
746
|
+
root = root.object;
|
|
747
|
+
}
|
|
748
|
+
if (!t.isIdentifier(root.object)) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (!t.isIdentifier(root.property) || root.property.name !== "value") {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const stateName = root.object.name;
|
|
755
|
+
if (!stateNames.has(stateName)) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (left === root) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (path.parentPath?.isSequenceExpression()) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
path.replaceWith(
|
|
765
|
+
t.sequenceExpression([
|
|
766
|
+
path.node,
|
|
767
|
+
t.callExpression(
|
|
768
|
+
t.memberExpression(t.identifier(stateName), t.identifier("notify")),
|
|
769
|
+
[]
|
|
770
|
+
)
|
|
771
|
+
])
|
|
772
|
+
);
|
|
773
|
+
path.skip();
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
function collectScriptInfo(script) {
|
|
778
|
+
const ast = parseModule(script || "");
|
|
779
|
+
const stateNames = /* @__PURE__ */ new Set();
|
|
780
|
+
const allSignalNames = /* @__PURE__ */ new Set();
|
|
781
|
+
const componentNames = /* @__PURE__ */ new Set();
|
|
782
|
+
const jsxFunctionNames = /* @__PURE__ */ new Set();
|
|
783
|
+
let needsGlobalRuntime = false;
|
|
784
|
+
for (const node of ast.program.body) {
|
|
785
|
+
if (!t.isImportDeclaration(node)) {
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
for (const specifier of node.specifiers) {
|
|
789
|
+
componentNames.add(specifier.local.name);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const rewrittenBody = [];
|
|
793
|
+
for (const node of ast.program.body) {
|
|
794
|
+
if (t.isImportDeclaration(node) && t.isStringLiteral(node.source) && (node.source.value === "olova/global" || node.source.value === "olova/core")) {
|
|
795
|
+
if (node.source.value === "olova/global") {
|
|
796
|
+
needsGlobalRuntime = true;
|
|
797
|
+
const declarators = [];
|
|
798
|
+
for (const specifier of node.specifiers) {
|
|
799
|
+
if (!t.isImportSpecifier(specifier)) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
"[olova] Use named imports with 'olova/global' (e.g. import { user } from 'olova/global')."
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
|
|
805
|
+
declarators.push(
|
|
806
|
+
t.variableDeclarator(
|
|
807
|
+
t.identifier(specifier.local.name),
|
|
808
|
+
t.callExpression(
|
|
809
|
+
t.memberExpression(
|
|
810
|
+
t.identifier("__olovaGlobal"),
|
|
811
|
+
t.identifier("get")
|
|
812
|
+
),
|
|
813
|
+
[t.stringLiteral(imported)]
|
|
814
|
+
)
|
|
815
|
+
)
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
if (declarators.length > 0) {
|
|
819
|
+
rewrittenBody.push(t.variableDeclaration("const", declarators));
|
|
820
|
+
}
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const keptSpecifiers = [];
|
|
824
|
+
const globalDeclarators = [];
|
|
825
|
+
for (const specifier of node.specifiers) {
|
|
826
|
+
if (!t.isImportSpecifier(specifier)) {
|
|
827
|
+
keptSpecifiers.push(specifier);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
const imported = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
|
|
831
|
+
if (CORE_KNOWN_IMPORTS.has(imported)) {
|
|
832
|
+
keptSpecifiers.push(specifier);
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
needsGlobalRuntime = true;
|
|
836
|
+
globalDeclarators.push(
|
|
837
|
+
t.variableDeclarator(
|
|
838
|
+
t.identifier(specifier.local.name),
|
|
839
|
+
t.callExpression(
|
|
840
|
+
t.memberExpression(
|
|
841
|
+
t.identifier("__olovaGlobal"),
|
|
842
|
+
t.identifier("get")
|
|
843
|
+
),
|
|
844
|
+
[t.stringLiteral(imported)]
|
|
845
|
+
)
|
|
846
|
+
)
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (keptSpecifiers.length > 0) {
|
|
850
|
+
rewrittenBody.push(
|
|
851
|
+
t.importDeclaration(keptSpecifiers, t.stringLiteral("olova/core"))
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
if (globalDeclarators.length > 0) {
|
|
855
|
+
rewrittenBody.push(t.variableDeclaration("const", globalDeclarators));
|
|
856
|
+
}
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
rewrittenBody.push(node);
|
|
860
|
+
}
|
|
861
|
+
ast.program.body = rewrittenBody;
|
|
862
|
+
traverse(ast, {
|
|
863
|
+
CallExpression(path) {
|
|
864
|
+
if (!isGlobalStateCall(path.node)) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
needsGlobalRuntime = true;
|
|
868
|
+
path.node.callee = t.memberExpression(
|
|
869
|
+
t.identifier("__olovaGlobal"),
|
|
870
|
+
t.identifier("state")
|
|
871
|
+
);
|
|
872
|
+
if (path.parentPath && path.parentPath.isVariableDeclarator() && t.isIdentifier(path.parentPath.node.id)) {
|
|
873
|
+
const firstArg = path.node.arguments[0];
|
|
874
|
+
const alreadyNamed = !!firstArg && t.isStringLiteral(firstArg) && firstArg.value.length > 0;
|
|
875
|
+
if (!alreadyNamed) {
|
|
876
|
+
path.node.arguments = [
|
|
877
|
+
t.stringLiteral(path.parentPath.node.id.name),
|
|
878
|
+
...path.node.arguments
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
traverse(ast, {
|
|
885
|
+
VariableDeclarator(path) {
|
|
886
|
+
if (!t.isIdentifier(path.node.id)) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (containsStateInit(path.node.init)) {
|
|
890
|
+
stateNames.add(path.node.id.name);
|
|
891
|
+
appendHmrStateKey(path.node.init, path.node.id.name);
|
|
892
|
+
}
|
|
893
|
+
if (containsSignalInit(path.node.init)) {
|
|
894
|
+
allSignalNames.add(path.node.id.name);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
traverse(ast, {
|
|
899
|
+
AssignmentExpression(path) {
|
|
900
|
+
if (!t.isIdentifier(path.node.left)) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const name = path.node.left.name;
|
|
904
|
+
if (!stateNames.has(name)) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const binding = path.scope.getBinding(name);
|
|
908
|
+
if (!binding || !t.isVariableDeclarator(binding.path.node)) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (!t.isIdentifier(binding.path.node.id) || !containsStateInit(binding.path.node.init)) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
path.node.left = t.memberExpression(
|
|
915
|
+
t.identifier(name),
|
|
916
|
+
t.identifier("value")
|
|
917
|
+
);
|
|
918
|
+
},
|
|
919
|
+
UpdateExpression(path) {
|
|
920
|
+
if (!t.isIdentifier(path.node.argument)) {
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
const name = path.node.argument.name;
|
|
924
|
+
if (!stateNames.has(name)) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const binding = path.scope.getBinding(name);
|
|
928
|
+
if (!binding || !t.isVariableDeclarator(binding.path.node)) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (!t.isIdentifier(binding.path.node.id) || !containsStateInit(binding.path.node.init)) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
path.node.argument = t.memberExpression(
|
|
935
|
+
t.identifier(name),
|
|
936
|
+
t.identifier("value")
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
traverse(ast, {
|
|
941
|
+
Identifier(path) {
|
|
942
|
+
const name = path.node.name;
|
|
943
|
+
if (!allSignalNames.has(name)) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (shouldSkipIdentifierTransform(path)) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const binding = path.scope.getBinding(name);
|
|
950
|
+
if (!binding || !t.isVariableDeclarator(binding.path.node)) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (!t.isIdentifier(binding.path.node.id) || !containsSignalInit(binding.path.node.init)) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
path.replaceWith(
|
|
957
|
+
t.memberExpression(t.identifier(name), t.identifier("value"))
|
|
958
|
+
);
|
|
959
|
+
path.skip();
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
addNestedMutationNotify(ast, stateNames);
|
|
963
|
+
const nodeContainsJsx = (node) => {
|
|
964
|
+
if (!node) {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
let found = false;
|
|
968
|
+
traverse(parseModule(`(${generate(node).code})`), {
|
|
969
|
+
JSXElement() {
|
|
970
|
+
found = true;
|
|
971
|
+
},
|
|
972
|
+
JSXFragment() {
|
|
973
|
+
found = true;
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
return found;
|
|
977
|
+
};
|
|
978
|
+
traverse(ast, {
|
|
979
|
+
FunctionDeclaration(path) {
|
|
980
|
+
if (path.node.id && nodeContainsJsx(path.node.body)) {
|
|
981
|
+
jsxFunctionNames.add(path.node.id.name);
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
VariableDeclarator(path) {
|
|
985
|
+
if (t.isIdentifier(path.node.id) && nodeContainsJsx(path.node.init)) {
|
|
986
|
+
jsxFunctionNames.add(path.node.id.name);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
traverse(ast, {
|
|
991
|
+
JSXElement(path) {
|
|
992
|
+
const transformed = transformExpression(
|
|
993
|
+
generate(path.node).code,
|
|
994
|
+
stateNames,
|
|
995
|
+
allSignalNames,
|
|
996
|
+
jsxFunctionNames
|
|
997
|
+
);
|
|
998
|
+
const parsed = parseModule(`(${transformed.code})`);
|
|
999
|
+
const statement = parsed.program.body[0];
|
|
1000
|
+
if (statement && t.isExpressionStatement(statement)) {
|
|
1001
|
+
path.replaceWith(statement.expression);
|
|
1002
|
+
path.skip();
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
JSXFragment(path) {
|
|
1006
|
+
const transformed = transformExpression(
|
|
1007
|
+
generate(path.node).code,
|
|
1008
|
+
stateNames,
|
|
1009
|
+
allSignalNames,
|
|
1010
|
+
jsxFunctionNames
|
|
1011
|
+
);
|
|
1012
|
+
const parsed = parseModule(`(${transformed.code})`);
|
|
1013
|
+
const statement = parsed.program.body[0];
|
|
1014
|
+
if (statement && t.isExpressionStatement(statement)) {
|
|
1015
|
+
path.replaceWith(statement.expression);
|
|
1016
|
+
path.skip();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
if (needsGlobalRuntime) {
|
|
1021
|
+
ast.program.body.unshift(
|
|
1022
|
+
t.importDeclaration(
|
|
1023
|
+
[
|
|
1024
|
+
t.importSpecifier(
|
|
1025
|
+
t.identifier("__olovaGlobal"),
|
|
1026
|
+
t.identifier("global")
|
|
1027
|
+
)
|
|
1028
|
+
],
|
|
1029
|
+
t.stringLiteral("olova/global")
|
|
1030
|
+
)
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
const importNodes = ast.program.body.filter(
|
|
1034
|
+
(node) => t.isImportDeclaration(node)
|
|
1035
|
+
);
|
|
1036
|
+
const bodyNodes = ast.program.body.filter(
|
|
1037
|
+
(node) => !t.isImportDeclaration(node)
|
|
1038
|
+
);
|
|
1039
|
+
const importsCode = importNodes.map((node) => generate(node).code).join("\n");
|
|
1040
|
+
const scriptBodyCode = bodyNodes.map((node) => generate(node).code).join("\n");
|
|
1041
|
+
return {
|
|
1042
|
+
importsCode,
|
|
1043
|
+
scriptBodyCode,
|
|
1044
|
+
stateNames,
|
|
1045
|
+
allSignalNames,
|
|
1046
|
+
componentNames,
|
|
1047
|
+
jsxFunctionNames
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames = /* @__PURE__ */ new Set(), scopeId) {
|
|
1051
|
+
const ast = parseModule(`(${expr})`);
|
|
1052
|
+
let containsJsx = false;
|
|
1053
|
+
const jsxScopeAttr = createScopeAttr(scopeId);
|
|
1054
|
+
traverse(ast, {
|
|
1055
|
+
AssignmentExpression(path) {
|
|
1056
|
+
if (!t.isIdentifier(path.node.left)) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const name = path.node.left.name;
|
|
1060
|
+
if (!stateNames.has(name)) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const binding = path.scope.getBinding(name);
|
|
1064
|
+
if (binding) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
path.node.left = t.memberExpression(
|
|
1068
|
+
t.identifier(name),
|
|
1069
|
+
t.identifier("value")
|
|
1070
|
+
);
|
|
1071
|
+
},
|
|
1072
|
+
UpdateExpression(path) {
|
|
1073
|
+
if (!t.isIdentifier(path.node.argument)) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const name = path.node.argument.name;
|
|
1077
|
+
if (!stateNames.has(name)) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const binding = path.scope.getBinding(name);
|
|
1081
|
+
if (binding) {
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
path.node.argument = t.memberExpression(
|
|
1085
|
+
t.identifier(name),
|
|
1086
|
+
t.identifier("value")
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
traverse(ast, {
|
|
1091
|
+
Identifier(path) {
|
|
1092
|
+
const name = path.node.name;
|
|
1093
|
+
if (!allSignalNames.has(name)) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (shouldSkipIdentifierTransform(path)) {
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const binding = path.scope.getBinding(name);
|
|
1100
|
+
if (binding) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
path.replaceWith(
|
|
1104
|
+
t.memberExpression(t.identifier(name), t.identifier("value"))
|
|
1105
|
+
);
|
|
1106
|
+
path.skip();
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
addNestedMutationNotify(ast, stateNames);
|
|
1110
|
+
const toStringCall = (input) => t.callExpression(t.identifier("__olovaToString"), [input]);
|
|
1111
|
+
const escapeCall = (input) => t.callExpression(t.identifier("__olovaEscape"), [input]);
|
|
1112
|
+
const expressionContainsJsx = (input) => {
|
|
1113
|
+
let found = false;
|
|
1114
|
+
traverse(parseModule(`(${generate(input).code})`), {
|
|
1115
|
+
JSXElement() {
|
|
1116
|
+
found = true;
|
|
1117
|
+
},
|
|
1118
|
+
JSXFragment() {
|
|
1119
|
+
found = true;
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
return found;
|
|
1123
|
+
};
|
|
1124
|
+
const expressionProducesHtml = (input) => {
|
|
1125
|
+
if (!input) {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
if (expressionContainsJsx(input)) {
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
let found = false;
|
|
1132
|
+
traverse(parseModule(`(${generate(input).code})`), {
|
|
1133
|
+
CallExpression(path) {
|
|
1134
|
+
if (t.isIdentifier(path.node.callee) && jsxFunctionNames.has(path.node.callee.name)) {
|
|
1135
|
+
found = true;
|
|
1136
|
+
path.stop();
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.property) && (path.node.callee.property.name === "map" || path.node.callee.property.name === "flatMap")) {
|
|
1140
|
+
const [firstArg] = path.node.arguments;
|
|
1141
|
+
if (t.isIdentifier(firstArg) && jsxFunctionNames.has(firstArg.name) || nodeContainsRenderableFunction(firstArg, jsxFunctionNames)) {
|
|
1142
|
+
found = true;
|
|
1143
|
+
path.stop();
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
return found;
|
|
1149
|
+
};
|
|
1150
|
+
const nodeContainsRenderableFunction = (node, knownJsxFunctions) => {
|
|
1151
|
+
if (!node) {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
|
|
1155
|
+
return expressionContainsJsx(node.body) || expressionProducesHtml(node.body);
|
|
1156
|
+
}
|
|
1157
|
+
if (t.isIdentifier(node)) {
|
|
1158
|
+
return knownJsxFunctions.has(node.name);
|
|
1159
|
+
}
|
|
1160
|
+
return false;
|
|
1161
|
+
};
|
|
1162
|
+
const concatExpressions = (parts) => {
|
|
1163
|
+
if (parts.length === 0) {
|
|
1164
|
+
return t.stringLiteral("");
|
|
1165
|
+
}
|
|
1166
|
+
return parts.slice(1).reduce(
|
|
1167
|
+
(acc, current) => t.binaryExpression("+", acc, current),
|
|
1168
|
+
parts[0]
|
|
1169
|
+
);
|
|
1170
|
+
};
|
|
1171
|
+
const jsxNameToString = (name) => {
|
|
1172
|
+
if (t.isJSXIdentifier(name)) {
|
|
1173
|
+
return name.name;
|
|
1174
|
+
}
|
|
1175
|
+
if (t.isJSXNamespacedName(name)) {
|
|
1176
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
1177
|
+
}
|
|
1178
|
+
const walkMember = (node) => {
|
|
1179
|
+
if (t.isJSXIdentifier(node)) {
|
|
1180
|
+
return node.name;
|
|
1181
|
+
}
|
|
1182
|
+
return `${walkMember(node.object)}.${node.property.name}`;
|
|
1183
|
+
};
|
|
1184
|
+
return walkMember(name);
|
|
1185
|
+
};
|
|
1186
|
+
const transformEmbeddedJsxExpression = (input) => {
|
|
1187
|
+
const wrapped = parseModule(`(${generate(input).code})`);
|
|
1188
|
+
let containsNestedJsx = false;
|
|
1189
|
+
traverse(wrapped, {
|
|
1190
|
+
JSXElement(path) {
|
|
1191
|
+
containsNestedJsx = true;
|
|
1192
|
+
path.replaceWith(jsxElementToExpression(path.node));
|
|
1193
|
+
path.skip();
|
|
1194
|
+
},
|
|
1195
|
+
JSXFragment(path) {
|
|
1196
|
+
containsNestedJsx = true;
|
|
1197
|
+
path.replaceWith(jsxFragmentToExpression(path.node));
|
|
1198
|
+
path.skip();
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
const statement2 = wrapped.program.body[0];
|
|
1202
|
+
if (!statement2 || !t.isExpressionStatement(statement2)) {
|
|
1203
|
+
return { expression: input, containsJsx: containsNestedJsx };
|
|
1204
|
+
}
|
|
1205
|
+
return {
|
|
1206
|
+
expression: statement2.expression,
|
|
1207
|
+
containsJsx: containsNestedJsx
|
|
1208
|
+
};
|
|
1209
|
+
};
|
|
1210
|
+
const jsxAttrToExpression = (attr) => {
|
|
1211
|
+
if (t.isJSXSpreadAttribute(attr)) {
|
|
1212
|
+
const transformed = transformEmbeddedJsxExpression(
|
|
1213
|
+
attr.argument
|
|
1214
|
+
);
|
|
1215
|
+
return t.callExpression(t.identifier("__olovaSpreadAttrs"), [
|
|
1216
|
+
transformed.expression
|
|
1217
|
+
]);
|
|
1218
|
+
}
|
|
1219
|
+
const name = jsxNameToString(attr.name);
|
|
1220
|
+
if (name === "key") {
|
|
1221
|
+
return t.stringLiteral("");
|
|
1222
|
+
}
|
|
1223
|
+
const normalizedName = name === "className" ? "class" : name;
|
|
1224
|
+
const eventMatch = name.match(/^(?:on:|on)([A-Za-z]+)$/);
|
|
1225
|
+
if (!attr.value) {
|
|
1226
|
+
return t.stringLiteral(` ${normalizedName}`);
|
|
1227
|
+
}
|
|
1228
|
+
if (t.isStringLiteral(attr.value)) {
|
|
1229
|
+
return t.stringLiteral(` ${normalizedName}="${attr.value.value}"`);
|
|
1230
|
+
}
|
|
1231
|
+
if (t.isJSXExpressionContainer(attr.value)) {
|
|
1232
|
+
const transformed = transformEmbeddedJsxExpression(
|
|
1233
|
+
attr.value.expression ?? t.stringLiteral("")
|
|
1234
|
+
);
|
|
1235
|
+
if (eventMatch) {
|
|
1236
|
+
return t.callExpression(t.identifier("__olovaRegisterJsxEvent"), [
|
|
1237
|
+
t.stringLiteral(eventMatch[1].toLowerCase()),
|
|
1238
|
+
transformed.expression
|
|
1239
|
+
]);
|
|
1240
|
+
}
|
|
1241
|
+
const valueExpression = normalizedName === "class" ? t.callExpression(t.identifier("__olovaNormalizeClassValue"), [
|
|
1242
|
+
transformed.expression
|
|
1243
|
+
]) : transformed.expression;
|
|
1244
|
+
return concatExpressions([
|
|
1245
|
+
t.stringLiteral(` ${normalizedName}="`),
|
|
1246
|
+
escapeCall(valueExpression),
|
|
1247
|
+
t.stringLiteral('"')
|
|
1248
|
+
]);
|
|
1249
|
+
}
|
|
1250
|
+
return t.stringLiteral("");
|
|
1251
|
+
};
|
|
1252
|
+
const jsxChildToExpression = (child) => {
|
|
1253
|
+
if (t.isJSXText(child)) {
|
|
1254
|
+
return t.stringLiteral(child.value);
|
|
1255
|
+
}
|
|
1256
|
+
if (t.isJSXExpressionContainer(child)) {
|
|
1257
|
+
if (t.isJSXEmptyExpression(child.expression)) {
|
|
1258
|
+
return t.stringLiteral("");
|
|
1259
|
+
}
|
|
1260
|
+
const rawExpression = child.expression;
|
|
1261
|
+
const transformed = transformEmbeddedJsxExpression(rawExpression);
|
|
1262
|
+
return transformed.containsJsx || expressionProducesHtml(rawExpression) ? toStringCall(transformed.expression) : escapeCall(transformed.expression);
|
|
1263
|
+
}
|
|
1264
|
+
if (t.isJSXSpreadChild(child)) {
|
|
1265
|
+
const transformed = transformEmbeddedJsxExpression(child.expression);
|
|
1266
|
+
return transformed.containsJsx || expressionProducesHtml(child.expression) ? toStringCall(transformed.expression) : escapeCall(transformed.expression);
|
|
1267
|
+
}
|
|
1268
|
+
if (t.isJSXElement(child)) {
|
|
1269
|
+
return jsxElementToExpression(child);
|
|
1270
|
+
}
|
|
1271
|
+
return jsxFragmentToExpression(child);
|
|
1272
|
+
};
|
|
1273
|
+
const jsxFragmentToExpression = (fragment) => {
|
|
1274
|
+
const parts = fragment.children.map((child) => jsxChildToExpression(child));
|
|
1275
|
+
return concatExpressions(parts);
|
|
1276
|
+
};
|
|
1277
|
+
const jsxElementToExpression = (element) => {
|
|
1278
|
+
const opening = element.openingElement;
|
|
1279
|
+
const tag = jsxNameToString(opening.name);
|
|
1280
|
+
if (tag === "For") {
|
|
1281
|
+
let eachExpr = t.arrayExpression([]);
|
|
1282
|
+
for (const attr of opening.attributes) {
|
|
1283
|
+
if (t.isJSXAttribute(attr) && jsxNameToString(attr.name) === "each" && attr.value && t.isJSXExpressionContainer(attr.value) && !t.isJSXEmptyExpression(attr.value.expression)) {
|
|
1284
|
+
eachExpr = transformEmbeddedJsxExpression(
|
|
1285
|
+
attr.value.expression
|
|
1286
|
+
).expression;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
const childRenderer = element.children.find(
|
|
1290
|
+
(child) => t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)
|
|
1291
|
+
);
|
|
1292
|
+
if (!childRenderer) {
|
|
1293
|
+
return t.stringLiteral("");
|
|
1294
|
+
}
|
|
1295
|
+
return t.callExpression(t.identifier("__olovaFor"), [
|
|
1296
|
+
eachExpr,
|
|
1297
|
+
transformEmbeddedJsxExpression(
|
|
1298
|
+
childRenderer.expression
|
|
1299
|
+
).expression
|
|
1300
|
+
]);
|
|
1301
|
+
}
|
|
1302
|
+
if (tag === "Show") {
|
|
1303
|
+
let whenExpr = t.booleanLiteral(false);
|
|
1304
|
+
let fallbackExpr = t.stringLiteral("");
|
|
1305
|
+
for (const attr of opening.attributes) {
|
|
1306
|
+
if (!t.isJSXAttribute(attr)) {
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
const attrName = jsxNameToString(attr.name);
|
|
1310
|
+
if (attrName === "when" && attr.value && t.isJSXExpressionContainer(attr.value) && !t.isJSXEmptyExpression(attr.value.expression)) {
|
|
1311
|
+
whenExpr = transformEmbeddedJsxExpression(
|
|
1312
|
+
attr.value.expression
|
|
1313
|
+
).expression;
|
|
1314
|
+
}
|
|
1315
|
+
if (attrName === "fallback" && attr.value && t.isJSXExpressionContainer(attr.value) && !t.isJSXEmptyExpression(attr.value.expression)) {
|
|
1316
|
+
fallbackExpr = transformEmbeddedJsxExpression(
|
|
1317
|
+
attr.value.expression
|
|
1318
|
+
).expression;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const childExpr2 = concatExpressions(
|
|
1322
|
+
element.children.map((child) => jsxChildToExpression(child)).filter(Boolean)
|
|
1323
|
+
);
|
|
1324
|
+
return t.callExpression(t.identifier("__olovaShow"), [
|
|
1325
|
+
whenExpr,
|
|
1326
|
+
t.arrowFunctionExpression(
|
|
1327
|
+
[t.identifier("__value")],
|
|
1328
|
+
childExpr2
|
|
1329
|
+
),
|
|
1330
|
+
fallbackExpr
|
|
1331
|
+
]);
|
|
1332
|
+
}
|
|
1333
|
+
const attrExpr = concatExpressions(
|
|
1334
|
+
[
|
|
1335
|
+
...jsxScopeAttr ? [t.stringLiteral(` ${jsxScopeAttr}`)] : [],
|
|
1336
|
+
...opening.attributes.map((attr) => jsxAttrToExpression(attr))
|
|
1337
|
+
]
|
|
1338
|
+
);
|
|
1339
|
+
const childExpr = concatExpressions(
|
|
1340
|
+
element.children.map((child) => jsxChildToExpression(child)).filter(Boolean)
|
|
1341
|
+
);
|
|
1342
|
+
if (opening.selfClosing) {
|
|
1343
|
+
return concatExpressions([
|
|
1344
|
+
t.stringLiteral(`<${tag}`),
|
|
1345
|
+
attrExpr,
|
|
1346
|
+
t.stringLiteral("/>")
|
|
1347
|
+
]);
|
|
1348
|
+
}
|
|
1349
|
+
return concatExpressions([
|
|
1350
|
+
t.stringLiteral(`<${tag}`),
|
|
1351
|
+
attrExpr,
|
|
1352
|
+
t.stringLiteral(">"),
|
|
1353
|
+
childExpr,
|
|
1354
|
+
t.stringLiteral(`</${tag}>`)
|
|
1355
|
+
]);
|
|
1356
|
+
};
|
|
1357
|
+
traverse(ast, {
|
|
1358
|
+
JSXElement(path) {
|
|
1359
|
+
containsJsx = true;
|
|
1360
|
+
path.replaceWith(jsxElementToExpression(path.node));
|
|
1361
|
+
path.skip();
|
|
1362
|
+
},
|
|
1363
|
+
JSXFragment(path) {
|
|
1364
|
+
containsJsx = true;
|
|
1365
|
+
path.replaceWith(jsxFragmentToExpression(path.node));
|
|
1366
|
+
path.skip();
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
const statement = ast.program.body[0];
|
|
1370
|
+
if (!statement || !t.isExpressionStatement(statement)) {
|
|
1371
|
+
return { code: expr.trim(), containsJsx };
|
|
1372
|
+
}
|
|
1373
|
+
containsJsx = containsJsx || expressionProducesHtml(statement.expression);
|
|
1374
|
+
return { code: generate(statement.expression).code, containsJsx };
|
|
1375
|
+
}
|
|
1376
|
+
function jsxToTemplateHtml(node) {
|
|
1377
|
+
if (t.isJSXElement(node)) {
|
|
1378
|
+
const opening = node.openingElement;
|
|
1379
|
+
let tag = "";
|
|
1380
|
+
if (t.isJSXIdentifier(opening.name)) {
|
|
1381
|
+
tag = opening.name.name;
|
|
1382
|
+
} else if (t.isJSXMemberExpression(opening.name)) {
|
|
1383
|
+
const walk = (n) => t.isJSXIdentifier(n) ? n.name : `${walk(n.object)}.${n.property.name}`;
|
|
1384
|
+
tag = walk(opening.name);
|
|
1385
|
+
} else if (t.isJSXNamespacedName(opening.name)) {
|
|
1386
|
+
tag = `${opening.name.namespace.name}:${opening.name.name.name}`;
|
|
1387
|
+
}
|
|
1388
|
+
let attrsStr = "";
|
|
1389
|
+
for (const attr of opening.attributes) {
|
|
1390
|
+
if (t.isJSXSpreadAttribute(attr)) {
|
|
1391
|
+
attrsStr += ` {...${generate(attr.argument).code}}`;
|
|
1392
|
+
} else {
|
|
1393
|
+
let attrName = "";
|
|
1394
|
+
if (t.isJSXIdentifier(attr.name)) attrName = attr.name.name;
|
|
1395
|
+
else attrName = `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
1396
|
+
if (!attr.value) attrsStr += ` ${attrName}`;
|
|
1397
|
+
else if (t.isStringLiteral(attr.value))
|
|
1398
|
+
attrsStr += ` ${attrName}="${attr.value.value}"`;
|
|
1399
|
+
else if (t.isJSXExpressionContainer(attr.value)) {
|
|
1400
|
+
if (!t.isJSXEmptyExpression(attr.value.expression)) {
|
|
1401
|
+
attrsStr += ` ${attrName}={${generate(attr.value.expression).code}}`;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
const childrenStr = node.children.map(jsxToTemplateHtml).join("");
|
|
1407
|
+
if (opening.selfClosing) {
|
|
1408
|
+
return `<${tag}${attrsStr} />`;
|
|
1409
|
+
}
|
|
1410
|
+
return `<${tag}${attrsStr}>${childrenStr}</${tag}>`;
|
|
1411
|
+
}
|
|
1412
|
+
if (t.isJSXText(node)) {
|
|
1413
|
+
return node.value;
|
|
1414
|
+
}
|
|
1415
|
+
if (t.isJSXExpressionContainer(node)) {
|
|
1416
|
+
if (t.isJSXEmptyExpression(node.expression)) return "";
|
|
1417
|
+
return `{${generate(node.expression).code}}`;
|
|
1418
|
+
}
|
|
1419
|
+
if (t.isJSXFragment(node)) {
|
|
1420
|
+
return node.children.map(jsxToTemplateHtml).join("");
|
|
1421
|
+
}
|
|
1422
|
+
return "";
|
|
1423
|
+
}
|
|
1424
|
+
function replaceTemplateExpressions(input, replacer) {
|
|
1425
|
+
let output = "";
|
|
1426
|
+
let index = 0;
|
|
1427
|
+
while (index < input.length) {
|
|
1428
|
+
const start = input.indexOf("{", index);
|
|
1429
|
+
if (start < 0) {
|
|
1430
|
+
output += input.slice(index);
|
|
1431
|
+
break;
|
|
1432
|
+
}
|
|
1433
|
+
let prevIndex = start - 1;
|
|
1434
|
+
while (prevIndex >= 0 && /\s/.test(input[prevIndex])) {
|
|
1435
|
+
prevIndex -= 1;
|
|
1436
|
+
}
|
|
1437
|
+
if (prevIndex >= 0 && input[prevIndex] === "=") {
|
|
1438
|
+
output += input.slice(index, start + 1);
|
|
1439
|
+
index = start + 1;
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
if (start > 0 && input[start - 1] === "$") {
|
|
1443
|
+
output += input.slice(index, start + 1);
|
|
1444
|
+
index = start + 1;
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
output += input.slice(index, start);
|
|
1448
|
+
let depth = 1;
|
|
1449
|
+
let cursor = start + 1;
|
|
1450
|
+
let quote = null;
|
|
1451
|
+
while (cursor < input.length && depth > 0) {
|
|
1452
|
+
const char = input[cursor];
|
|
1453
|
+
const prev = input[cursor - 1];
|
|
1454
|
+
if (quote) {
|
|
1455
|
+
if (char === quote && prev !== "\\") {
|
|
1456
|
+
quote = null;
|
|
1457
|
+
}
|
|
1458
|
+
cursor += 1;
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
1462
|
+
quote = char;
|
|
1463
|
+
cursor += 1;
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (char === "{") {
|
|
1467
|
+
depth += 1;
|
|
1468
|
+
} else if (char === "}") {
|
|
1469
|
+
depth -= 1;
|
|
1470
|
+
}
|
|
1471
|
+
cursor += 1;
|
|
1472
|
+
}
|
|
1473
|
+
if (depth !== 0) {
|
|
1474
|
+
throw createCompileError("[olova] Unclosed template expression block.", input, start);
|
|
1475
|
+
}
|
|
1476
|
+
const expression = input.slice(start + 1, cursor - 1);
|
|
1477
|
+
output += replacer(expression);
|
|
1478
|
+
index = cursor;
|
|
1479
|
+
}
|
|
1480
|
+
return output;
|
|
1481
|
+
}
|
|
1482
|
+
function parsePropsFromAttrs(attrs, stateNames, allSignalNames, jsxFunctionNames) {
|
|
1483
|
+
const props = [];
|
|
1484
|
+
for (const [key, rawValue] of Object.entries(attrs)) {
|
|
1485
|
+
if (key === "slot" || key === "key") {
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
const propName = key === "className" ? "class" : key;
|
|
1489
|
+
if (rawValue === true) {
|
|
1490
|
+
props.push(`${JSON.stringify(propName)}: true`);
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
if (rawValue.startsWith("{") && rawValue.endsWith("}")) {
|
|
1494
|
+
const expr = rawValue.slice(1, -1);
|
|
1495
|
+
const valueExpr = transformExpression(
|
|
1496
|
+
expr,
|
|
1497
|
+
stateNames,
|
|
1498
|
+
allSignalNames,
|
|
1499
|
+
jsxFunctionNames
|
|
1500
|
+
).code;
|
|
1501
|
+
props.push(
|
|
1502
|
+
`${JSON.stringify(propName)}: (${propName === "class" ? `__olovaNormalizeClassValue(${valueExpr})` : valueExpr})`
|
|
1503
|
+
);
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
props.push(`${JSON.stringify(propName)}: ${JSON.stringify(rawValue)}`);
|
|
1507
|
+
}
|
|
1508
|
+
return props.length === 0 ? "{}" : `{ ${props.join(", ")} }`;
|
|
1509
|
+
}
|
|
1510
|
+
function transformTemplate(template, stateNames, allSignalNames, jsxFunctionNames, componentNames, counters, scopeId) {
|
|
1511
|
+
let html = template;
|
|
1512
|
+
const textBindings = [];
|
|
1513
|
+
const htmlBindings = [];
|
|
1514
|
+
const attrBindings = [];
|
|
1515
|
+
const eventBindings = [];
|
|
1516
|
+
const slotBindings = [];
|
|
1517
|
+
const componentBindings = [];
|
|
1518
|
+
const ifBindings = [];
|
|
1519
|
+
html = replaceTemplateExpressions(html, (expr) => {
|
|
1520
|
+
try {
|
|
1521
|
+
const ast = parseModule(`(${expr})`);
|
|
1522
|
+
const exprNode = ast.program.body[0];
|
|
1523
|
+
if (t.isExpressionStatement(exprNode)) {
|
|
1524
|
+
const node = exprNode.expression;
|
|
1525
|
+
const containsJsxNode = (n) => {
|
|
1526
|
+
let found = false;
|
|
1527
|
+
traverse(parseModule(`(${generate(n).code})`), {
|
|
1528
|
+
JSXElement() {
|
|
1529
|
+
found = true;
|
|
1530
|
+
},
|
|
1531
|
+
JSXFragment() {
|
|
1532
|
+
found = true;
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
return found;
|
|
1536
|
+
};
|
|
1537
|
+
if (t.isLogicalExpression(node) && node.operator === "&&") {
|
|
1538
|
+
if (containsJsxNode(node.right)) {
|
|
1539
|
+
const id = `i${counters.if++}`;
|
|
1540
|
+
const condition = generate(node.left).code;
|
|
1541
|
+
const trueBranchHtml = jsxToTemplateHtml(node.right);
|
|
1542
|
+
ifBindings.push({
|
|
1543
|
+
id,
|
|
1544
|
+
expr: transformExpression(
|
|
1545
|
+
condition,
|
|
1546
|
+
stateNames,
|
|
1547
|
+
allSignalNames,
|
|
1548
|
+
jsxFunctionNames,
|
|
1549
|
+
scopeId
|
|
1550
|
+
).code,
|
|
1551
|
+
trueBranch: transformTemplate(
|
|
1552
|
+
trueBranchHtml,
|
|
1553
|
+
stateNames,
|
|
1554
|
+
allSignalNames,
|
|
1555
|
+
jsxFunctionNames,
|
|
1556
|
+
componentNames,
|
|
1557
|
+
counters,
|
|
1558
|
+
scopeId
|
|
1559
|
+
)
|
|
1560
|
+
});
|
|
1561
|
+
return `__O_IF_${id}__`;
|
|
1562
|
+
}
|
|
1563
|
+
} else if (t.isConditionalExpression(node)) {
|
|
1564
|
+
if (containsJsxNode(node.consequent) || containsJsxNode(node.alternate)) {
|
|
1565
|
+
const id = `i${counters.if++}`;
|
|
1566
|
+
const condition = generate(node.test).code;
|
|
1567
|
+
const trueBranchHtml = containsJsxNode(node.consequent) ? jsxToTemplateHtml(node.consequent) : `{${generate(node.consequent).code}}`;
|
|
1568
|
+
const falseBranchHtml = containsJsxNode(node.alternate) ? jsxToTemplateHtml(node.alternate) : `{${generate(node.alternate).code}}`;
|
|
1569
|
+
ifBindings.push({
|
|
1570
|
+
id,
|
|
1571
|
+
expr: transformExpression(
|
|
1572
|
+
condition,
|
|
1573
|
+
stateNames,
|
|
1574
|
+
allSignalNames,
|
|
1575
|
+
jsxFunctionNames,
|
|
1576
|
+
scopeId
|
|
1577
|
+
).code,
|
|
1578
|
+
trueBranch: transformTemplate(
|
|
1579
|
+
trueBranchHtml,
|
|
1580
|
+
stateNames,
|
|
1581
|
+
allSignalNames,
|
|
1582
|
+
jsxFunctionNames,
|
|
1583
|
+
componentNames,
|
|
1584
|
+
counters,
|
|
1585
|
+
scopeId
|
|
1586
|
+
),
|
|
1587
|
+
falseBranch: transformTemplate(
|
|
1588
|
+
falseBranchHtml,
|
|
1589
|
+
stateNames,
|
|
1590
|
+
allSignalNames,
|
|
1591
|
+
jsxFunctionNames,
|
|
1592
|
+
componentNames,
|
|
1593
|
+
counters,
|
|
1594
|
+
scopeId
|
|
1595
|
+
)
|
|
1596
|
+
});
|
|
1597
|
+
return `__O_IF_${id}__`;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
} catch {
|
|
1602
|
+
}
|
|
1603
|
+
return `{${expr}}`;
|
|
1604
|
+
});
|
|
1605
|
+
const rawExpressions = [];
|
|
1606
|
+
html = replaceTemplateExpressions(html, (expr) => {
|
|
1607
|
+
const id = `raw_${rawExpressions.length}`;
|
|
1608
|
+
rawExpressions.push({ id, expression: expr });
|
|
1609
|
+
return `__O_RAW_EXPR_${id}__`;
|
|
1610
|
+
});
|
|
1611
|
+
const renderTextContent = (content) => {
|
|
1612
|
+
return content.replace(
|
|
1613
|
+
/__O_RAW_EXPR_([a-zA-Z0-9_]+)__/g,
|
|
1614
|
+
(_full, exprId) => {
|
|
1615
|
+
const found = rawExpressions.find((entry) => entry.id === exprId);
|
|
1616
|
+
const expr = found ? found.expression : "";
|
|
1617
|
+
const transformed = transformExpression(
|
|
1618
|
+
expr,
|
|
1619
|
+
stateNames,
|
|
1620
|
+
allSignalNames,
|
|
1621
|
+
jsxFunctionNames,
|
|
1622
|
+
scopeId
|
|
1623
|
+
);
|
|
1624
|
+
if (transformed.containsJsx) {
|
|
1625
|
+
const id2 = `h${counters.text++}`;
|
|
1626
|
+
htmlBindings.push({
|
|
1627
|
+
id: id2,
|
|
1628
|
+
expr: `__olovaDangerouslySetHtml(${transformed.code})`
|
|
1629
|
+
});
|
|
1630
|
+
return `__O_HTML_${id2}__`;
|
|
1631
|
+
}
|
|
1632
|
+
const id = `t${counters.text++}`;
|
|
1633
|
+
textBindings.push({ id, expr: transformed.code });
|
|
1634
|
+
return `__O_TEXT_${id}__`;
|
|
1635
|
+
}
|
|
1636
|
+
);
|
|
1637
|
+
};
|
|
1638
|
+
const renderAttrs = (element) => {
|
|
1639
|
+
let output = "";
|
|
1640
|
+
const scopeAttr = createScopeAttr(scopeId);
|
|
1641
|
+
if (scopeAttr) {
|
|
1642
|
+
output += ` ${scopeAttr}`;
|
|
1643
|
+
}
|
|
1644
|
+
for (const [attrName, rawValue] of Object.entries(element.attrs)) {
|
|
1645
|
+
if (attrName === "key") {
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
const normalizedAttrName = attrName === "className" ? "class" : attrName;
|
|
1649
|
+
if (rawValue === true) {
|
|
1650
|
+
output += ` ${normalizedAttrName}`;
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
if (rawValue.startsWith("{") && rawValue.endsWith("}")) {
|
|
1654
|
+
const expr = rawValue.slice(1, -1);
|
|
1655
|
+
const eventMatch = normalizedAttrName.match(/^(?:on:|on)([A-Za-z]+)$/);
|
|
1656
|
+
if (eventMatch) {
|
|
1657
|
+
const id2 = `e${counters.event++}`;
|
|
1658
|
+
const event = eventMatch[1].toLowerCase();
|
|
1659
|
+
eventBindings.push({
|
|
1660
|
+
id: id2,
|
|
1661
|
+
event,
|
|
1662
|
+
expr: transformExpression(
|
|
1663
|
+
expr,
|
|
1664
|
+
stateNames,
|
|
1665
|
+
allSignalNames,
|
|
1666
|
+
jsxFunctionNames
|
|
1667
|
+
).code
|
|
1668
|
+
});
|
|
1669
|
+
output += ` data-o-on-${event}="${id2}"`;
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
const id = `a${counters.attr++}`;
|
|
1673
|
+
attrBindings.push({
|
|
1674
|
+
id,
|
|
1675
|
+
attr: normalizedAttrName,
|
|
1676
|
+
expr: normalizedAttrName === "class" ? `__olovaNormalizeClassValue(${transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames).code})` : transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames).code
|
|
1677
|
+
});
|
|
1678
|
+
output += ` ${normalizedAttrName}="__O_ATTR_${id}__"`;
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
output += ` ${normalizedAttrName}="${rawValue.replace(/"/g, """)}"`;
|
|
1682
|
+
}
|
|
1683
|
+
return output;
|
|
1684
|
+
};
|
|
1685
|
+
const serializeRawNodes = (nodes) => nodes.map((node) => {
|
|
1686
|
+
if (node.type === "comment") {
|
|
1687
|
+
return `<!--${node.content}-->`;
|
|
1688
|
+
}
|
|
1689
|
+
if (node.type === "text") {
|
|
1690
|
+
return node.content;
|
|
1691
|
+
}
|
|
1692
|
+
const attrs = Object.entries(node.attrs).map(([name, value]) => {
|
|
1693
|
+
if (value === true) {
|
|
1694
|
+
return ` ${name}`;
|
|
1695
|
+
}
|
|
1696
|
+
return ` ${name}="${String(value).replace(/"/g, """)}"`;
|
|
1697
|
+
}).join("");
|
|
1698
|
+
const children = serializeRawNodes(node.children);
|
|
1699
|
+
if (node.isSelfClosing) {
|
|
1700
|
+
return `<${node.tag}${attrs} />`;
|
|
1701
|
+
}
|
|
1702
|
+
return `<${node.tag}${attrs}>${children}</${node.tag}>`;
|
|
1703
|
+
}).join("");
|
|
1704
|
+
const renderNodes = (nodes) => nodes.map((node) => {
|
|
1705
|
+
if (node.type === "comment") {
|
|
1706
|
+
return `<!--${node.content}-->`;
|
|
1707
|
+
}
|
|
1708
|
+
if (node.type === "text") {
|
|
1709
|
+
return renderTextContent(node.content);
|
|
1710
|
+
}
|
|
1711
|
+
if (node.tag === "slot") {
|
|
1712
|
+
const id = `s${counters.slot++}`;
|
|
1713
|
+
const slotName = typeof node.attrs.name === "string" ? node.attrs.name : "default";
|
|
1714
|
+
slotBindings.push({ id, name: slotName });
|
|
1715
|
+
return `__O_SLOT_${id}__`;
|
|
1716
|
+
}
|
|
1717
|
+
if (componentNames.has(node.tag)) {
|
|
1718
|
+
const id = `c${counters.comp++}`;
|
|
1719
|
+
const namedSlotNodes = /* @__PURE__ */ new Map();
|
|
1720
|
+
const defaultNodes = [];
|
|
1721
|
+
for (const child of node.children) {
|
|
1722
|
+
if (child.type === "element" && child.tag === "template" && typeof child.attrs.slot === "string") {
|
|
1723
|
+
namedSlotNodes.set(child.attrs.slot, child.children);
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
defaultNodes.push(child);
|
|
1727
|
+
}
|
|
1728
|
+
const slotFactories = [];
|
|
1729
|
+
if (defaultNodes.length > 0) {
|
|
1730
|
+
slotFactories.push({
|
|
1731
|
+
varName: `__slot_${id}_default`,
|
|
1732
|
+
name: "default",
|
|
1733
|
+
descriptor: transformTemplate(
|
|
1734
|
+
serializeRawNodes(defaultNodes),
|
|
1735
|
+
stateNames,
|
|
1736
|
+
allSignalNames,
|
|
1737
|
+
jsxFunctionNames,
|
|
1738
|
+
componentNames,
|
|
1739
|
+
counters,
|
|
1740
|
+
scopeId
|
|
1741
|
+
)
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
for (const [slotName, slotChildren] of namedSlotNodes.entries()) {
|
|
1745
|
+
slotFactories.push({
|
|
1746
|
+
varName: `__slot_${id}_${slotName.replace(/[^A-Za-z0-9_$]/g, "_")}`,
|
|
1747
|
+
name: slotName,
|
|
1748
|
+
descriptor: transformTemplate(
|
|
1749
|
+
serializeRawNodes(slotChildren),
|
|
1750
|
+
stateNames,
|
|
1751
|
+
allSignalNames,
|
|
1752
|
+
jsxFunctionNames,
|
|
1753
|
+
componentNames,
|
|
1754
|
+
counters,
|
|
1755
|
+
scopeId
|
|
1756
|
+
)
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
componentBindings.push({
|
|
1760
|
+
id,
|
|
1761
|
+
component: node.tag,
|
|
1762
|
+
propsExpr: parsePropsFromAttrs(
|
|
1763
|
+
node.attrs,
|
|
1764
|
+
stateNames,
|
|
1765
|
+
allSignalNames,
|
|
1766
|
+
jsxFunctionNames
|
|
1767
|
+
),
|
|
1768
|
+
slotFactories
|
|
1769
|
+
});
|
|
1770
|
+
return `__O_COMP_${id}__`;
|
|
1771
|
+
}
|
|
1772
|
+
const attrs = renderAttrs(node);
|
|
1773
|
+
const children = renderNodes(node.children);
|
|
1774
|
+
if (node.isSelfClosing) {
|
|
1775
|
+
return `<${node.tag}${attrs} />`;
|
|
1776
|
+
}
|
|
1777
|
+
return `<${node.tag}${attrs}>${children}</${node.tag}>`;
|
|
1778
|
+
}).join("");
|
|
1779
|
+
html = renderNodes(parseHTML(html));
|
|
1780
|
+
const resolveExpr = (expr) => transformExpression(
|
|
1781
|
+
expr,
|
|
1782
|
+
stateNames,
|
|
1783
|
+
allSignalNames,
|
|
1784
|
+
jsxFunctionNames,
|
|
1785
|
+
scopeId
|
|
1786
|
+
);
|
|
1787
|
+
const buildFnStr = generateBuildFunction(
|
|
1788
|
+
escapeTemplate(html),
|
|
1789
|
+
stateNames,
|
|
1790
|
+
resolveExpr,
|
|
1791
|
+
counters,
|
|
1792
|
+
componentNames
|
|
1793
|
+
);
|
|
1794
|
+
return {
|
|
1795
|
+
html: escapeTemplate(html),
|
|
1796
|
+
textBindings,
|
|
1797
|
+
htmlBindings,
|
|
1798
|
+
attrBindings,
|
|
1799
|
+
eventBindings,
|
|
1800
|
+
slotBindings,
|
|
1801
|
+
componentBindings,
|
|
1802
|
+
ifBindings,
|
|
1803
|
+
buildFnStr
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
function indentBlock(code, spaces = 2) {
|
|
1807
|
+
const pad = " ".repeat(spaces);
|
|
1808
|
+
if (!code.trim()) {
|
|
1809
|
+
return "";
|
|
1810
|
+
}
|
|
1811
|
+
return code.split("\n").map((line) => `${pad}${line}`).join("\n");
|
|
1812
|
+
}
|
|
1813
|
+
function renderDescriptorLiteral(descriptor, indent = " ") {
|
|
1814
|
+
const slotFactoryDecls = descriptor.componentBindings.flatMap(
|
|
1815
|
+
(binding) => binding.slotFactories.map(
|
|
1816
|
+
(slot) => `${indent}const ${slot.varName} = () => (${renderDescriptorLiteral(slot.descriptor, indent + " ")});`
|
|
1817
|
+
)
|
|
1818
|
+
);
|
|
1819
|
+
const componentBindingsCode = descriptor.componentBindings.map((binding) => {
|
|
1820
|
+
const slotsObject = binding.slotFactories.length === 0 ? "{}" : `{ ${binding.slotFactories.map((slot) => `${JSON.stringify(slot.name)}: ${slot.varName}`).join(", ")} }`;
|
|
1821
|
+
return `${indent} { id: ${JSON.stringify(binding.id)}, getComponent: () => ${binding.component}, getProps: () => (${binding.propsExpr}), getSlots: () => (${slotsObject}) }`;
|
|
1822
|
+
}).join(",\n");
|
|
1823
|
+
const buildLines = descriptor.buildFnStr ? `${indent}build: ${descriptor.buildFnStr.replace(/\n /g, "\n" + indent)},` : "";
|
|
1824
|
+
const objectLiteral = `{
|
|
1825
|
+
${buildLines}
|
|
1826
|
+
${indent}textBindings: [
|
|
1827
|
+
${descriptor.textBindings.map((binding) => `${indent} { id: ${JSON.stringify(binding.id)}, get: () => (${binding.expr}) }`).join(",\n")}
|
|
1828
|
+
${indent}],
|
|
1829
|
+
${indent}htmlBindings: [
|
|
1830
|
+
${descriptor.htmlBindings.map((binding) => `${indent} { id: ${JSON.stringify(binding.id)}, get: () => (${binding.expr}) }`).join(",\n")}
|
|
1831
|
+
${indent}],
|
|
1832
|
+
${indent}attrBindings: [
|
|
1833
|
+
${descriptor.attrBindings.map((binding) => `${indent} { id: ${JSON.stringify(binding.id)}, attr: ${JSON.stringify(binding.attr)}, get: () => (${binding.expr}) }`).join(",\n")}
|
|
1834
|
+
${indent}],
|
|
1835
|
+
${indent}eventBindings: [
|
|
1836
|
+
${descriptor.eventBindings.map((binding) => `${indent} { id: ${JSON.stringify(binding.id)}, event: ${JSON.stringify(binding.event)}, get: () => (${binding.expr}) }`).join(",\n")}
|
|
1837
|
+
${indent}],
|
|
1838
|
+
${indent}slotBindings: [
|
|
1839
|
+
${descriptor.slotBindings.map((binding) => `${indent} { id: ${JSON.stringify(binding.id)}, name: ${JSON.stringify(binding.name)} }`).join(",\n")}
|
|
1840
|
+
${indent}],
|
|
1841
|
+
${indent}componentBindings: [
|
|
1842
|
+
${componentBindingsCode}
|
|
1843
|
+
${indent}],
|
|
1844
|
+
${indent}ifBindings: [
|
|
1845
|
+
${descriptor.ifBindings.map((binding) => {
|
|
1846
|
+
const trueFactory = `() => (${renderDescriptorLiteral(binding.trueBranch, indent + " ")})`;
|
|
1847
|
+
const falseFactory = binding.falseBranch ? `() => (${renderDescriptorLiteral(binding.falseBranch, indent + " ")})` : "undefined";
|
|
1848
|
+
return `${indent} { id: ${JSON.stringify(binding.id)}, get: () => (${binding.expr}), trueBranch: ${trueFactory}, falseBranch: ${falseFactory} }`;
|
|
1849
|
+
}).join(",\n")}
|
|
1850
|
+
${indent}]
|
|
1851
|
+
${indent}}`;
|
|
1852
|
+
if (slotFactoryDecls.length === 0) {
|
|
1853
|
+
return objectLiteral;
|
|
1854
|
+
}
|
|
1855
|
+
const outerIndent = indent.length >= 2 ? indent.slice(2) : "";
|
|
1856
|
+
return `(() => {
|
|
1857
|
+
${slotFactoryDecls.join("\n")}
|
|
1858
|
+
${indent}return ${objectLiteral};
|
|
1859
|
+
${outerIndent}})()`;
|
|
1860
|
+
}
|
|
1861
|
+
function buildComponentCode(parsed, options) {
|
|
1862
|
+
const scriptInfo = collectScriptInfo(parsed.script);
|
|
1863
|
+
const counters = {
|
|
1864
|
+
text: 0,
|
|
1865
|
+
attr: 0,
|
|
1866
|
+
event: 0,
|
|
1867
|
+
comp: 0,
|
|
1868
|
+
slot: 0,
|
|
1869
|
+
if: 0
|
|
1870
|
+
};
|
|
1871
|
+
const transformed = transformTemplate(
|
|
1872
|
+
parsed.template,
|
|
1873
|
+
scriptInfo.stateNames,
|
|
1874
|
+
scriptInfo.allSignalNames,
|
|
1875
|
+
scriptInfo.jsxFunctionNames,
|
|
1876
|
+
scriptInfo.componentNames,
|
|
1877
|
+
counters,
|
|
1878
|
+
options?.scopeId
|
|
1879
|
+
);
|
|
1880
|
+
const exportHead = options?.exportName ? `export const ${options.exportName} = __defineComponent((__props, __slots) => {` : `const __olovaComponent = __defineComponent((__props, __slots) => {`;
|
|
1881
|
+
const exportTail = options?.exportName ? `}, ${JSON.stringify(options?.hmrId)});` : `}, ${JSON.stringify(options?.hmrId)});
|
|
1882
|
+
export default __olovaComponent;`;
|
|
1883
|
+
const hmrCode = options?.dev && !options?.exportName && options?.hmrId ? `
|
|
1884
|
+
if (import.meta.hot) {
|
|
1885
|
+
import.meta.hot.accept((nextModule) => {
|
|
1886
|
+
if (nextModule?.default) {
|
|
1887
|
+
__replaceComponent(__olovaComponent, nextModule.default);
|
|
1888
|
+
}
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
` : "";
|
|
1892
|
+
const code = `
|
|
1893
|
+
${exportHead}
|
|
1894
|
+
const props = __props;
|
|
1895
|
+
const slots = __slots;
|
|
1896
|
+
const __olovaGlobalObj = globalThis;
|
|
1897
|
+
const __olovaJsxHandlers =
|
|
1898
|
+
__olovaGlobalObj.__olovaJsxHandlers ?? (__olovaGlobalObj.__olovaJsxHandlers = new Map());
|
|
1899
|
+
const __olovaNextJsxEventId = () => {
|
|
1900
|
+
__olovaGlobalObj.__olovaJsxEventId = (__olovaGlobalObj.__olovaJsxEventId ?? 0) + 1;
|
|
1901
|
+
return String(__olovaGlobalObj.__olovaJsxEventId);
|
|
1902
|
+
};
|
|
1903
|
+
if (typeof __olovaGlobalObj.__olovaDispatchJsxEvent !== 'function') {
|
|
1904
|
+
__olovaGlobalObj.__olovaDispatchJsxEvent = (event, element) => {
|
|
1905
|
+
const key = element?.getAttribute?.('data-o-jsx-' + event.type);
|
|
1906
|
+
if (!key) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
const handler = __olovaJsxHandlers.get(key);
|
|
1911
|
+
if (typeof handler === 'function') {
|
|
1912
|
+
handler(event);
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
const __olovaRegisterJsxEvent = (eventName, handler) => {
|
|
1917
|
+
if (typeof handler !== 'function') {
|
|
1918
|
+
return '';
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
const id = __olovaNextJsxEventId();
|
|
1922
|
+
__olovaJsxHandlers.set(id, handler);
|
|
1923
|
+
return ' data-o-jsx-' + eventName + '="' + id + '" on' + eventName + '="window.__olovaDispatchJsxEvent(event, this)"';
|
|
1924
|
+
};
|
|
1925
|
+
const __olovaToString = (value) => Array.isArray(value) ? value.map(__olovaToString).join('') : value == null ? '' : String(value);
|
|
1926
|
+
const __olovaDangerouslySetHtml = (value) => ({ __dangerousHtml: true, value: __olovaToString(value) });
|
|
1927
|
+
const __olovaNormalizeClassValue = (value) => {
|
|
1928
|
+
if (value == null || value === false) {
|
|
1929
|
+
return '';
|
|
1930
|
+
}
|
|
1931
|
+
if (Array.isArray(value)) {
|
|
1932
|
+
return value.map(__olovaNormalizeClassValue).filter(Boolean).join(' ');
|
|
1933
|
+
}
|
|
1934
|
+
if (typeof value === 'object') {
|
|
1935
|
+
return Object.entries(value)
|
|
1936
|
+
.filter(([, enabled]) => !!enabled)
|
|
1937
|
+
.map(([key]) => key)
|
|
1938
|
+
.join(' ');
|
|
1939
|
+
}
|
|
1940
|
+
return String(value);
|
|
1941
|
+
};
|
|
1942
|
+
const __olovaEscape = (value) =>
|
|
1943
|
+
__olovaToString(value)
|
|
1944
|
+
.replace(/&/g, '&')
|
|
1945
|
+
.replace(/</g, '<')
|
|
1946
|
+
.replace(/>/g, '>')
|
|
1947
|
+
.replace(/"/g, '"')
|
|
1948
|
+
.replace(/'/g, ''');
|
|
1949
|
+
const __olovaSpreadAttrs = (input) => {
|
|
1950
|
+
if (input == null || input === false) {
|
|
1951
|
+
return '';
|
|
1952
|
+
}
|
|
1953
|
+
if (Array.isArray(input)) {
|
|
1954
|
+
return input.map(__olovaSpreadAttrs).join('');
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof input !== 'object') {
|
|
1957
|
+
return '';
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
return Object.entries(input).reduce((acc, [key, value]) => {
|
|
1961
|
+
if (value == null || value === false) {
|
|
1962
|
+
return acc;
|
|
1963
|
+
}
|
|
1964
|
+
const attrKey = key === 'className' ? 'class' : key;
|
|
1965
|
+
const normalizedValue = attrKey === 'class'
|
|
1966
|
+
? __olovaNormalizeClassValue(value)
|
|
1967
|
+
: value;
|
|
1968
|
+
if (attrKey === 'key') {
|
|
1969
|
+
return acc;
|
|
1970
|
+
}
|
|
1971
|
+
if (normalizedValue == null || normalizedValue === false || normalizedValue === '') {
|
|
1972
|
+
return acc;
|
|
1973
|
+
}
|
|
1974
|
+
if (normalizedValue === true) {
|
|
1975
|
+
return acc + ' ' + attrKey;
|
|
1976
|
+
}
|
|
1977
|
+
return acc + ' ' + attrKey + '="' + __olovaEscape(normalizedValue) + '"';
|
|
1978
|
+
}, '');
|
|
1979
|
+
};
|
|
1980
|
+
const __olovaFor = (each, render) => {
|
|
1981
|
+
if (!Array.isArray(each) || typeof render !== 'function') {
|
|
1982
|
+
return '';
|
|
1983
|
+
}
|
|
1984
|
+
return each.map((item, index) => render(item, index));
|
|
1985
|
+
};
|
|
1986
|
+
const __olovaShow = (when, render, fallback = '') => {
|
|
1987
|
+
if (!when) {
|
|
1988
|
+
return fallback;
|
|
1989
|
+
}
|
|
1990
|
+
return typeof render === 'function' ? render(when) : render;
|
|
1991
|
+
};
|
|
1992
|
+
${indentBlock(scriptInfo.scriptBodyCode, 2)}
|
|
1993
|
+
|
|
1994
|
+
return ${renderDescriptorLiteral(transformed, " ")};
|
|
1995
|
+
${exportTail}
|
|
1996
|
+
${hmrCode}
|
|
1997
|
+
`;
|
|
1998
|
+
return { importsCode: scriptInfo.importsCode, code };
|
|
1999
|
+
}
|
|
2000
|
+
function hashScopeSeed(input) {
|
|
2001
|
+
let hash = 2166136261;
|
|
2002
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
2003
|
+
hash ^= input.charCodeAt(index);
|
|
2004
|
+
hash = Math.imul(hash, 16777619);
|
|
2005
|
+
}
|
|
2006
|
+
return Math.abs(hash >>> 0).toString(36);
|
|
2007
|
+
}
|
|
2008
|
+
function createScopeAttr(scopeId) {
|
|
2009
|
+
return scopeId ? `data-o-scope-${scopeId}` : null;
|
|
2010
|
+
}
|
|
2011
|
+
function scopeSelectorList(selectorList, scopeAttr) {
|
|
2012
|
+
const applyScopeToSegment = (segment) => {
|
|
2013
|
+
const trimmed = segment.trim();
|
|
2014
|
+
if (!trimmed) {
|
|
2015
|
+
return trimmed;
|
|
2016
|
+
}
|
|
2017
|
+
if (trimmed.includes(scopeAttr)) {
|
|
2018
|
+
return trimmed;
|
|
2019
|
+
}
|
|
2020
|
+
const globalPattern = /:global\(([^()]+)\)/g;
|
|
2021
|
+
const localCandidate = trimmed.replace(globalPattern, "").trim();
|
|
2022
|
+
const normalized = trimmed.replace(globalPattern, "$1");
|
|
2023
|
+
if (!localCandidate) {
|
|
2024
|
+
return normalized;
|
|
2025
|
+
}
|
|
2026
|
+
if (normalized === ":root") {
|
|
2027
|
+
return `[${scopeAttr}]`;
|
|
2028
|
+
}
|
|
2029
|
+
const pseudoIndex = normalized.search(/(?<!:):(?!:)/);
|
|
2030
|
+
if (pseudoIndex === -1) {
|
|
2031
|
+
return `${normalized}[${scopeAttr}]`;
|
|
2032
|
+
}
|
|
2033
|
+
return `${normalized.slice(0, pseudoIndex)}[${scopeAttr}]${normalized.slice(pseudoIndex)}`;
|
|
2034
|
+
};
|
|
2035
|
+
return selectorList.split(",").map((selector) => selector.trim()).filter(Boolean).map((selector) => {
|
|
2036
|
+
if (selector.startsWith("@") || selector.includes(scopeAttr)) {
|
|
2037
|
+
return selector;
|
|
2038
|
+
}
|
|
2039
|
+
return selector.split(/(\s+|[>+~])/).map((part) => {
|
|
2040
|
+
if (!part || /^(\s+|[>+~])$/.test(part)) {
|
|
2041
|
+
return part;
|
|
2042
|
+
}
|
|
2043
|
+
return applyScopeToSegment(part);
|
|
2044
|
+
}).join("");
|
|
2045
|
+
}).join(", ");
|
|
2046
|
+
}
|
|
2047
|
+
function scopeCss(css, scopeAttr) {
|
|
2048
|
+
let output = "";
|
|
2049
|
+
let index = 0;
|
|
2050
|
+
while (index < css.length) {
|
|
2051
|
+
const open = css.indexOf("{", index);
|
|
2052
|
+
if (open < 0) {
|
|
2053
|
+
output += css.slice(index);
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
2056
|
+
const selector = css.slice(index, open).trim();
|
|
2057
|
+
let depth = 1;
|
|
2058
|
+
let cursor = open + 1;
|
|
2059
|
+
while (cursor < css.length && depth > 0) {
|
|
2060
|
+
if (css[cursor] === "{") {
|
|
2061
|
+
depth += 1;
|
|
2062
|
+
} else if (css[cursor] === "}") {
|
|
2063
|
+
depth -= 1;
|
|
2064
|
+
}
|
|
2065
|
+
cursor += 1;
|
|
2066
|
+
}
|
|
2067
|
+
const body = css.slice(open + 1, cursor - 1);
|
|
2068
|
+
if (/^@(?:media|supports|container|layer)\b/i.test(selector)) {
|
|
2069
|
+
output += `${selector} {${scopeCss(body, scopeAttr)}}`;
|
|
2070
|
+
} else if (/^@(?:keyframes|-webkit-keyframes)\b/i.test(selector)) {
|
|
2071
|
+
output += `${selector} {${body}}`;
|
|
2072
|
+
} else {
|
|
2073
|
+
output += `${scopeSelectorList(selector, scopeAttr)} {${body}}`;
|
|
2074
|
+
}
|
|
2075
|
+
index = cursor;
|
|
2076
|
+
}
|
|
2077
|
+
return output;
|
|
2078
|
+
}
|
|
2079
|
+
function collectCss(blocks, scopeId) {
|
|
2080
|
+
const scopeAttr = createScopeAttr(scopeId);
|
|
2081
|
+
return blocks.map((block) => {
|
|
2082
|
+
const content = block.content.trim();
|
|
2083
|
+
if (!content) {
|
|
2084
|
+
return "";
|
|
2085
|
+
}
|
|
2086
|
+
if (!("global" in block.attrs) && scopeAttr) {
|
|
2087
|
+
return scopeCss(content, scopeAttr);
|
|
2088
|
+
}
|
|
2089
|
+
return content;
|
|
2090
|
+
}).filter(Boolean).join("\n\n");
|
|
2091
|
+
}
|
|
2092
|
+
function compileOlovaScriptModule(source, options) {
|
|
2093
|
+
try {
|
|
2094
|
+
const scriptInfo = collectScriptInfo(source);
|
|
2095
|
+
return transpileTypeScript(
|
|
2096
|
+
[scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
|
|
2097
|
+
options?.filename ?? "module.ts"
|
|
2098
|
+
);
|
|
2099
|
+
} catch (error) {
|
|
2100
|
+
return normalizeCompileError(error, source, options?.filename ?? "module.ts");
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
function compileOlova(source, options) {
|
|
2104
|
+
try {
|
|
2105
|
+
const parsed = parseOlovaFile(source);
|
|
2106
|
+
if (!parsed.template.trim() && isModuleOnlyScript(parsed.script)) {
|
|
2107
|
+
return {
|
|
2108
|
+
...compileModuleScript(parsed.script, options?.filename ?? "module.olova.ts"),
|
|
2109
|
+
css: collectCss(parsed.styles)
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
const scopeId = parsed.styles.some((style) => !("global" in style.attrs)) ? hashScopeSeed(`${options?.filename ?? "component.olova"}:${options?.exportName ?? "default"}`) : void 0;
|
|
2113
|
+
const compiled = buildComponentCode(parsed, {
|
|
2114
|
+
scopeId,
|
|
2115
|
+
hmrId: options?.filename ?? "component.olova",
|
|
2116
|
+
dev: options?.dev
|
|
2117
|
+
});
|
|
2118
|
+
const runtimeImport = options?.dev ? `import { defineComponent as __defineComponent, replaceComponent as __replaceComponent } from 'olova/runtime';` : `import { defineComponent as __defineComponent } from 'olova/runtime';`;
|
|
2119
|
+
const result = transpileTypeScript(`
|
|
2120
|
+
${runtimeImport}
|
|
2121
|
+
${compiled.importsCode}
|
|
2122
|
+
${compiled.code}
|
|
2123
|
+
`, options?.filename ?? "component.olova.ts");
|
|
2124
|
+
return {
|
|
2125
|
+
...result,
|
|
2126
|
+
css: collectCss(parsed.styles, scopeId)
|
|
2127
|
+
};
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
return normalizeCompileError(
|
|
2130
|
+
error,
|
|
2131
|
+
source,
|
|
2132
|
+
options?.filename ?? "component.olova"
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
export { compileOlova, compileOlovaScriptModule, generateBuildFunction, parseHTML };
|
|
2138
|
+
//# sourceMappingURL=compiler.js.map
|
|
2139
|
+
//# sourceMappingURL=compiler.js.map
|