olova 2.0.67 → 2.0.70
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 -5
- package/README.md +42 -42
- package/dist/compiler.d.ts +76 -1
- package/dist/compiler.js +566 -420
- package/dist/compiler.js.map +1 -1
- package/dist/core.d.ts +2 -2
- package/dist/core.js +400 -38
- package/dist/core.js.map +1 -1
- package/dist/global.d.ts +1 -1
- package/dist/global.js +54 -8
- package/dist/global.js.map +1 -1
- package/dist/index.js +534 -424
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +8 -3
- package/dist/runtime.js +512 -32
- package/dist/runtime.js.map +1 -1
- package/dist/{signals-core-BdfWh1Yt.d.ts → signals-core-BWZ5zXK5.d.ts} +6 -5
- package/dist/vite.js +534 -424
- package/dist/vite.js.map +1 -1
- package/package.json +86 -83
package/dist/index.js
CHANGED
|
@@ -7,8 +7,161 @@ import generatorModule from '@babel/generator';
|
|
|
7
7
|
import traverseModule from '@babel/traverse';
|
|
8
8
|
import * as t from '@babel/types';
|
|
9
9
|
import ts from 'typescript';
|
|
10
|
+
import * as csstree from 'css-tree';
|
|
11
|
+
import { parseDocument } from 'htmlparser2';
|
|
10
12
|
|
|
11
13
|
// vite/olova-plugin.ts
|
|
14
|
+
function createScopeAttr(scopeId) {
|
|
15
|
+
return scopeId ? `data-o-scope-${scopeId}` : null;
|
|
16
|
+
}
|
|
17
|
+
function scopeSelectorList(selectorList, scopeAttr) {
|
|
18
|
+
const applyScopeToSegment = (segment) => {
|
|
19
|
+
const trimmed = segment.trim();
|
|
20
|
+
if (!trimmed) {
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
if (trimmed.includes(scopeAttr)) {
|
|
24
|
+
return trimmed;
|
|
25
|
+
}
|
|
26
|
+
const globalPattern = /:global\(([^()]+)\)/g;
|
|
27
|
+
const localCandidate = trimmed.replace(globalPattern, "").trim();
|
|
28
|
+
const normalized = trimmed.replace(globalPattern, "$1");
|
|
29
|
+
if (!localCandidate) {
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
if (normalized === ":root") {
|
|
33
|
+
return `[${scopeAttr}]`;
|
|
34
|
+
}
|
|
35
|
+
const pseudoIndex = normalized.search(/(?<!:):(?!:)/);
|
|
36
|
+
if (pseudoIndex === -1) {
|
|
37
|
+
return `${normalized}[${scopeAttr}]`;
|
|
38
|
+
}
|
|
39
|
+
return `${normalized.slice(0, pseudoIndex)}[${scopeAttr}]${normalized.slice(pseudoIndex)}`;
|
|
40
|
+
};
|
|
41
|
+
return selectorList.split(",").map((selector) => selector.trim()).filter(Boolean).map((selector) => {
|
|
42
|
+
if (selector.startsWith("@") || selector.includes(scopeAttr)) {
|
|
43
|
+
return selector;
|
|
44
|
+
}
|
|
45
|
+
return selector.split(/(\s+|[>+~])/).map((part) => {
|
|
46
|
+
if (!part || /^(\s+|[>+~])$/.test(part)) {
|
|
47
|
+
return part;
|
|
48
|
+
}
|
|
49
|
+
return applyScopeToSegment(part);
|
|
50
|
+
}).join("");
|
|
51
|
+
}).join(", ");
|
|
52
|
+
}
|
|
53
|
+
function scopeCss(css, scopeAttr) {
|
|
54
|
+
const ast = csstree.parse(css, {
|
|
55
|
+
context: "stylesheet",
|
|
56
|
+
positions: false
|
|
57
|
+
});
|
|
58
|
+
csstree.walk(ast, {
|
|
59
|
+
visit: "Rule",
|
|
60
|
+
enter(node) {
|
|
61
|
+
const rule = node;
|
|
62
|
+
if (!rule.prelude || rule.prelude.type !== "SelectorList") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
rule.prelude = csstree.parse(
|
|
66
|
+
scopeSelectorList(csstree.generate(rule.prelude), scopeAttr),
|
|
67
|
+
{
|
|
68
|
+
context: "selectorList",
|
|
69
|
+
positions: false
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return csstree.generate(ast);
|
|
75
|
+
}
|
|
76
|
+
function hashScopeSeed(input) {
|
|
77
|
+
let hash = 2166136261;
|
|
78
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
79
|
+
hash ^= input.charCodeAt(index);
|
|
80
|
+
hash = Math.imul(hash, 16777619);
|
|
81
|
+
}
|
|
82
|
+
return Math.abs(hash >>> 0).toString(36);
|
|
83
|
+
}
|
|
84
|
+
function collectCss(blocks, scopeId) {
|
|
85
|
+
const scopeAttr = createScopeAttr(scopeId);
|
|
86
|
+
return blocks.map((block) => {
|
|
87
|
+
const content = block.content.trim();
|
|
88
|
+
if (!content) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
if (!("global" in block.attrs) && scopeAttr) {
|
|
92
|
+
return scopeCss(content, scopeAttr);
|
|
93
|
+
}
|
|
94
|
+
return content;
|
|
95
|
+
}).filter(Boolean).join("\n\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// compiler/diagnostics.ts
|
|
99
|
+
function indexToLocation(source, index) {
|
|
100
|
+
const safeIndex = Math.max(0, Math.min(index, source.length));
|
|
101
|
+
const lines = source.slice(0, safeIndex).split("\n");
|
|
102
|
+
return {
|
|
103
|
+
line: lines.length,
|
|
104
|
+
column: lines[lines.length - 1]?.length ?? 0,
|
|
105
|
+
index: safeIndex
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function createCodeFrame(source, line, column, contextLines = 2) {
|
|
109
|
+
const lines = source.split("\n");
|
|
110
|
+
const start = Math.max(0, line - 1 - contextLines);
|
|
111
|
+
const end = Math.min(lines.length, line + contextLines);
|
|
112
|
+
return lines.slice(start, end).map((content, offset) => {
|
|
113
|
+
const lineNumber = start + offset + 1;
|
|
114
|
+
const prefix = `${String(lineNumber).padStart(4, " ")} | `;
|
|
115
|
+
if (lineNumber !== line) {
|
|
116
|
+
return `${prefix}${content}`;
|
|
117
|
+
}
|
|
118
|
+
return `${prefix}${content}
|
|
119
|
+
| ${" ".repeat(Math.max(column, 0))}^`;
|
|
120
|
+
}).join("\n");
|
|
121
|
+
}
|
|
122
|
+
function createCompileError(message, source, index, options) {
|
|
123
|
+
const loc = indexToLocation(source, index);
|
|
124
|
+
const error = new Error(message);
|
|
125
|
+
error.loc = { start: loc };
|
|
126
|
+
error.frame = createCodeFrame(source, loc.line, loc.column);
|
|
127
|
+
error.phase = options?.phase;
|
|
128
|
+
error.hint = options?.hint;
|
|
129
|
+
error.diagnostic = {
|
|
130
|
+
message,
|
|
131
|
+
filename: options?.filename,
|
|
132
|
+
phase: options?.phase,
|
|
133
|
+
line: loc.line,
|
|
134
|
+
column: loc.column + 1,
|
|
135
|
+
index: loc.index,
|
|
136
|
+
frame: error.frame,
|
|
137
|
+
hint: options?.hint
|
|
138
|
+
};
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
function normalizeCompileError(error, source, filename = "component.olova") {
|
|
142
|
+
const compileError = error instanceof Error ? error : new Error(String(error));
|
|
143
|
+
const start = compileError.loc?.start ?? compileError.loc;
|
|
144
|
+
const line = start?.line;
|
|
145
|
+
const column = start?.column;
|
|
146
|
+
const phasePrefix = compileError.phase ? ` [${compileError.phase}]` : "";
|
|
147
|
+
const locationSuffix = line === void 0 || column === void 0 ? "" : ` (${filename}:${line}:${column + 1})`;
|
|
148
|
+
const normalizedMessage = compileError.message.replace(/^\[olova\]\s*/, "");
|
|
149
|
+
compileError.message = `[olova]${phasePrefix} ${normalizedMessage}${locationSuffix}`;
|
|
150
|
+
if (line !== void 0 && column !== void 0 && !compileError.frame) {
|
|
151
|
+
compileError.frame = createCodeFrame(source, line, column);
|
|
152
|
+
}
|
|
153
|
+
compileError.diagnostic = {
|
|
154
|
+
message: normalizedMessage,
|
|
155
|
+
filename,
|
|
156
|
+
phase: compileError.phase,
|
|
157
|
+
line,
|
|
158
|
+
column: column === void 0 ? void 0 : column + 1,
|
|
159
|
+
index: start?.index,
|
|
160
|
+
frame: compileError.frame,
|
|
161
|
+
hint: compileError.hint
|
|
162
|
+
};
|
|
163
|
+
throw compileError;
|
|
164
|
+
}
|
|
12
165
|
|
|
13
166
|
// compiler/parse.ts
|
|
14
167
|
function isWhitespace(char) {
|
|
@@ -181,17 +334,17 @@ function removeRanges(source, ranges) {
|
|
|
181
334
|
return output.trim();
|
|
182
335
|
}
|
|
183
336
|
function parseOlovaFile(source) {
|
|
184
|
-
const
|
|
337
|
+
const scriptBlocks = collectTopLevelBlocks(source, "script");
|
|
185
338
|
const styleBlocks = collectTopLevelBlocks(source, "style");
|
|
186
339
|
const ranges = styleBlocks.map((block) => ({
|
|
187
340
|
start: block.fullStart,
|
|
188
341
|
end: block.closeEnd
|
|
189
342
|
}));
|
|
190
|
-
|
|
343
|
+
for (const scriptBlock of scriptBlocks) {
|
|
191
344
|
ranges.push({ start: scriptBlock.fullStart, end: scriptBlock.closeEnd });
|
|
192
345
|
}
|
|
193
346
|
return {
|
|
194
|
-
script:
|
|
347
|
+
script: scriptBlocks.map((block) => block.inner.trim()).filter(Boolean).join("\n\n"),
|
|
195
348
|
template: removeRanges(source, ranges),
|
|
196
349
|
styles: styleBlocks.map((block) => ({
|
|
197
350
|
content: block.inner.trim(),
|
|
@@ -199,55 +352,30 @@ function parseOlovaFile(source) {
|
|
|
199
352
|
}))
|
|
200
353
|
};
|
|
201
354
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
if (char === ">") {
|
|
222
|
-
return index;
|
|
223
|
-
}
|
|
224
|
-
index += 1;
|
|
225
|
-
}
|
|
226
|
-
return -1;
|
|
227
|
-
}
|
|
228
|
-
function readQuotedValue(source, start, quote) {
|
|
229
|
-
let index = start + 1;
|
|
230
|
-
let value = "";
|
|
231
|
-
while (index < source.length) {
|
|
232
|
-
const char = source[index];
|
|
233
|
-
if (char === "\\" && index + 1 < source.length) {
|
|
234
|
-
value += source.slice(index, index + 2);
|
|
235
|
-
index += 2;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (char === quote) {
|
|
239
|
-
return { value, end: index + 1 };
|
|
240
|
-
}
|
|
241
|
-
value += char;
|
|
242
|
-
index += 1;
|
|
243
|
-
}
|
|
244
|
-
return { value, end: source.length };
|
|
355
|
+
var VOID_TAGS = /* @__PURE__ */ new Set([
|
|
356
|
+
"area",
|
|
357
|
+
"base",
|
|
358
|
+
"br",
|
|
359
|
+
"col",
|
|
360
|
+
"embed",
|
|
361
|
+
"hr",
|
|
362
|
+
"img",
|
|
363
|
+
"input",
|
|
364
|
+
"link",
|
|
365
|
+
"meta",
|
|
366
|
+
"param",
|
|
367
|
+
"source",
|
|
368
|
+
"track",
|
|
369
|
+
"wbr"
|
|
370
|
+
]);
|
|
371
|
+
function normalizeNewlines(value) {
|
|
372
|
+
return value.replace(/\r\n?/g, "\n");
|
|
245
373
|
}
|
|
246
|
-
function
|
|
374
|
+
function readBracedValue(source, start) {
|
|
247
375
|
let index = start + 1;
|
|
248
376
|
let depth = 1;
|
|
249
|
-
let value = "{";
|
|
250
377
|
let quote = null;
|
|
378
|
+
let value = "{";
|
|
251
379
|
while (index < source.length && depth > 0) {
|
|
252
380
|
const char = source[index];
|
|
253
381
|
const prev = source[index - 1];
|
|
@@ -273,158 +401,106 @@ function readBraceValue(source, start) {
|
|
|
273
401
|
}
|
|
274
402
|
return { value, end: index };
|
|
275
403
|
}
|
|
276
|
-
function
|
|
277
|
-
const
|
|
404
|
+
function protectOlovaAttrExpressions(html) {
|
|
405
|
+
const placeholders = /* @__PURE__ */ new Map();
|
|
406
|
+
let output = "";
|
|
278
407
|
let index = 0;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (index
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
let nameEnd = index;
|
|
287
|
-
while (nameEnd < source.length && !isWhitespace2(source[nameEnd]) && source[nameEnd] !== "=") {
|
|
288
|
-
nameEnd += 1;
|
|
289
|
-
}
|
|
290
|
-
const name = source.slice(index, nameEnd).trim();
|
|
291
|
-
index = nameEnd;
|
|
292
|
-
if (!name) {
|
|
293
|
-
index += 1;
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
while (index < source.length && isWhitespace2(source[index])) {
|
|
408
|
+
let count = 0;
|
|
409
|
+
while (index < html.length) {
|
|
410
|
+
const char = html[index];
|
|
411
|
+
output += char;
|
|
412
|
+
if (char !== "=" || html[index + 1] !== "{") {
|
|
297
413
|
index += 1;
|
|
298
|
-
}
|
|
299
|
-
if (source[index] !== "=") {
|
|
300
|
-
attrs[name] = true;
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
index += 1;
|
|
304
|
-
while (index < source.length && isWhitespace2(source[index])) {
|
|
305
|
-
index += 1;
|
|
306
|
-
}
|
|
307
|
-
if (index >= source.length) {
|
|
308
|
-
attrs[name] = true;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
const start = index;
|
|
312
|
-
const char = source[index];
|
|
313
|
-
if (char === '"' || char === "'") {
|
|
314
|
-
const quoted = readQuotedValue(source, index, char);
|
|
315
|
-
attrs[name] = quoted.value;
|
|
316
|
-
index = quoted.end;
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (char === "{") {
|
|
320
|
-
const braced = readBraceValue(source, index);
|
|
321
|
-
attrs[name] = braced.value;
|
|
322
|
-
index = braced.end;
|
|
323
414
|
continue;
|
|
324
415
|
}
|
|
325
|
-
|
|
326
|
-
|
|
416
|
+
const braced = readBracedValue(html, index + 1);
|
|
417
|
+
const key = `__OLOVA_ATTR_EXPR_${count++}__`;
|
|
418
|
+
placeholders.set(key, braced.value);
|
|
419
|
+
output += `"${key}"`;
|
|
420
|
+
index = braced.end;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
return { html: output, placeholders };
|
|
424
|
+
}
|
|
425
|
+
function normalizeAttrs(attribs) {
|
|
426
|
+
const output = {};
|
|
427
|
+
for (const [name, value] of Object.entries(attribs)) {
|
|
428
|
+
output[name] = value === "" ? true : value;
|
|
429
|
+
}
|
|
430
|
+
return output;
|
|
431
|
+
}
|
|
432
|
+
function isSelfClosingElement(node) {
|
|
433
|
+
return VOID_TAGS.has(node.name.toLowerCase());
|
|
434
|
+
}
|
|
435
|
+
function toAstNode(node) {
|
|
436
|
+
if (node.type === "text") {
|
|
437
|
+
return {
|
|
438
|
+
type: "text",
|
|
439
|
+
content: normalizeNewlines(node.data)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (node.type === "comment") {
|
|
443
|
+
return {
|
|
444
|
+
type: "comment",
|
|
445
|
+
content: normalizeNewlines(node.data)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (node.type === "tag" || node.type === "script" || node.type === "style") {
|
|
449
|
+
const element = node;
|
|
450
|
+
return {
|
|
451
|
+
type: "element",
|
|
452
|
+
tag: element.name,
|
|
453
|
+
attrs: normalizeAttrs(element.attribs ?? {}),
|
|
454
|
+
children: toAstNodes(element.children ?? []),
|
|
455
|
+
isSelfClosing: isSelfClosingElement(element)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
function toAstNodes(nodes) {
|
|
461
|
+
const output = [];
|
|
462
|
+
for (const node of nodes) {
|
|
463
|
+
const normalized = toAstNode(node);
|
|
464
|
+
if (normalized) {
|
|
465
|
+
output.push(normalized);
|
|
327
466
|
}
|
|
328
|
-
attrs[name] = source.slice(start, index);
|
|
329
467
|
}
|
|
330
|
-
return
|
|
468
|
+
return output;
|
|
331
469
|
}
|
|
332
470
|
function parseHTML(html) {
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
stack[stack.length - 1].children.push({
|
|
344
|
-
type: "text",
|
|
345
|
-
content: html.slice(textStart)
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
if (tagStart > textStart) {
|
|
351
|
-
stack[stack.length - 1].children.push({
|
|
352
|
-
type: "text",
|
|
353
|
-
content: html.slice(textStart, tagStart)
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
i = tagStart;
|
|
357
|
-
if (html.startsWith("<!--", i)) {
|
|
358
|
-
const commentEnd = html.indexOf("-->", i + 4);
|
|
359
|
-
if (commentEnd === -1) {
|
|
360
|
-
stack[stack.length - 1].children.push({
|
|
361
|
-
type: "comment",
|
|
362
|
-
content: html.slice(i + 4)
|
|
363
|
-
});
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
stack[stack.length - 1].children.push({
|
|
367
|
-
type: "comment",
|
|
368
|
-
content: html.slice(i + 4, commentEnd)
|
|
369
|
-
});
|
|
370
|
-
i = commentEnd + 3;
|
|
371
|
-
continue;
|
|
471
|
+
const protectedHtml = protectOlovaAttrExpressions(html);
|
|
472
|
+
const document = parseDocument(protectedHtml.html, {
|
|
473
|
+
decodeEntities: false,
|
|
474
|
+
recognizeSelfClosing: true,
|
|
475
|
+
lowerCaseAttributeNames: false,
|
|
476
|
+
lowerCaseTags: false
|
|
477
|
+
});
|
|
478
|
+
const rootNodes = document.children.filter((node) => {
|
|
479
|
+
if (node.type === "directive" || node.type === "cdata") {
|
|
480
|
+
return false;
|
|
372
481
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
482
|
+
return true;
|
|
483
|
+
});
|
|
484
|
+
const ast = toAstNodes(rootNodes);
|
|
485
|
+
const restorePlaceholders = (nodes) => {
|
|
486
|
+
for (const node of nodes) {
|
|
487
|
+
if (node.type !== "element") {
|
|
377
488
|
continue;
|
|
378
489
|
}
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
490
|
+
for (const [name, value] of Object.entries(node.attrs)) {
|
|
491
|
+
if (typeof value !== "string") {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const restored = protectedHtml.placeholders.get(value);
|
|
495
|
+
if (restored) {
|
|
496
|
+
node.attrs[name] = restored;
|
|
497
|
+
}
|
|
386
498
|
}
|
|
387
|
-
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
const tagEnd = findTagEnd2(html, i + 1);
|
|
391
|
-
if (tagEnd === -1) {
|
|
392
|
-
stack[stack.length - 1].children.push({ type: "text", content: "<" });
|
|
393
|
-
i++;
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
const rawTag = html.slice(i + 1, tagEnd).trim();
|
|
397
|
-
let tagNameEnd = 0;
|
|
398
|
-
while (tagNameEnd < rawTag.length && !isWhitespace2(rawTag[tagNameEnd]) && rawTag[tagNameEnd] !== "/") {
|
|
399
|
-
tagNameEnd += 1;
|
|
400
|
-
}
|
|
401
|
-
const tag = rawTag.slice(0, tagNameEnd);
|
|
402
|
-
if (!tag) {
|
|
403
|
-
stack[stack.length - 1].children.push({ type: "text", content: "<" });
|
|
404
|
-
i++;
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
const attrsStr = rawTag.slice(tagNameEnd).replace(/\/\s*$/, "").trim();
|
|
408
|
-
const selfClose = /\/\s*$/.test(rawTag);
|
|
409
|
-
const attrs = parseAttributes2(attrsStr);
|
|
410
|
-
const isNativeVoidTag = tag === tag.toLowerCase() && /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/.test(
|
|
411
|
-
tag
|
|
412
|
-
);
|
|
413
|
-
const isSelfClosing = !!selfClose || isNativeVoidTag;
|
|
414
|
-
const element = {
|
|
415
|
-
type: "element",
|
|
416
|
-
tag,
|
|
417
|
-
attrs,
|
|
418
|
-
children: [],
|
|
419
|
-
isSelfClosing
|
|
420
|
-
};
|
|
421
|
-
stack[stack.length - 1].children.push(element);
|
|
422
|
-
if (!isSelfClosing) {
|
|
423
|
-
stack.push({ node: element, children: element.children });
|
|
499
|
+
restorePlaceholders(node.children);
|
|
424
500
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return
|
|
501
|
+
};
|
|
502
|
+
restorePlaceholders(ast);
|
|
503
|
+
return ast;
|
|
428
504
|
}
|
|
429
505
|
|
|
430
506
|
// compiler/dom-generator.ts
|
|
@@ -482,7 +558,7 @@ function processNode(node, parentVar, stateNames, transformExpr, counters, build
|
|
|
482
558
|
const varName = `_el${buildCtx.localCount++}`;
|
|
483
559
|
const useSvgNamespace = isSvgContext(tag, inSvg);
|
|
484
560
|
buildCtx.statements.push(
|
|
485
|
-
useSvgNamespace ? `const ${varName} = document.createElementNS(${JSON.stringify(SVG_NAMESPACE)},
|
|
561
|
+
useSvgNamespace ? `const ${varName} = document.createElementNS(${JSON.stringify(SVG_NAMESPACE)}, ${JSON.stringify(tag)});` : `const ${varName} = document.createElement(${JSON.stringify(tag)});`
|
|
486
562
|
);
|
|
487
563
|
for (const [attr, val] of Object.entries(node.attrs)) {
|
|
488
564
|
if (attr.startsWith("data-o-on-")) {
|
|
@@ -496,10 +572,12 @@ function processNode(node, parentVar, stateNames, transformExpr, counters, build
|
|
|
496
572
|
}
|
|
497
573
|
} else {
|
|
498
574
|
if (val === true) {
|
|
499
|
-
buildCtx.statements.push(
|
|
575
|
+
buildCtx.statements.push(
|
|
576
|
+
`${varName}.setAttribute(${JSON.stringify(attr)}, "");`
|
|
577
|
+
);
|
|
500
578
|
} else {
|
|
501
579
|
buildCtx.statements.push(
|
|
502
|
-
`${varName}.setAttribute(
|
|
580
|
+
`${varName}.setAttribute(${JSON.stringify(attr)}, ${JSON.stringify(val)});`
|
|
503
581
|
);
|
|
504
582
|
}
|
|
505
583
|
}
|
|
@@ -545,12 +623,14 @@ function generateBuildFunction(html, stateNames, transformExpr, counters, compon
|
|
|
545
623
|
}
|
|
546
624
|
|
|
547
625
|
// compiler/compile.ts
|
|
548
|
-
var
|
|
626
|
+
var generate2 = generatorModule.default ?? generatorModule;
|
|
549
627
|
var traverse = traverseModule.default ?? traverseModule;
|
|
550
628
|
var CORE_KNOWN_IMPORTS = /* @__PURE__ */ new Set([
|
|
551
629
|
"Signal",
|
|
552
630
|
"computed",
|
|
553
631
|
"createApp",
|
|
632
|
+
"createErrorBoundary",
|
|
633
|
+
"dangerouslySetHtml",
|
|
554
634
|
"defineComponent",
|
|
555
635
|
"derived",
|
|
556
636
|
"effect",
|
|
@@ -558,53 +638,20 @@ var CORE_KNOWN_IMPORTS = /* @__PURE__ */ new Set([
|
|
|
558
638
|
"global",
|
|
559
639
|
"hasContext",
|
|
560
640
|
"mount",
|
|
641
|
+
"onCleanup",
|
|
642
|
+
"onMount",
|
|
643
|
+
"replaceComponent",
|
|
561
644
|
"setContext",
|
|
562
|
-
"state"
|
|
645
|
+
"state",
|
|
646
|
+
"Suspense"
|
|
563
647
|
]);
|
|
564
648
|
function escapeTemplate(source) {
|
|
565
649
|
return source.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
|
566
650
|
}
|
|
567
|
-
function
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
return {
|
|
571
|
-
line: lines.length,
|
|
572
|
-
column: lines[lines.length - 1]?.length ?? 0,
|
|
573
|
-
index: safeIndex
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
function createCodeFrame(source, line, column, contextLines = 2) {
|
|
577
|
-
const lines = source.split("\n");
|
|
578
|
-
const start = Math.max(0, line - 1 - contextLines);
|
|
579
|
-
const end = Math.min(lines.length, line + contextLines);
|
|
580
|
-
return lines.slice(start, end).map((content, offset) => {
|
|
581
|
-
const lineNumber = start + offset + 1;
|
|
582
|
-
const prefix = `${String(lineNumber).padStart(4, " ")} | `;
|
|
583
|
-
if (lineNumber !== line) {
|
|
584
|
-
return `${prefix}${content}`;
|
|
585
|
-
}
|
|
586
|
-
return `${prefix}${content}
|
|
587
|
-
| ${" ".repeat(Math.max(column, 0))}^`;
|
|
588
|
-
}).join("\n");
|
|
589
|
-
}
|
|
590
|
-
function createCompileError(message, source, index) {
|
|
591
|
-
const loc = indexToLocation(source, index);
|
|
592
|
-
const error = new Error(message);
|
|
593
|
-
error.loc = { start: loc };
|
|
594
|
-
error.frame = createCodeFrame(source, loc.line, loc.column);
|
|
595
|
-
return error;
|
|
596
|
-
}
|
|
597
|
-
function normalizeCompileError(error, source, filename = "component.olova") {
|
|
598
|
-
const compileError = error instanceof Error ? error : new Error(String(error));
|
|
599
|
-
const start = compileError.loc?.start ?? compileError.loc;
|
|
600
|
-
const line = start?.line;
|
|
601
|
-
const column = start?.column;
|
|
602
|
-
const locationSuffix = line === void 0 || column === void 0 ? "" : ` (${filename}:${line}:${column + 1})`;
|
|
603
|
-
compileError.message = `[olova] ${compileError.message.replace(/^\[olova\]\s*/, "")}${locationSuffix}`;
|
|
604
|
-
if (line !== void 0 && column !== void 0 && !compileError.frame) {
|
|
605
|
-
compileError.frame = createCodeFrame(source, line, column);
|
|
651
|
+
function emitDevWarnings(warnings) {
|
|
652
|
+
for (const warning of warnings) {
|
|
653
|
+
console.warn(warning);
|
|
606
654
|
}
|
|
607
|
-
throw compileError;
|
|
608
655
|
}
|
|
609
656
|
function parseModule(code) {
|
|
610
657
|
return parse(code, {
|
|
@@ -612,6 +659,17 @@ function parseModule(code) {
|
|
|
612
659
|
plugins: ["typescript", "jsx"]
|
|
613
660
|
});
|
|
614
661
|
}
|
|
662
|
+
function runCompilePhase(phase, fn) {
|
|
663
|
+
try {
|
|
664
|
+
return fn();
|
|
665
|
+
} catch (error) {
|
|
666
|
+
const compileError = error instanceof Error ? error : new Error(String(error));
|
|
667
|
+
if (!compileError.phase) {
|
|
668
|
+
compileError.phase = phase;
|
|
669
|
+
}
|
|
670
|
+
throw compileError;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
615
673
|
function transpileTypeScript(code, filename = "component.ts") {
|
|
616
674
|
const result = ts.transpileModule(code, {
|
|
617
675
|
fileName: filename,
|
|
@@ -631,20 +689,22 @@ function transpileTypeScript(code, filename = "component.ts") {
|
|
|
631
689
|
};
|
|
632
690
|
}
|
|
633
691
|
function compileModuleScript(script, filename = "module.olova.ts") {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
692
|
+
return runCompilePhase("analyze-script", () => {
|
|
693
|
+
const scriptInfo = collectScriptInfo(script);
|
|
694
|
+
return runCompilePhase(
|
|
695
|
+
"transpile",
|
|
696
|
+
() => transpileTypeScript(
|
|
697
|
+
[scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
|
|
698
|
+
filename
|
|
699
|
+
)
|
|
700
|
+
);
|
|
701
|
+
});
|
|
639
702
|
}
|
|
640
703
|
function isModuleOnlyScript(script) {
|
|
641
704
|
if (!script.trim()) {
|
|
642
705
|
return false;
|
|
643
706
|
}
|
|
644
|
-
|
|
645
|
-
return ast.program.body.every(
|
|
646
|
-
(node) => t.isImportDeclaration(node) || t.isExportNamedDeclaration(node) || t.isExportAllDeclaration(node) || t.isExportDefaultDeclaration(node)
|
|
647
|
-
);
|
|
707
|
+
return true;
|
|
648
708
|
}
|
|
649
709
|
function isStateInit(node) {
|
|
650
710
|
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")));
|
|
@@ -970,7 +1030,7 @@ function collectScriptInfo(script) {
|
|
|
970
1030
|
return false;
|
|
971
1031
|
}
|
|
972
1032
|
let found = false;
|
|
973
|
-
traverse(parseModule(`(${
|
|
1033
|
+
traverse(parseModule(`(${generate2(node).code})`), {
|
|
974
1034
|
JSXElement() {
|
|
975
1035
|
found = true;
|
|
976
1036
|
},
|
|
@@ -995,7 +1055,7 @@ function collectScriptInfo(script) {
|
|
|
995
1055
|
traverse(ast, {
|
|
996
1056
|
JSXElement(path2) {
|
|
997
1057
|
const transformed = transformExpression(
|
|
998
|
-
|
|
1058
|
+
generate2(path2.node).code,
|
|
999
1059
|
stateNames,
|
|
1000
1060
|
allSignalNames,
|
|
1001
1061
|
jsxFunctionNames
|
|
@@ -1009,7 +1069,7 @@ function collectScriptInfo(script) {
|
|
|
1009
1069
|
},
|
|
1010
1070
|
JSXFragment(path2) {
|
|
1011
1071
|
const transformed = transformExpression(
|
|
1012
|
-
|
|
1072
|
+
generate2(path2.node).code,
|
|
1013
1073
|
stateNames,
|
|
1014
1074
|
allSignalNames,
|
|
1015
1075
|
jsxFunctionNames
|
|
@@ -1041,8 +1101,8 @@ function collectScriptInfo(script) {
|
|
|
1041
1101
|
const bodyNodes = ast.program.body.filter(
|
|
1042
1102
|
(node) => !t.isImportDeclaration(node)
|
|
1043
1103
|
);
|
|
1044
|
-
const importsCode = importNodes.map((node) =>
|
|
1045
|
-
const scriptBodyCode = bodyNodes.map((node) =>
|
|
1104
|
+
const importsCode = importNodes.map((node) => generate2(node).code).join("\n");
|
|
1105
|
+
const scriptBodyCode = bodyNodes.map((node) => generate2(node).code).join("\n");
|
|
1046
1106
|
return {
|
|
1047
1107
|
importsCode,
|
|
1048
1108
|
scriptBodyCode,
|
|
@@ -1116,7 +1176,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
|
|
|
1116
1176
|
const escapeCall = (input) => t.callExpression(t.identifier("__olovaEscape"), [input]);
|
|
1117
1177
|
const expressionContainsJsx = (input) => {
|
|
1118
1178
|
let found = false;
|
|
1119
|
-
traverse(parseModule(`(${
|
|
1179
|
+
traverse(parseModule(`(${generate2(input).code})`), {
|
|
1120
1180
|
JSXElement() {
|
|
1121
1181
|
found = true;
|
|
1122
1182
|
},
|
|
@@ -1134,7 +1194,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
|
|
|
1134
1194
|
return true;
|
|
1135
1195
|
}
|
|
1136
1196
|
let found = false;
|
|
1137
|
-
traverse(parseModule(`(${
|
|
1197
|
+
traverse(parseModule(`(${generate2(input).code})`), {
|
|
1138
1198
|
CallExpression(path2) {
|
|
1139
1199
|
if (t.isIdentifier(path2.node.callee) && jsxFunctionNames.has(path2.node.callee.name)) {
|
|
1140
1200
|
found = true;
|
|
@@ -1189,7 +1249,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
|
|
|
1189
1249
|
return walkMember(name);
|
|
1190
1250
|
};
|
|
1191
1251
|
const transformEmbeddedJsxExpression = (input) => {
|
|
1192
|
-
const wrapped = parseModule(`(${
|
|
1252
|
+
const wrapped = parseModule(`(${generate2(input).code})`);
|
|
1193
1253
|
let containsNestedJsx = false;
|
|
1194
1254
|
traverse(wrapped, {
|
|
1195
1255
|
JSXElement(path2) {
|
|
@@ -1376,7 +1436,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
|
|
|
1376
1436
|
return { code: expr.trim(), containsJsx };
|
|
1377
1437
|
}
|
|
1378
1438
|
containsJsx = containsJsx || expressionProducesHtml(statement.expression);
|
|
1379
|
-
return { code:
|
|
1439
|
+
return { code: generate2(statement.expression).code, containsJsx };
|
|
1380
1440
|
}
|
|
1381
1441
|
function jsxToTemplateHtml(node) {
|
|
1382
1442
|
if (t.isJSXElement(node)) {
|
|
@@ -1385,15 +1445,15 @@ function jsxToTemplateHtml(node) {
|
|
|
1385
1445
|
if (t.isJSXIdentifier(opening.name)) {
|
|
1386
1446
|
tag = opening.name.name;
|
|
1387
1447
|
} else if (t.isJSXMemberExpression(opening.name)) {
|
|
1388
|
-
const
|
|
1389
|
-
tag =
|
|
1448
|
+
const walk2 = (n) => t.isJSXIdentifier(n) ? n.name : `${walk2(n.object)}.${n.property.name}`;
|
|
1449
|
+
tag = walk2(opening.name);
|
|
1390
1450
|
} else if (t.isJSXNamespacedName(opening.name)) {
|
|
1391
1451
|
tag = `${opening.name.namespace.name}:${opening.name.name.name}`;
|
|
1392
1452
|
}
|
|
1393
1453
|
let attrsStr = "";
|
|
1394
1454
|
for (const attr of opening.attributes) {
|
|
1395
1455
|
if (t.isJSXSpreadAttribute(attr)) {
|
|
1396
|
-
attrsStr += ` {...${
|
|
1456
|
+
attrsStr += ` {...${generate2(attr.argument).code}}`;
|
|
1397
1457
|
} else {
|
|
1398
1458
|
let attrName = "";
|
|
1399
1459
|
if (t.isJSXIdentifier(attr.name)) attrName = attr.name.name;
|
|
@@ -1403,7 +1463,7 @@ function jsxToTemplateHtml(node) {
|
|
|
1403
1463
|
attrsStr += ` ${attrName}="${attr.value.value}"`;
|
|
1404
1464
|
else if (t.isJSXExpressionContainer(attr.value)) {
|
|
1405
1465
|
if (!t.isJSXEmptyExpression(attr.value.expression)) {
|
|
1406
|
-
attrsStr += ` ${attrName}={${
|
|
1466
|
+
attrsStr += ` ${attrName}={${generate2(attr.value.expression).code}}`;
|
|
1407
1467
|
}
|
|
1408
1468
|
}
|
|
1409
1469
|
}
|
|
@@ -1419,7 +1479,7 @@ function jsxToTemplateHtml(node) {
|
|
|
1419
1479
|
}
|
|
1420
1480
|
if (t.isJSXExpressionContainer(node)) {
|
|
1421
1481
|
if (t.isJSXEmptyExpression(node.expression)) return "";
|
|
1422
|
-
return `{${
|
|
1482
|
+
return `{${generate2(node.expression).code}}`;
|
|
1423
1483
|
}
|
|
1424
1484
|
if (t.isJSXFragment(node)) {
|
|
1425
1485
|
return node.children.map(jsxToTemplateHtml).join("");
|
|
@@ -1429,56 +1489,78 @@ function jsxToTemplateHtml(node) {
|
|
|
1429
1489
|
function replaceTemplateExpressions(input, replacer) {
|
|
1430
1490
|
let output = "";
|
|
1431
1491
|
let index = 0;
|
|
1492
|
+
let inTag = false;
|
|
1493
|
+
let tagQuote = null;
|
|
1494
|
+
let tagBraceDepth = 0;
|
|
1432
1495
|
while (index < input.length) {
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1496
|
+
const char = input[index];
|
|
1497
|
+
const prev = index > 0 ? input[index - 1] : "";
|
|
1498
|
+
if (inTag) {
|
|
1499
|
+
output += char;
|
|
1500
|
+
if (tagQuote) {
|
|
1501
|
+
if (char === tagQuote && prev !== "\\") {
|
|
1502
|
+
tagQuote = null;
|
|
1503
|
+
}
|
|
1504
|
+
index += 1;
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
1508
|
+
tagQuote = char;
|
|
1509
|
+
} else if (char === "{") {
|
|
1510
|
+
tagBraceDepth += 1;
|
|
1511
|
+
} else if (char === "}" && tagBraceDepth > 0) {
|
|
1512
|
+
tagBraceDepth -= 1;
|
|
1513
|
+
} else if (char === ">" && tagBraceDepth === 0) {
|
|
1514
|
+
inTag = false;
|
|
1515
|
+
}
|
|
1516
|
+
index += 1;
|
|
1517
|
+
continue;
|
|
1437
1518
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1519
|
+
if (char === "<") {
|
|
1520
|
+
inTag = true;
|
|
1521
|
+
output += char;
|
|
1522
|
+
index += 1;
|
|
1523
|
+
continue;
|
|
1441
1524
|
}
|
|
1442
|
-
if (
|
|
1443
|
-
output +=
|
|
1444
|
-
index
|
|
1525
|
+
if (char !== "{") {
|
|
1526
|
+
output += char;
|
|
1527
|
+
index += 1;
|
|
1445
1528
|
continue;
|
|
1446
1529
|
}
|
|
1447
|
-
if (
|
|
1448
|
-
output +=
|
|
1449
|
-
index
|
|
1530
|
+
if (prev === "$") {
|
|
1531
|
+
output += char;
|
|
1532
|
+
index += 1;
|
|
1450
1533
|
continue;
|
|
1451
1534
|
}
|
|
1452
|
-
output += input.slice(index, start);
|
|
1453
1535
|
let depth = 1;
|
|
1454
|
-
let cursor =
|
|
1536
|
+
let cursor = index + 1;
|
|
1455
1537
|
let quote = null;
|
|
1456
1538
|
while (cursor < input.length && depth > 0) {
|
|
1457
|
-
const
|
|
1458
|
-
const
|
|
1539
|
+
const char2 = input[cursor];
|
|
1540
|
+
const prev2 = input[cursor - 1];
|
|
1459
1541
|
if (quote) {
|
|
1460
|
-
if (
|
|
1542
|
+
if (char2 === quote && prev2 !== "\\") {
|
|
1461
1543
|
quote = null;
|
|
1462
1544
|
}
|
|
1463
1545
|
cursor += 1;
|
|
1464
1546
|
continue;
|
|
1465
1547
|
}
|
|
1466
|
-
if (
|
|
1467
|
-
quote =
|
|
1548
|
+
if (char2 === "'" || char2 === '"' || char2 === "`") {
|
|
1549
|
+
quote = char2;
|
|
1468
1550
|
cursor += 1;
|
|
1469
1551
|
continue;
|
|
1470
1552
|
}
|
|
1471
|
-
if (
|
|
1553
|
+
if (char2 === "{") {
|
|
1472
1554
|
depth += 1;
|
|
1473
|
-
} else if (
|
|
1555
|
+
} else if (char2 === "}") {
|
|
1474
1556
|
depth -= 1;
|
|
1475
1557
|
}
|
|
1476
1558
|
cursor += 1;
|
|
1477
1559
|
}
|
|
1478
1560
|
if (depth !== 0) {
|
|
1479
|
-
throw createCompileError("[olova] Unclosed template expression block.", input,
|
|
1561
|
+
throw createCompileError("[olova] Unclosed template expression block.", input, index);
|
|
1480
1562
|
}
|
|
1481
|
-
const expression = input.slice(
|
|
1563
|
+
const expression = input.slice(index + 1, cursor - 1);
|
|
1482
1564
|
output += replacer(expression);
|
|
1483
1565
|
index = cursor;
|
|
1484
1566
|
}
|
|
@@ -1512,7 +1594,7 @@ function parsePropsFromAttrs(attrs, stateNames, allSignalNames, jsxFunctionNames
|
|
|
1512
1594
|
}
|
|
1513
1595
|
return props.length === 0 ? "{}" : `{ ${props.join(", ")} }`;
|
|
1514
1596
|
}
|
|
1515
|
-
function transformTemplate(template, stateNames, allSignalNames, jsxFunctionNames, componentNames, counters, scopeId) {
|
|
1597
|
+
function transformTemplate(template, stateNames, allSignalNames, jsxFunctionNames, componentNames, counters, scopeId, devWarnings, filename = "component.olova") {
|
|
1516
1598
|
let html = template;
|
|
1517
1599
|
const textBindings = [];
|
|
1518
1600
|
const htmlBindings = [];
|
|
@@ -1529,7 +1611,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1529
1611
|
const node = exprNode.expression;
|
|
1530
1612
|
const containsJsxNode = (n) => {
|
|
1531
1613
|
let found = false;
|
|
1532
|
-
traverse(parseModule(`(${
|
|
1614
|
+
traverse(parseModule(`(${generate2(n).code})`), {
|
|
1533
1615
|
JSXElement() {
|
|
1534
1616
|
found = true;
|
|
1535
1617
|
},
|
|
@@ -1542,7 +1624,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1542
1624
|
if (t.isLogicalExpression(node) && node.operator === "&&") {
|
|
1543
1625
|
if (containsJsxNode(node.right)) {
|
|
1544
1626
|
const id = `i${counters.if++}`;
|
|
1545
|
-
const condition =
|
|
1627
|
+
const condition = generate2(node.left).code;
|
|
1546
1628
|
const trueBranchHtml = jsxToTemplateHtml(node.right);
|
|
1547
1629
|
ifBindings.push({
|
|
1548
1630
|
id,
|
|
@@ -1560,7 +1642,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1560
1642
|
jsxFunctionNames,
|
|
1561
1643
|
componentNames,
|
|
1562
1644
|
counters,
|
|
1563
|
-
scopeId
|
|
1645
|
+
scopeId,
|
|
1646
|
+
devWarnings,
|
|
1647
|
+
filename
|
|
1564
1648
|
)
|
|
1565
1649
|
});
|
|
1566
1650
|
return `__O_IF_${id}__`;
|
|
@@ -1568,9 +1652,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1568
1652
|
} else if (t.isConditionalExpression(node)) {
|
|
1569
1653
|
if (containsJsxNode(node.consequent) || containsJsxNode(node.alternate)) {
|
|
1570
1654
|
const id = `i${counters.if++}`;
|
|
1571
|
-
const condition =
|
|
1572
|
-
const trueBranchHtml = containsJsxNode(node.consequent) ? jsxToTemplateHtml(node.consequent) : `{${
|
|
1573
|
-
const falseBranchHtml = containsJsxNode(node.alternate) ? jsxToTemplateHtml(node.alternate) : `{${
|
|
1655
|
+
const condition = generate2(node.test).code;
|
|
1656
|
+
const trueBranchHtml = containsJsxNode(node.consequent) ? jsxToTemplateHtml(node.consequent) : `{${generate2(node.consequent).code}}`;
|
|
1657
|
+
const falseBranchHtml = containsJsxNode(node.alternate) ? jsxToTemplateHtml(node.alternate) : `{${generate2(node.alternate).code}}`;
|
|
1574
1658
|
ifBindings.push({
|
|
1575
1659
|
id,
|
|
1576
1660
|
expr: transformExpression(
|
|
@@ -1587,7 +1671,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1587
1671
|
jsxFunctionNames,
|
|
1588
1672
|
componentNames,
|
|
1589
1673
|
counters,
|
|
1590
|
-
scopeId
|
|
1674
|
+
scopeId,
|
|
1675
|
+
devWarnings,
|
|
1676
|
+
filename
|
|
1591
1677
|
),
|
|
1592
1678
|
falseBranch: transformTemplate(
|
|
1593
1679
|
falseBranchHtml,
|
|
@@ -1596,7 +1682,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1596
1682
|
jsxFunctionNames,
|
|
1597
1683
|
componentNames,
|
|
1598
1684
|
counters,
|
|
1599
|
-
scopeId
|
|
1685
|
+
scopeId,
|
|
1686
|
+
devWarnings,
|
|
1687
|
+
filename
|
|
1600
1688
|
)
|
|
1601
1689
|
});
|
|
1602
1690
|
return `__O_IF_${id}__`;
|
|
@@ -1651,6 +1739,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1651
1739
|
continue;
|
|
1652
1740
|
}
|
|
1653
1741
|
const normalizedAttrName = attrName === "className" ? "class" : attrName;
|
|
1742
|
+
const lowerAttrName = normalizedAttrName.toLowerCase();
|
|
1654
1743
|
if (rawValue === true) {
|
|
1655
1744
|
output += ` ${normalizedAttrName}`;
|
|
1656
1745
|
continue;
|
|
@@ -1683,6 +1772,16 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1683
1772
|
output += ` ${normalizedAttrName}="__O_ATTR_${id}__"`;
|
|
1684
1773
|
continue;
|
|
1685
1774
|
}
|
|
1775
|
+
if (devWarnings && lowerAttrName.startsWith("on")) {
|
|
1776
|
+
devWarnings.add(
|
|
1777
|
+
`[olova] Avoid static inline event attributes like "${normalizedAttrName}" in templates (${filename}). Prefer ${normalizedAttrName}={handler}.`
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
if (devWarnings && (lowerAttrName === "href" || lowerAttrName === "src" || lowerAttrName === "xlink:href") && /^\s*javascript:/i.test(rawValue)) {
|
|
1781
|
+
devWarnings.add(
|
|
1782
|
+
`[olova] Suspicious javascript: URL found in static "${normalizedAttrName}" attribute (${filename}).`
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1686
1785
|
output += ` ${normalizedAttrName}="${rawValue.replace(/"/g, """)}"`;
|
|
1687
1786
|
}
|
|
1688
1787
|
return output;
|
|
@@ -1731,6 +1830,73 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1731
1830
|
defaultNodes.push(child);
|
|
1732
1831
|
}
|
|
1733
1832
|
const slotFactories = [];
|
|
1833
|
+
if (node.tag === "Suspense" && typeof node.attrs.fallback === "string") {
|
|
1834
|
+
const rawValue = node.attrs.fallback;
|
|
1835
|
+
if (rawValue.startsWith("{") && rawValue.endsWith("}")) {
|
|
1836
|
+
const fallbackExpression = rawValue.slice(1, -1).trim();
|
|
1837
|
+
const fallbackAst = parseModule(`(${fallbackExpression})`);
|
|
1838
|
+
const fallbackStatement = fallbackAst.program.body[0];
|
|
1839
|
+
const fallbackNode = fallbackStatement && t.isExpressionStatement(fallbackStatement) ? fallbackStatement.expression : null;
|
|
1840
|
+
if (fallbackNode && (t.isJSXElement(fallbackNode) || t.isJSXFragment(fallbackNode))) {
|
|
1841
|
+
slotFactories.push({
|
|
1842
|
+
varName: `__slot_${id}_fallback`,
|
|
1843
|
+
name: "fallback",
|
|
1844
|
+
descriptor: transformTemplate(
|
|
1845
|
+
jsxToTemplateHtml(fallbackNode),
|
|
1846
|
+
stateNames,
|
|
1847
|
+
allSignalNames,
|
|
1848
|
+
jsxFunctionNames,
|
|
1849
|
+
componentNames,
|
|
1850
|
+
counters,
|
|
1851
|
+
scopeId,
|
|
1852
|
+
devWarnings,
|
|
1853
|
+
filename
|
|
1854
|
+
)
|
|
1855
|
+
});
|
|
1856
|
+
} else {
|
|
1857
|
+
const transformedFallback = transformExpression(
|
|
1858
|
+
fallbackExpression,
|
|
1859
|
+
stateNames,
|
|
1860
|
+
allSignalNames,
|
|
1861
|
+
jsxFunctionNames,
|
|
1862
|
+
scopeId
|
|
1863
|
+
);
|
|
1864
|
+
const fallbackBindingId = `fallback_${id}`;
|
|
1865
|
+
slotFactories.push({
|
|
1866
|
+
varName: `__slot_${id}_fallback`,
|
|
1867
|
+
name: "fallback",
|
|
1868
|
+
descriptor: {
|
|
1869
|
+
html: `__O_HTML_${fallbackBindingId}__`,
|
|
1870
|
+
textBindings: [],
|
|
1871
|
+
htmlBindings: [
|
|
1872
|
+
{
|
|
1873
|
+
id: fallbackBindingId,
|
|
1874
|
+
expr: transformedFallback.containsJsx ? `__olovaDangerouslySetHtml(${transformedFallback.code})` : transformedFallback.code
|
|
1875
|
+
}
|
|
1876
|
+
],
|
|
1877
|
+
attrBindings: [],
|
|
1878
|
+
eventBindings: [],
|
|
1879
|
+
slotBindings: [],
|
|
1880
|
+
componentBindings: [],
|
|
1881
|
+
ifBindings: [],
|
|
1882
|
+
buildFnStr: generateBuildFunction(
|
|
1883
|
+
`__O_HTML_${fallbackBindingId}__`,
|
|
1884
|
+
stateNames,
|
|
1885
|
+
(expr) => transformExpression(
|
|
1886
|
+
expr,
|
|
1887
|
+
stateNames,
|
|
1888
|
+
allSignalNames,
|
|
1889
|
+
jsxFunctionNames,
|
|
1890
|
+
scopeId
|
|
1891
|
+
),
|
|
1892
|
+
counters,
|
|
1893
|
+
componentNames
|
|
1894
|
+
)
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1734
1900
|
if (defaultNodes.length > 0) {
|
|
1735
1901
|
slotFactories.push({
|
|
1736
1902
|
varName: `__slot_${id}_default`,
|
|
@@ -1742,7 +1908,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1742
1908
|
jsxFunctionNames,
|
|
1743
1909
|
componentNames,
|
|
1744
1910
|
counters,
|
|
1745
|
-
scopeId
|
|
1911
|
+
scopeId,
|
|
1912
|
+
devWarnings,
|
|
1913
|
+
filename
|
|
1746
1914
|
)
|
|
1747
1915
|
});
|
|
1748
1916
|
}
|
|
@@ -1765,7 +1933,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1765
1933
|
id,
|
|
1766
1934
|
component: node.tag,
|
|
1767
1935
|
propsExpr: parsePropsFromAttrs(
|
|
1768
|
-
node.
|
|
1936
|
+
node.tag === "Suspense" ? Object.fromEntries(
|
|
1937
|
+
Object.entries(node.attrs).filter(([key]) => key !== "fallback")
|
|
1938
|
+
) : node.attrs,
|
|
1769
1939
|
stateNames,
|
|
1770
1940
|
allSignalNames,
|
|
1771
1941
|
jsxFunctionNames
|
|
@@ -1790,7 +1960,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
|
|
|
1790
1960
|
scopeId
|
|
1791
1961
|
);
|
|
1792
1962
|
const buildFnStr = generateBuildFunction(
|
|
1793
|
-
|
|
1963
|
+
html,
|
|
1794
1964
|
stateNames,
|
|
1795
1965
|
resolveExpr,
|
|
1796
1966
|
counters,
|
|
@@ -1873,14 +2043,20 @@ function buildComponentCode(parsed, options) {
|
|
|
1873
2043
|
slot: 0,
|
|
1874
2044
|
if: 0
|
|
1875
2045
|
};
|
|
1876
|
-
const
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2046
|
+
const warnings = options?.dev ? /* @__PURE__ */ new Set() : void 0;
|
|
2047
|
+
const transformed = runCompilePhase(
|
|
2048
|
+
"transform-template",
|
|
2049
|
+
() => transformTemplate(
|
|
2050
|
+
parsed.template,
|
|
2051
|
+
scriptInfo.stateNames,
|
|
2052
|
+
scriptInfo.allSignalNames,
|
|
2053
|
+
scriptInfo.jsxFunctionNames,
|
|
2054
|
+
scriptInfo.componentNames,
|
|
2055
|
+
counters,
|
|
2056
|
+
options?.scopeId,
|
|
2057
|
+
warnings,
|
|
2058
|
+
options?.hmrId ?? "component.olova"
|
|
2059
|
+
)
|
|
1884
2060
|
);
|
|
1885
2061
|
const exportHead = options?.exportName ? `export const ${options.exportName} = __defineComponent((__props, __slots) => {` : `const __olovaComponent = __defineComponent((__props, __slots) => {`;
|
|
1886
2062
|
const exportTail = options?.exportName ? `}, ${JSON.stringify(options?.hmrId)});` : `}, ${JSON.stringify(options?.hmrId)});
|
|
@@ -1894,6 +2070,9 @@ if (import.meta.hot) {
|
|
|
1894
2070
|
});
|
|
1895
2071
|
}
|
|
1896
2072
|
` : "";
|
|
2073
|
+
if (warnings) {
|
|
2074
|
+
emitDevWarnings(warnings);
|
|
2075
|
+
}
|
|
1897
2076
|
const code = `
|
|
1898
2077
|
${exportHead}
|
|
1899
2078
|
const props = __props;
|
|
@@ -2007,140 +2186,64 @@ ${hmrCode}
|
|
|
2007
2186
|
`;
|
|
2008
2187
|
return { importsCode: scriptInfo.importsCode, code };
|
|
2009
2188
|
}
|
|
2010
|
-
function hashScopeSeed(input) {
|
|
2011
|
-
let hash = 2166136261;
|
|
2012
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
2013
|
-
hash ^= input.charCodeAt(index);
|
|
2014
|
-
hash = Math.imul(hash, 16777619);
|
|
2015
|
-
}
|
|
2016
|
-
return Math.abs(hash >>> 0).toString(36);
|
|
2017
|
-
}
|
|
2018
|
-
function createScopeAttr(scopeId) {
|
|
2019
|
-
return scopeId ? `data-o-scope-${scopeId}` : null;
|
|
2020
|
-
}
|
|
2021
|
-
function scopeSelectorList(selectorList, scopeAttr) {
|
|
2022
|
-
const applyScopeToSegment = (segment) => {
|
|
2023
|
-
const trimmed = segment.trim();
|
|
2024
|
-
if (!trimmed) {
|
|
2025
|
-
return trimmed;
|
|
2026
|
-
}
|
|
2027
|
-
if (trimmed.includes(scopeAttr)) {
|
|
2028
|
-
return trimmed;
|
|
2029
|
-
}
|
|
2030
|
-
const globalPattern = /:global\(([^()]+)\)/g;
|
|
2031
|
-
const localCandidate = trimmed.replace(globalPattern, "").trim();
|
|
2032
|
-
const normalized = trimmed.replace(globalPattern, "$1");
|
|
2033
|
-
if (!localCandidate) {
|
|
2034
|
-
return normalized;
|
|
2035
|
-
}
|
|
2036
|
-
if (normalized === ":root") {
|
|
2037
|
-
return `[${scopeAttr}]`;
|
|
2038
|
-
}
|
|
2039
|
-
const pseudoIndex = normalized.search(/(?<!:):(?!:)/);
|
|
2040
|
-
if (pseudoIndex === -1) {
|
|
2041
|
-
return `${normalized}[${scopeAttr}]`;
|
|
2042
|
-
}
|
|
2043
|
-
return `${normalized.slice(0, pseudoIndex)}[${scopeAttr}]${normalized.slice(pseudoIndex)}`;
|
|
2044
|
-
};
|
|
2045
|
-
return selectorList.split(",").map((selector) => selector.trim()).filter(Boolean).map((selector) => {
|
|
2046
|
-
if (selector.startsWith("@") || selector.includes(scopeAttr)) {
|
|
2047
|
-
return selector;
|
|
2048
|
-
}
|
|
2049
|
-
return selector.split(/(\s+|[>+~])/).map((part) => {
|
|
2050
|
-
if (!part || /^(\s+|[>+~])$/.test(part)) {
|
|
2051
|
-
return part;
|
|
2052
|
-
}
|
|
2053
|
-
return applyScopeToSegment(part);
|
|
2054
|
-
}).join("");
|
|
2055
|
-
}).join(", ");
|
|
2056
|
-
}
|
|
2057
|
-
function scopeCss(css, scopeAttr) {
|
|
2058
|
-
let output = "";
|
|
2059
|
-
let index = 0;
|
|
2060
|
-
while (index < css.length) {
|
|
2061
|
-
const open = css.indexOf("{", index);
|
|
2062
|
-
if (open < 0) {
|
|
2063
|
-
output += css.slice(index);
|
|
2064
|
-
break;
|
|
2065
|
-
}
|
|
2066
|
-
const selector = css.slice(index, open).trim();
|
|
2067
|
-
let depth = 1;
|
|
2068
|
-
let cursor = open + 1;
|
|
2069
|
-
while (cursor < css.length && depth > 0) {
|
|
2070
|
-
if (css[cursor] === "{") {
|
|
2071
|
-
depth += 1;
|
|
2072
|
-
} else if (css[cursor] === "}") {
|
|
2073
|
-
depth -= 1;
|
|
2074
|
-
}
|
|
2075
|
-
cursor += 1;
|
|
2076
|
-
}
|
|
2077
|
-
const body = css.slice(open + 1, cursor - 1);
|
|
2078
|
-
if (/^@(?:media|supports|container|layer)\b/i.test(selector)) {
|
|
2079
|
-
output += `${selector} {${scopeCss(body, scopeAttr)}}`;
|
|
2080
|
-
} else if (/^@(?:keyframes|-webkit-keyframes)\b/i.test(selector)) {
|
|
2081
|
-
output += `${selector} {${body}}`;
|
|
2082
|
-
} else {
|
|
2083
|
-
output += `${scopeSelectorList(selector, scopeAttr)} {${body}}`;
|
|
2084
|
-
}
|
|
2085
|
-
index = cursor;
|
|
2086
|
-
}
|
|
2087
|
-
return output;
|
|
2088
|
-
}
|
|
2089
|
-
function collectCss(blocks, scopeId) {
|
|
2090
|
-
const scopeAttr = createScopeAttr(scopeId);
|
|
2091
|
-
return blocks.map((block) => {
|
|
2092
|
-
const content = block.content.trim();
|
|
2093
|
-
if (!content) {
|
|
2094
|
-
return "";
|
|
2095
|
-
}
|
|
2096
|
-
if (!("global" in block.attrs) && scopeAttr) {
|
|
2097
|
-
return scopeCss(content, scopeAttr);
|
|
2098
|
-
}
|
|
2099
|
-
return content;
|
|
2100
|
-
}).filter(Boolean).join("\n\n");
|
|
2101
|
-
}
|
|
2102
2189
|
function compileOlovaScriptModule(source, options) {
|
|
2103
2190
|
try {
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2191
|
+
return runCompilePhase("analyze-script", () => {
|
|
2192
|
+
const scriptInfo = collectScriptInfo(source);
|
|
2193
|
+
return runCompilePhase(
|
|
2194
|
+
"transpile",
|
|
2195
|
+
() => transpileTypeScript(
|
|
2196
|
+
[scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
|
|
2197
|
+
options?.filename ?? "module.ts"
|
|
2198
|
+
)
|
|
2199
|
+
);
|
|
2200
|
+
});
|
|
2109
2201
|
} catch (error) {
|
|
2110
2202
|
return normalizeCompileError(error, source, options?.filename ?? "module.ts");
|
|
2111
2203
|
}
|
|
2112
2204
|
}
|
|
2113
2205
|
function compileOlova(source, options) {
|
|
2206
|
+
const filename = options?.filename ?? "component.olova";
|
|
2114
2207
|
try {
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2208
|
+
return runCompilePhase("parse-file", () => {
|
|
2209
|
+
const parsed = parseOlovaFile(source);
|
|
2210
|
+
if (!parsed.template.trim() && isModuleOnlyScript(parsed.script)) {
|
|
2211
|
+
const moduleResult = compileModuleScript(parsed.script, `${filename}.ts`);
|
|
2212
|
+
const css2 = runCompilePhase(
|
|
2213
|
+
"generate-module",
|
|
2214
|
+
() => collectCss(parsed.styles)
|
|
2215
|
+
);
|
|
2216
|
+
return {
|
|
2217
|
+
...moduleResult,
|
|
2218
|
+
css: css2
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
const scopeId = parsed.styles.some((style) => !("global" in style.attrs)) ? hashScopeSeed(`${filename}:${options?.exportName ?? "default"}`) : void 0;
|
|
2222
|
+
const compiled = runCompilePhase(
|
|
2223
|
+
"generate-module",
|
|
2224
|
+
() => buildComponentCode(parsed, {
|
|
2225
|
+
scopeId,
|
|
2226
|
+
hmrId: filename,
|
|
2227
|
+
dev: options?.dev
|
|
2228
|
+
})
|
|
2229
|
+
);
|
|
2230
|
+
const runtimeImport = options?.dev ? `import { defineComponent as __defineComponent, replaceComponent as __replaceComponent } from 'olova/runtime';` : `import { defineComponent as __defineComponent } from 'olova/runtime';`;
|
|
2231
|
+
const result = runCompilePhase("transpile", () => transpileTypeScript(`
|
|
2130
2232
|
${runtimeImport}
|
|
2131
2233
|
${compiled.importsCode}
|
|
2132
2234
|
${compiled.code}
|
|
2133
|
-
`,
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2235
|
+
`, `${filename}.ts`));
|
|
2236
|
+
const css = runCompilePhase(
|
|
2237
|
+
"generate-module",
|
|
2238
|
+
() => collectCss(parsed.styles, scopeId)
|
|
2239
|
+
);
|
|
2240
|
+
return {
|
|
2241
|
+
...result,
|
|
2242
|
+
css
|
|
2243
|
+
};
|
|
2244
|
+
});
|
|
2138
2245
|
} catch (error) {
|
|
2139
|
-
return normalizeCompileError(
|
|
2140
|
-
error,
|
|
2141
|
-
source,
|
|
2142
|
-
options?.filename ?? "component.olova"
|
|
2143
|
-
);
|
|
2246
|
+
return normalizeCompileError(error, source, filename);
|
|
2144
2247
|
}
|
|
2145
2248
|
}
|
|
2146
2249
|
|
|
@@ -2271,7 +2374,10 @@ function olovaPlugin() {
|
|
|
2271
2374
|
map: result.map || null
|
|
2272
2375
|
};
|
|
2273
2376
|
} catch (err) {
|
|
2274
|
-
this
|
|
2377
|
+
if (typeof this?.error === "function") {
|
|
2378
|
+
this.error(err, err.loc?.start?.line);
|
|
2379
|
+
}
|
|
2380
|
+
throw err;
|
|
2275
2381
|
}
|
|
2276
2382
|
},
|
|
2277
2383
|
async handleHotUpdate(ctx) {
|
|
@@ -2289,8 +2395,12 @@ function olovaPlugin() {
|
|
|
2289
2395
|
} else {
|
|
2290
2396
|
cssCache.delete(filename);
|
|
2291
2397
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2398
|
+
const styleId2 = `${filename}.css${STYLE_QUERY}`;
|
|
2399
|
+
const styleModule2 = ctx.server.moduleGraph.getModuleById(styleId2);
|
|
2400
|
+
if (styleModule2) {
|
|
2401
|
+
ctx.server.moduleGraph.invalidateModule(styleModule2);
|
|
2402
|
+
return [styleModule2];
|
|
2403
|
+
}
|
|
2294
2404
|
}
|
|
2295
2405
|
} catch {
|
|
2296
2406
|
}
|