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/dist/compiler.js CHANGED
@@ -3,8 +3,161 @@ import generatorModule from '@babel/generator';
3
3
  import traverseModule from '@babel/traverse';
4
4
  import * as t from '@babel/types';
5
5
  import ts from 'typescript';
6
+ import * as csstree from 'css-tree';
7
+ import { parseDocument } from 'htmlparser2';
6
8
 
7
9
  // compiler/compile.ts
10
+ function createScopeAttr(scopeId) {
11
+ return scopeId ? `data-o-scope-${scopeId}` : null;
12
+ }
13
+ function scopeSelectorList(selectorList, scopeAttr) {
14
+ const applyScopeToSegment = (segment) => {
15
+ const trimmed = segment.trim();
16
+ if (!trimmed) {
17
+ return trimmed;
18
+ }
19
+ if (trimmed.includes(scopeAttr)) {
20
+ return trimmed;
21
+ }
22
+ const globalPattern = /:global\(([^()]+)\)/g;
23
+ const localCandidate = trimmed.replace(globalPattern, "").trim();
24
+ const normalized = trimmed.replace(globalPattern, "$1");
25
+ if (!localCandidate) {
26
+ return normalized;
27
+ }
28
+ if (normalized === ":root") {
29
+ return `[${scopeAttr}]`;
30
+ }
31
+ const pseudoIndex = normalized.search(/(?<!:):(?!:)/);
32
+ if (pseudoIndex === -1) {
33
+ return `${normalized}[${scopeAttr}]`;
34
+ }
35
+ return `${normalized.slice(0, pseudoIndex)}[${scopeAttr}]${normalized.slice(pseudoIndex)}`;
36
+ };
37
+ return selectorList.split(",").map((selector) => selector.trim()).filter(Boolean).map((selector) => {
38
+ if (selector.startsWith("@") || selector.includes(scopeAttr)) {
39
+ return selector;
40
+ }
41
+ return selector.split(/(\s+|[>+~])/).map((part) => {
42
+ if (!part || /^(\s+|[>+~])$/.test(part)) {
43
+ return part;
44
+ }
45
+ return applyScopeToSegment(part);
46
+ }).join("");
47
+ }).join(", ");
48
+ }
49
+ function scopeCss(css, scopeAttr) {
50
+ const ast = csstree.parse(css, {
51
+ context: "stylesheet",
52
+ positions: false
53
+ });
54
+ csstree.walk(ast, {
55
+ visit: "Rule",
56
+ enter(node) {
57
+ const rule = node;
58
+ if (!rule.prelude || rule.prelude.type !== "SelectorList") {
59
+ return;
60
+ }
61
+ rule.prelude = csstree.parse(
62
+ scopeSelectorList(csstree.generate(rule.prelude), scopeAttr),
63
+ {
64
+ context: "selectorList",
65
+ positions: false
66
+ }
67
+ );
68
+ }
69
+ });
70
+ return csstree.generate(ast);
71
+ }
72
+ function hashScopeSeed(input) {
73
+ let hash = 2166136261;
74
+ for (let index = 0; index < input.length; index += 1) {
75
+ hash ^= input.charCodeAt(index);
76
+ hash = Math.imul(hash, 16777619);
77
+ }
78
+ return Math.abs(hash >>> 0).toString(36);
79
+ }
80
+ function collectCss(blocks, scopeId) {
81
+ const scopeAttr = createScopeAttr(scopeId);
82
+ return blocks.map((block) => {
83
+ const content = block.content.trim();
84
+ if (!content) {
85
+ return "";
86
+ }
87
+ if (!("global" in block.attrs) && scopeAttr) {
88
+ return scopeCss(content, scopeAttr);
89
+ }
90
+ return content;
91
+ }).filter(Boolean).join("\n\n");
92
+ }
93
+
94
+ // compiler/diagnostics.ts
95
+ function indexToLocation(source, index) {
96
+ const safeIndex = Math.max(0, Math.min(index, source.length));
97
+ const lines = source.slice(0, safeIndex).split("\n");
98
+ return {
99
+ line: lines.length,
100
+ column: lines[lines.length - 1]?.length ?? 0,
101
+ index: safeIndex
102
+ };
103
+ }
104
+ function createCodeFrame(source, line, column, contextLines = 2) {
105
+ const lines = source.split("\n");
106
+ const start = Math.max(0, line - 1 - contextLines);
107
+ const end = Math.min(lines.length, line + contextLines);
108
+ return lines.slice(start, end).map((content, offset) => {
109
+ const lineNumber = start + offset + 1;
110
+ const prefix = `${String(lineNumber).padStart(4, " ")} | `;
111
+ if (lineNumber !== line) {
112
+ return `${prefix}${content}`;
113
+ }
114
+ return `${prefix}${content}
115
+ | ${" ".repeat(Math.max(column, 0))}^`;
116
+ }).join("\n");
117
+ }
118
+ function createCompileError(message, source, index, options) {
119
+ const loc = indexToLocation(source, index);
120
+ const error = new Error(message);
121
+ error.loc = { start: loc };
122
+ error.frame = createCodeFrame(source, loc.line, loc.column);
123
+ error.phase = options?.phase;
124
+ error.hint = options?.hint;
125
+ error.diagnostic = {
126
+ message,
127
+ filename: options?.filename,
128
+ phase: options?.phase,
129
+ line: loc.line,
130
+ column: loc.column + 1,
131
+ index: loc.index,
132
+ frame: error.frame,
133
+ hint: options?.hint
134
+ };
135
+ return error;
136
+ }
137
+ function normalizeCompileError(error, source, filename = "component.olova") {
138
+ const compileError = error instanceof Error ? error : new Error(String(error));
139
+ const start = compileError.loc?.start ?? compileError.loc;
140
+ const line = start?.line;
141
+ const column = start?.column;
142
+ const phasePrefix = compileError.phase ? ` [${compileError.phase}]` : "";
143
+ const locationSuffix = line === void 0 || column === void 0 ? "" : ` (${filename}:${line}:${column + 1})`;
144
+ const normalizedMessage = compileError.message.replace(/^\[olova\]\s*/, "");
145
+ compileError.message = `[olova]${phasePrefix} ${normalizedMessage}${locationSuffix}`;
146
+ if (line !== void 0 && column !== void 0 && !compileError.frame) {
147
+ compileError.frame = createCodeFrame(source, line, column);
148
+ }
149
+ compileError.diagnostic = {
150
+ message: normalizedMessage,
151
+ filename,
152
+ phase: compileError.phase,
153
+ line,
154
+ column: column === void 0 ? void 0 : column + 1,
155
+ index: start?.index,
156
+ frame: compileError.frame,
157
+ hint: compileError.hint
158
+ };
159
+ throw compileError;
160
+ }
8
161
 
9
162
  // compiler/parse.ts
10
163
  function isWhitespace(char) {
@@ -177,17 +330,17 @@ function removeRanges(source, ranges) {
177
330
  return output.trim();
178
331
  }
179
332
  function parseOlovaFile(source) {
180
- const scriptBlock = collectTopLevelBlocks(source, "script")[0] ?? null;
333
+ const scriptBlocks = collectTopLevelBlocks(source, "script");
181
334
  const styleBlocks = collectTopLevelBlocks(source, "style");
182
335
  const ranges = styleBlocks.map((block) => ({
183
336
  start: block.fullStart,
184
337
  end: block.closeEnd
185
338
  }));
186
- if (scriptBlock) {
339
+ for (const scriptBlock of scriptBlocks) {
187
340
  ranges.push({ start: scriptBlock.fullStart, end: scriptBlock.closeEnd });
188
341
  }
189
342
  return {
190
- script: scriptBlock?.inner.trim() ?? "",
343
+ script: scriptBlocks.map((block) => block.inner.trim()).filter(Boolean).join("\n\n"),
191
344
  template: removeRanges(source, ranges),
192
345
  styles: styleBlocks.map((block) => ({
193
346
  content: block.inner.trim(),
@@ -195,55 +348,30 @@ function parseOlovaFile(source) {
195
348
  }))
196
349
  };
197
350
  }
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 };
351
+ var VOID_TAGS = /* @__PURE__ */ new Set([
352
+ "area",
353
+ "base",
354
+ "br",
355
+ "col",
356
+ "embed",
357
+ "hr",
358
+ "img",
359
+ "input",
360
+ "link",
361
+ "meta",
362
+ "param",
363
+ "source",
364
+ "track",
365
+ "wbr"
366
+ ]);
367
+ function normalizeNewlines(value) {
368
+ return value.replace(/\r\n?/g, "\n");
241
369
  }
242
- function readBraceValue(source, start) {
370
+ function readBracedValue(source, start) {
243
371
  let index = start + 1;
244
372
  let depth = 1;
245
- let value = "{";
246
373
  let quote = null;
374
+ let value = "{";
247
375
  while (index < source.length && depth > 0) {
248
376
  const char = source[index];
249
377
  const prev = source[index - 1];
@@ -269,158 +397,106 @@ function readBraceValue(source, start) {
269
397
  }
270
398
  return { value, end: index };
271
399
  }
272
- function parseAttributes2(source) {
273
- const attrs = {};
400
+ function protectOlovaAttrExpressions(html) {
401
+ const placeholders = /* @__PURE__ */ new Map();
402
+ let output = "";
274
403
  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) {
404
+ let count = 0;
405
+ while (index < html.length) {
406
+ const char = html[index];
407
+ output += char;
408
+ if (char !== "=" || html[index + 1] !== "{") {
289
409
  index += 1;
290
410
  continue;
291
411
  }
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;
412
+ const braced = readBracedValue(html, index + 1);
413
+ const key = `__OLOVA_ATTR_EXPR_${count++}__`;
414
+ placeholders.set(key, braced.value);
415
+ output += `"${key}"`;
416
+ index = braced.end;
417
+ continue;
418
+ }
419
+ return { html: output, placeholders };
420
+ }
421
+ function normalizeAttrs(attribs) {
422
+ const output = {};
423
+ for (const [name, value] of Object.entries(attribs)) {
424
+ output[name] = value === "" ? true : value;
425
+ }
426
+ return output;
427
+ }
428
+ function isSelfClosingElement(node) {
429
+ return VOID_TAGS.has(node.name.toLowerCase());
430
+ }
431
+ function toAstNode(node) {
432
+ if (node.type === "text") {
433
+ return {
434
+ type: "text",
435
+ content: normalizeNewlines(node.data)
436
+ };
437
+ }
438
+ if (node.type === "comment") {
439
+ return {
440
+ type: "comment",
441
+ content: normalizeNewlines(node.data)
442
+ };
443
+ }
444
+ if (node.type === "tag" || node.type === "script" || node.type === "style") {
445
+ const element = node;
446
+ return {
447
+ type: "element",
448
+ tag: element.name,
449
+ attrs: normalizeAttrs(element.attribs ?? {}),
450
+ children: toAstNodes(element.children ?? []),
451
+ isSelfClosing: isSelfClosingElement(element)
452
+ };
453
+ }
454
+ return null;
455
+ }
456
+ function toAstNodes(nodes) {
457
+ const output = [];
458
+ for (const node of nodes) {
459
+ const normalized = toAstNode(node);
460
+ if (normalized) {
461
+ output.push(normalized);
323
462
  }
324
- attrs[name] = source.slice(start, index);
325
463
  }
326
- return attrs;
464
+ return output;
327
465
  }
328
466
  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;
467
+ const protectedHtml = protectOlovaAttrExpressions(html);
468
+ const document = parseDocument(protectedHtml.html, {
469
+ decodeEntities: false,
470
+ recognizeSelfClosing: true,
471
+ lowerCaseAttributeNames: false,
472
+ lowerCaseTags: false
473
+ });
474
+ const rootNodes = document.children.filter((node) => {
475
+ if (node.type === "directive" || node.type === "cdata") {
476
+ return false;
368
477
  }
369
- if (html.startsWith("</", i)) {
370
- const tagEnd2 = html.indexOf(">", i + 2);
371
- if (tagEnd2 === -1) {
372
- i += 2;
478
+ return true;
479
+ });
480
+ const ast = toAstNodes(rootNodes);
481
+ const restorePlaceholders = (nodes) => {
482
+ for (const node of nodes) {
483
+ if (node.type !== "element") {
373
484
  continue;
374
485
  }
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;
486
+ for (const [name, value] of Object.entries(node.attrs)) {
487
+ if (typeof value !== "string") {
488
+ continue;
489
+ }
490
+ const restored = protectedHtml.placeholders.get(value);
491
+ if (restored) {
492
+ node.attrs[name] = restored;
493
+ }
382
494
  }
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 isNativeVoidTag = tag === tag.toLowerCase() && /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/.test(
407
- tag
408
- );
409
- const isSelfClosing = !!selfClose || isNativeVoidTag;
410
- const element = {
411
- type: "element",
412
- tag,
413
- attrs,
414
- children: [],
415
- isSelfClosing
416
- };
417
- stack[stack.length - 1].children.push(element);
418
- if (!isSelfClosing) {
419
- stack.push({ node: element, children: element.children });
495
+ restorePlaceholders(node.children);
420
496
  }
421
- i = tagEnd + 1;
422
- }
423
- return root;
497
+ };
498
+ restorePlaceholders(ast);
499
+ return ast;
424
500
  }
425
501
 
426
502
  // compiler/dom-generator.ts
@@ -478,7 +554,7 @@ function processNode(node, parentVar, stateNames, transformExpr, counters, build
478
554
  const varName = `_el${buildCtx.localCount++}`;
479
555
  const useSvgNamespace = isSvgContext(tag, inSvg);
480
556
  buildCtx.statements.push(
481
- useSvgNamespace ? `const ${varName} = document.createElementNS(${JSON.stringify(SVG_NAMESPACE)}, "${tag}");` : `const ${varName} = document.createElement("${tag}");`
557
+ useSvgNamespace ? `const ${varName} = document.createElementNS(${JSON.stringify(SVG_NAMESPACE)}, ${JSON.stringify(tag)});` : `const ${varName} = document.createElement(${JSON.stringify(tag)});`
482
558
  );
483
559
  for (const [attr, val] of Object.entries(node.attrs)) {
484
560
  if (attr.startsWith("data-o-on-")) {
@@ -492,10 +568,12 @@ function processNode(node, parentVar, stateNames, transformExpr, counters, build
492
568
  }
493
569
  } else {
494
570
  if (val === true) {
495
- buildCtx.statements.push(`${varName}.setAttribute("${attr}", "");`);
571
+ buildCtx.statements.push(
572
+ `${varName}.setAttribute(${JSON.stringify(attr)}, "");`
573
+ );
496
574
  } else {
497
575
  buildCtx.statements.push(
498
- `${varName}.setAttribute("${attr}", ${JSON.stringify(val)});`
576
+ `${varName}.setAttribute(${JSON.stringify(attr)}, ${JSON.stringify(val)});`
499
577
  );
500
578
  }
501
579
  }
@@ -541,12 +619,14 @@ function generateBuildFunction(html, stateNames, transformExpr, counters, compon
541
619
  }
542
620
 
543
621
  // compiler/compile.ts
544
- var generate = generatorModule.default ?? generatorModule;
622
+ var generate2 = generatorModule.default ?? generatorModule;
545
623
  var traverse = traverseModule.default ?? traverseModule;
546
624
  var CORE_KNOWN_IMPORTS = /* @__PURE__ */ new Set([
547
625
  "Signal",
548
626
  "computed",
549
627
  "createApp",
628
+ "createErrorBoundary",
629
+ "dangerouslySetHtml",
550
630
  "defineComponent",
551
631
  "derived",
552
632
  "effect",
@@ -554,53 +634,20 @@ var CORE_KNOWN_IMPORTS = /* @__PURE__ */ new Set([
554
634
  "global",
555
635
  "hasContext",
556
636
  "mount",
637
+ "onCleanup",
638
+ "onMount",
639
+ "replaceComponent",
557
640
  "setContext",
558
- "state"
641
+ "state",
642
+ "Suspense"
559
643
  ]);
560
644
  function escapeTemplate(source) {
561
645
  return source.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
562
646
  }
563
- function indexToLocation(source, index) {
564
- const safeIndex = Math.max(0, Math.min(index, source.length));
565
- const lines = source.slice(0, safeIndex).split("\n");
566
- return {
567
- line: lines.length,
568
- column: lines[lines.length - 1]?.length ?? 0,
569
- index: safeIndex
570
- };
571
- }
572
- function createCodeFrame(source, line, column, contextLines = 2) {
573
- const lines = source.split("\n");
574
- const start = Math.max(0, line - 1 - contextLines);
575
- const end = Math.min(lines.length, line + contextLines);
576
- return lines.slice(start, end).map((content, offset) => {
577
- const lineNumber = start + offset + 1;
578
- const prefix = `${String(lineNumber).padStart(4, " ")} | `;
579
- if (lineNumber !== line) {
580
- return `${prefix}${content}`;
581
- }
582
- return `${prefix}${content}
583
- | ${" ".repeat(Math.max(column, 0))}^`;
584
- }).join("\n");
585
- }
586
- function createCompileError(message, source, index) {
587
- const loc = indexToLocation(source, index);
588
- const error = new Error(message);
589
- error.loc = { start: loc };
590
- error.frame = createCodeFrame(source, loc.line, loc.column);
591
- return error;
592
- }
593
- function normalizeCompileError(error, source, filename = "component.olova") {
594
- const compileError = error instanceof Error ? error : new Error(String(error));
595
- const start = compileError.loc?.start ?? compileError.loc;
596
- const line = start?.line;
597
- const column = start?.column;
598
- const locationSuffix = line === void 0 || column === void 0 ? "" : ` (${filename}:${line}:${column + 1})`;
599
- compileError.message = `[olova] ${compileError.message.replace(/^\[olova\]\s*/, "")}${locationSuffix}`;
600
- if (line !== void 0 && column !== void 0 && !compileError.frame) {
601
- compileError.frame = createCodeFrame(source, line, column);
647
+ function emitDevWarnings(warnings) {
648
+ for (const warning of warnings) {
649
+ console.warn(warning);
602
650
  }
603
- throw compileError;
604
651
  }
605
652
  function parseModule(code) {
606
653
  return parse(code, {
@@ -608,6 +655,17 @@ function parseModule(code) {
608
655
  plugins: ["typescript", "jsx"]
609
656
  });
610
657
  }
658
+ function runCompilePhase(phase, fn) {
659
+ try {
660
+ return fn();
661
+ } catch (error) {
662
+ const compileError = error instanceof Error ? error : new Error(String(error));
663
+ if (!compileError.phase) {
664
+ compileError.phase = phase;
665
+ }
666
+ throw compileError;
667
+ }
668
+ }
611
669
  function transpileTypeScript(code, filename = "component.ts") {
612
670
  const result = ts.transpileModule(code, {
613
671
  fileName: filename,
@@ -627,20 +685,22 @@ function transpileTypeScript(code, filename = "component.ts") {
627
685
  };
628
686
  }
629
687
  function compileModuleScript(script, filename = "module.olova.ts") {
630
- const ast = parseModule(script || "");
631
- return transpileTypeScript(
632
- ast.program.body.map((node) => generate(node).code).join("\n"),
633
- filename
634
- );
688
+ return runCompilePhase("analyze-script", () => {
689
+ const scriptInfo = collectScriptInfo(script);
690
+ return runCompilePhase(
691
+ "transpile",
692
+ () => transpileTypeScript(
693
+ [scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
694
+ filename
695
+ )
696
+ );
697
+ });
635
698
  }
636
699
  function isModuleOnlyScript(script) {
637
700
  if (!script.trim()) {
638
701
  return false;
639
702
  }
640
- const ast = parseModule(script);
641
- return ast.program.body.every(
642
- (node) => t.isImportDeclaration(node) || t.isExportNamedDeclaration(node) || t.isExportAllDeclaration(node) || t.isExportDefaultDeclaration(node)
643
- );
703
+ return true;
644
704
  }
645
705
  function isStateInit(node) {
646
706
  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")));
@@ -966,7 +1026,7 @@ function collectScriptInfo(script) {
966
1026
  return false;
967
1027
  }
968
1028
  let found = false;
969
- traverse(parseModule(`(${generate(node).code})`), {
1029
+ traverse(parseModule(`(${generate2(node).code})`), {
970
1030
  JSXElement() {
971
1031
  found = true;
972
1032
  },
@@ -991,7 +1051,7 @@ function collectScriptInfo(script) {
991
1051
  traverse(ast, {
992
1052
  JSXElement(path) {
993
1053
  const transformed = transformExpression(
994
- generate(path.node).code,
1054
+ generate2(path.node).code,
995
1055
  stateNames,
996
1056
  allSignalNames,
997
1057
  jsxFunctionNames
@@ -1005,7 +1065,7 @@ function collectScriptInfo(script) {
1005
1065
  },
1006
1066
  JSXFragment(path) {
1007
1067
  const transformed = transformExpression(
1008
- generate(path.node).code,
1068
+ generate2(path.node).code,
1009
1069
  stateNames,
1010
1070
  allSignalNames,
1011
1071
  jsxFunctionNames
@@ -1037,8 +1097,8 @@ function collectScriptInfo(script) {
1037
1097
  const bodyNodes = ast.program.body.filter(
1038
1098
  (node) => !t.isImportDeclaration(node)
1039
1099
  );
1040
- const importsCode = importNodes.map((node) => generate(node).code).join("\n");
1041
- const scriptBodyCode = bodyNodes.map((node) => generate(node).code).join("\n");
1100
+ const importsCode = importNodes.map((node) => generate2(node).code).join("\n");
1101
+ const scriptBodyCode = bodyNodes.map((node) => generate2(node).code).join("\n");
1042
1102
  return {
1043
1103
  importsCode,
1044
1104
  scriptBodyCode,
@@ -1112,7 +1172,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
1112
1172
  const escapeCall = (input) => t.callExpression(t.identifier("__olovaEscape"), [input]);
1113
1173
  const expressionContainsJsx = (input) => {
1114
1174
  let found = false;
1115
- traverse(parseModule(`(${generate(input).code})`), {
1175
+ traverse(parseModule(`(${generate2(input).code})`), {
1116
1176
  JSXElement() {
1117
1177
  found = true;
1118
1178
  },
@@ -1130,7 +1190,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
1130
1190
  return true;
1131
1191
  }
1132
1192
  let found = false;
1133
- traverse(parseModule(`(${generate(input).code})`), {
1193
+ traverse(parseModule(`(${generate2(input).code})`), {
1134
1194
  CallExpression(path) {
1135
1195
  if (t.isIdentifier(path.node.callee) && jsxFunctionNames.has(path.node.callee.name)) {
1136
1196
  found = true;
@@ -1185,7 +1245,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
1185
1245
  return walkMember(name);
1186
1246
  };
1187
1247
  const transformEmbeddedJsxExpression = (input) => {
1188
- const wrapped = parseModule(`(${generate(input).code})`);
1248
+ const wrapped = parseModule(`(${generate2(input).code})`);
1189
1249
  let containsNestedJsx = false;
1190
1250
  traverse(wrapped, {
1191
1251
  JSXElement(path) {
@@ -1372,7 +1432,7 @@ function transformExpression(expr, stateNames, allSignalNames, jsxFunctionNames
1372
1432
  return { code: expr.trim(), containsJsx };
1373
1433
  }
1374
1434
  containsJsx = containsJsx || expressionProducesHtml(statement.expression);
1375
- return { code: generate(statement.expression).code, containsJsx };
1435
+ return { code: generate2(statement.expression).code, containsJsx };
1376
1436
  }
1377
1437
  function jsxToTemplateHtml(node) {
1378
1438
  if (t.isJSXElement(node)) {
@@ -1381,15 +1441,15 @@ function jsxToTemplateHtml(node) {
1381
1441
  if (t.isJSXIdentifier(opening.name)) {
1382
1442
  tag = opening.name.name;
1383
1443
  } else if (t.isJSXMemberExpression(opening.name)) {
1384
- const walk = (n) => t.isJSXIdentifier(n) ? n.name : `${walk(n.object)}.${n.property.name}`;
1385
- tag = walk(opening.name);
1444
+ const walk2 = (n) => t.isJSXIdentifier(n) ? n.name : `${walk2(n.object)}.${n.property.name}`;
1445
+ tag = walk2(opening.name);
1386
1446
  } else if (t.isJSXNamespacedName(opening.name)) {
1387
1447
  tag = `${opening.name.namespace.name}:${opening.name.name.name}`;
1388
1448
  }
1389
1449
  let attrsStr = "";
1390
1450
  for (const attr of opening.attributes) {
1391
1451
  if (t.isJSXSpreadAttribute(attr)) {
1392
- attrsStr += ` {...${generate(attr.argument).code}}`;
1452
+ attrsStr += ` {...${generate2(attr.argument).code}}`;
1393
1453
  } else {
1394
1454
  let attrName = "";
1395
1455
  if (t.isJSXIdentifier(attr.name)) attrName = attr.name.name;
@@ -1399,7 +1459,7 @@ function jsxToTemplateHtml(node) {
1399
1459
  attrsStr += ` ${attrName}="${attr.value.value}"`;
1400
1460
  else if (t.isJSXExpressionContainer(attr.value)) {
1401
1461
  if (!t.isJSXEmptyExpression(attr.value.expression)) {
1402
- attrsStr += ` ${attrName}={${generate(attr.value.expression).code}}`;
1462
+ attrsStr += ` ${attrName}={${generate2(attr.value.expression).code}}`;
1403
1463
  }
1404
1464
  }
1405
1465
  }
@@ -1415,7 +1475,7 @@ function jsxToTemplateHtml(node) {
1415
1475
  }
1416
1476
  if (t.isJSXExpressionContainer(node)) {
1417
1477
  if (t.isJSXEmptyExpression(node.expression)) return "";
1418
- return `{${generate(node.expression).code}}`;
1478
+ return `{${generate2(node.expression).code}}`;
1419
1479
  }
1420
1480
  if (t.isJSXFragment(node)) {
1421
1481
  return node.children.map(jsxToTemplateHtml).join("");
@@ -1425,56 +1485,78 @@ function jsxToTemplateHtml(node) {
1425
1485
  function replaceTemplateExpressions(input, replacer) {
1426
1486
  let output = "";
1427
1487
  let index = 0;
1488
+ let inTag = false;
1489
+ let tagQuote = null;
1490
+ let tagBraceDepth = 0;
1428
1491
  while (index < input.length) {
1429
- const start = input.indexOf("{", index);
1430
- if (start < 0) {
1431
- output += input.slice(index);
1432
- break;
1492
+ const char = input[index];
1493
+ const prev = index > 0 ? input[index - 1] : "";
1494
+ if (inTag) {
1495
+ output += char;
1496
+ if (tagQuote) {
1497
+ if (char === tagQuote && prev !== "\\") {
1498
+ tagQuote = null;
1499
+ }
1500
+ index += 1;
1501
+ continue;
1502
+ }
1503
+ if (char === "'" || char === '"' || char === "`") {
1504
+ tagQuote = char;
1505
+ } else if (char === "{") {
1506
+ tagBraceDepth += 1;
1507
+ } else if (char === "}" && tagBraceDepth > 0) {
1508
+ tagBraceDepth -= 1;
1509
+ } else if (char === ">" && tagBraceDepth === 0) {
1510
+ inTag = false;
1511
+ }
1512
+ index += 1;
1513
+ continue;
1433
1514
  }
1434
- let prevIndex = start - 1;
1435
- while (prevIndex >= 0 && /\s/.test(input[prevIndex])) {
1436
- prevIndex -= 1;
1515
+ if (char === "<") {
1516
+ inTag = true;
1517
+ output += char;
1518
+ index += 1;
1519
+ continue;
1437
1520
  }
1438
- if (prevIndex >= 0 && input[prevIndex] === "=") {
1439
- output += input.slice(index, start + 1);
1440
- index = start + 1;
1521
+ if (char !== "{") {
1522
+ output += char;
1523
+ index += 1;
1441
1524
  continue;
1442
1525
  }
1443
- if (start > 0 && input[start - 1] === "$") {
1444
- output += input.slice(index, start + 1);
1445
- index = start + 1;
1526
+ if (prev === "$") {
1527
+ output += char;
1528
+ index += 1;
1446
1529
  continue;
1447
1530
  }
1448
- output += input.slice(index, start);
1449
1531
  let depth = 1;
1450
- let cursor = start + 1;
1532
+ let cursor = index + 1;
1451
1533
  let quote = null;
1452
1534
  while (cursor < input.length && depth > 0) {
1453
- const char = input[cursor];
1454
- const prev = input[cursor - 1];
1535
+ const char2 = input[cursor];
1536
+ const prev2 = input[cursor - 1];
1455
1537
  if (quote) {
1456
- if (char === quote && prev !== "\\") {
1538
+ if (char2 === quote && prev2 !== "\\") {
1457
1539
  quote = null;
1458
1540
  }
1459
1541
  cursor += 1;
1460
1542
  continue;
1461
1543
  }
1462
- if (char === "'" || char === '"' || char === "`") {
1463
- quote = char;
1544
+ if (char2 === "'" || char2 === '"' || char2 === "`") {
1545
+ quote = char2;
1464
1546
  cursor += 1;
1465
1547
  continue;
1466
1548
  }
1467
- if (char === "{") {
1549
+ if (char2 === "{") {
1468
1550
  depth += 1;
1469
- } else if (char === "}") {
1551
+ } else if (char2 === "}") {
1470
1552
  depth -= 1;
1471
1553
  }
1472
1554
  cursor += 1;
1473
1555
  }
1474
1556
  if (depth !== 0) {
1475
- throw createCompileError("[olova] Unclosed template expression block.", input, start);
1557
+ throw createCompileError("[olova] Unclosed template expression block.", input, index);
1476
1558
  }
1477
- const expression = input.slice(start + 1, cursor - 1);
1559
+ const expression = input.slice(index + 1, cursor - 1);
1478
1560
  output += replacer(expression);
1479
1561
  index = cursor;
1480
1562
  }
@@ -1508,7 +1590,7 @@ function parsePropsFromAttrs(attrs, stateNames, allSignalNames, jsxFunctionNames
1508
1590
  }
1509
1591
  return props.length === 0 ? "{}" : `{ ${props.join(", ")} }`;
1510
1592
  }
1511
- function transformTemplate(template, stateNames, allSignalNames, jsxFunctionNames, componentNames, counters, scopeId) {
1593
+ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionNames, componentNames, counters, scopeId, devWarnings, filename = "component.olova") {
1512
1594
  let html = template;
1513
1595
  const textBindings = [];
1514
1596
  const htmlBindings = [];
@@ -1525,7 +1607,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1525
1607
  const node = exprNode.expression;
1526
1608
  const containsJsxNode = (n) => {
1527
1609
  let found = false;
1528
- traverse(parseModule(`(${generate(n).code})`), {
1610
+ traverse(parseModule(`(${generate2(n).code})`), {
1529
1611
  JSXElement() {
1530
1612
  found = true;
1531
1613
  },
@@ -1538,7 +1620,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1538
1620
  if (t.isLogicalExpression(node) && node.operator === "&&") {
1539
1621
  if (containsJsxNode(node.right)) {
1540
1622
  const id = `i${counters.if++}`;
1541
- const condition = generate(node.left).code;
1623
+ const condition = generate2(node.left).code;
1542
1624
  const trueBranchHtml = jsxToTemplateHtml(node.right);
1543
1625
  ifBindings.push({
1544
1626
  id,
@@ -1556,7 +1638,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1556
1638
  jsxFunctionNames,
1557
1639
  componentNames,
1558
1640
  counters,
1559
- scopeId
1641
+ scopeId,
1642
+ devWarnings,
1643
+ filename
1560
1644
  )
1561
1645
  });
1562
1646
  return `__O_IF_${id}__`;
@@ -1564,9 +1648,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1564
1648
  } else if (t.isConditionalExpression(node)) {
1565
1649
  if (containsJsxNode(node.consequent) || containsJsxNode(node.alternate)) {
1566
1650
  const id = `i${counters.if++}`;
1567
- const condition = generate(node.test).code;
1568
- const trueBranchHtml = containsJsxNode(node.consequent) ? jsxToTemplateHtml(node.consequent) : `{${generate(node.consequent).code}}`;
1569
- const falseBranchHtml = containsJsxNode(node.alternate) ? jsxToTemplateHtml(node.alternate) : `{${generate(node.alternate).code}}`;
1651
+ const condition = generate2(node.test).code;
1652
+ const trueBranchHtml = containsJsxNode(node.consequent) ? jsxToTemplateHtml(node.consequent) : `{${generate2(node.consequent).code}}`;
1653
+ const falseBranchHtml = containsJsxNode(node.alternate) ? jsxToTemplateHtml(node.alternate) : `{${generate2(node.alternate).code}}`;
1570
1654
  ifBindings.push({
1571
1655
  id,
1572
1656
  expr: transformExpression(
@@ -1583,7 +1667,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1583
1667
  jsxFunctionNames,
1584
1668
  componentNames,
1585
1669
  counters,
1586
- scopeId
1670
+ scopeId,
1671
+ devWarnings,
1672
+ filename
1587
1673
  ),
1588
1674
  falseBranch: transformTemplate(
1589
1675
  falseBranchHtml,
@@ -1592,7 +1678,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1592
1678
  jsxFunctionNames,
1593
1679
  componentNames,
1594
1680
  counters,
1595
- scopeId
1681
+ scopeId,
1682
+ devWarnings,
1683
+ filename
1596
1684
  )
1597
1685
  });
1598
1686
  return `__O_IF_${id}__`;
@@ -1647,6 +1735,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1647
1735
  continue;
1648
1736
  }
1649
1737
  const normalizedAttrName = attrName === "className" ? "class" : attrName;
1738
+ const lowerAttrName = normalizedAttrName.toLowerCase();
1650
1739
  if (rawValue === true) {
1651
1740
  output += ` ${normalizedAttrName}`;
1652
1741
  continue;
@@ -1679,6 +1768,16 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1679
1768
  output += ` ${normalizedAttrName}="__O_ATTR_${id}__"`;
1680
1769
  continue;
1681
1770
  }
1771
+ if (devWarnings && lowerAttrName.startsWith("on")) {
1772
+ devWarnings.add(
1773
+ `[olova] Avoid static inline event attributes like "${normalizedAttrName}" in templates (${filename}). Prefer ${normalizedAttrName}={handler}.`
1774
+ );
1775
+ }
1776
+ if (devWarnings && (lowerAttrName === "href" || lowerAttrName === "src" || lowerAttrName === "xlink:href") && /^\s*javascript:/i.test(rawValue)) {
1777
+ devWarnings.add(
1778
+ `[olova] Suspicious javascript: URL found in static "${normalizedAttrName}" attribute (${filename}).`
1779
+ );
1780
+ }
1682
1781
  output += ` ${normalizedAttrName}="${rawValue.replace(/"/g, "&quot;")}"`;
1683
1782
  }
1684
1783
  return output;
@@ -1727,6 +1826,73 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1727
1826
  defaultNodes.push(child);
1728
1827
  }
1729
1828
  const slotFactories = [];
1829
+ if (node.tag === "Suspense" && typeof node.attrs.fallback === "string") {
1830
+ const rawValue = node.attrs.fallback;
1831
+ if (rawValue.startsWith("{") && rawValue.endsWith("}")) {
1832
+ const fallbackExpression = rawValue.slice(1, -1).trim();
1833
+ const fallbackAst = parseModule(`(${fallbackExpression})`);
1834
+ const fallbackStatement = fallbackAst.program.body[0];
1835
+ const fallbackNode = fallbackStatement && t.isExpressionStatement(fallbackStatement) ? fallbackStatement.expression : null;
1836
+ if (fallbackNode && (t.isJSXElement(fallbackNode) || t.isJSXFragment(fallbackNode))) {
1837
+ slotFactories.push({
1838
+ varName: `__slot_${id}_fallback`,
1839
+ name: "fallback",
1840
+ descriptor: transformTemplate(
1841
+ jsxToTemplateHtml(fallbackNode),
1842
+ stateNames,
1843
+ allSignalNames,
1844
+ jsxFunctionNames,
1845
+ componentNames,
1846
+ counters,
1847
+ scopeId,
1848
+ devWarnings,
1849
+ filename
1850
+ )
1851
+ });
1852
+ } else {
1853
+ const transformedFallback = transformExpression(
1854
+ fallbackExpression,
1855
+ stateNames,
1856
+ allSignalNames,
1857
+ jsxFunctionNames,
1858
+ scopeId
1859
+ );
1860
+ const fallbackBindingId = `fallback_${id}`;
1861
+ slotFactories.push({
1862
+ varName: `__slot_${id}_fallback`,
1863
+ name: "fallback",
1864
+ descriptor: {
1865
+ html: `__O_HTML_${fallbackBindingId}__`,
1866
+ textBindings: [],
1867
+ htmlBindings: [
1868
+ {
1869
+ id: fallbackBindingId,
1870
+ expr: transformedFallback.containsJsx ? `__olovaDangerouslySetHtml(${transformedFallback.code})` : transformedFallback.code
1871
+ }
1872
+ ],
1873
+ attrBindings: [],
1874
+ eventBindings: [],
1875
+ slotBindings: [],
1876
+ componentBindings: [],
1877
+ ifBindings: [],
1878
+ buildFnStr: generateBuildFunction(
1879
+ `__O_HTML_${fallbackBindingId}__`,
1880
+ stateNames,
1881
+ (expr) => transformExpression(
1882
+ expr,
1883
+ stateNames,
1884
+ allSignalNames,
1885
+ jsxFunctionNames,
1886
+ scopeId
1887
+ ),
1888
+ counters,
1889
+ componentNames
1890
+ )
1891
+ }
1892
+ });
1893
+ }
1894
+ }
1895
+ }
1730
1896
  if (defaultNodes.length > 0) {
1731
1897
  slotFactories.push({
1732
1898
  varName: `__slot_${id}_default`,
@@ -1738,7 +1904,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1738
1904
  jsxFunctionNames,
1739
1905
  componentNames,
1740
1906
  counters,
1741
- scopeId
1907
+ scopeId,
1908
+ devWarnings,
1909
+ filename
1742
1910
  )
1743
1911
  });
1744
1912
  }
@@ -1761,7 +1929,9 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1761
1929
  id,
1762
1930
  component: node.tag,
1763
1931
  propsExpr: parsePropsFromAttrs(
1764
- node.attrs,
1932
+ node.tag === "Suspense" ? Object.fromEntries(
1933
+ Object.entries(node.attrs).filter(([key]) => key !== "fallback")
1934
+ ) : node.attrs,
1765
1935
  stateNames,
1766
1936
  allSignalNames,
1767
1937
  jsxFunctionNames
@@ -1786,7 +1956,7 @@ function transformTemplate(template, stateNames, allSignalNames, jsxFunctionName
1786
1956
  scopeId
1787
1957
  );
1788
1958
  const buildFnStr = generateBuildFunction(
1789
- escapeTemplate(html),
1959
+ html,
1790
1960
  stateNames,
1791
1961
  resolveExpr,
1792
1962
  counters,
@@ -1869,14 +2039,20 @@ function buildComponentCode(parsed, options) {
1869
2039
  slot: 0,
1870
2040
  if: 0
1871
2041
  };
1872
- const transformed = transformTemplate(
1873
- parsed.template,
1874
- scriptInfo.stateNames,
1875
- scriptInfo.allSignalNames,
1876
- scriptInfo.jsxFunctionNames,
1877
- scriptInfo.componentNames,
1878
- counters,
1879
- options?.scopeId
2042
+ const warnings = options?.dev ? /* @__PURE__ */ new Set() : void 0;
2043
+ const transformed = runCompilePhase(
2044
+ "transform-template",
2045
+ () => transformTemplate(
2046
+ parsed.template,
2047
+ scriptInfo.stateNames,
2048
+ scriptInfo.allSignalNames,
2049
+ scriptInfo.jsxFunctionNames,
2050
+ scriptInfo.componentNames,
2051
+ counters,
2052
+ options?.scopeId,
2053
+ warnings,
2054
+ options?.hmrId ?? "component.olova"
2055
+ )
1880
2056
  );
1881
2057
  const exportHead = options?.exportName ? `export const ${options.exportName} = __defineComponent((__props, __slots) => {` : `const __olovaComponent = __defineComponent((__props, __slots) => {`;
1882
2058
  const exportTail = options?.exportName ? `}, ${JSON.stringify(options?.hmrId)});` : `}, ${JSON.stringify(options?.hmrId)});
@@ -1890,6 +2066,9 @@ if (import.meta.hot) {
1890
2066
  });
1891
2067
  }
1892
2068
  ` : "";
2069
+ if (warnings) {
2070
+ emitDevWarnings(warnings);
2071
+ }
1893
2072
  const code = `
1894
2073
  ${exportHead}
1895
2074
  const props = __props;
@@ -2003,143 +2182,110 @@ ${hmrCode}
2003
2182
  `;
2004
2183
  return { importsCode: scriptInfo.importsCode, code };
2005
2184
  }
2006
- function hashScopeSeed(input) {
2007
- let hash = 2166136261;
2008
- for (let index = 0; index < input.length; index += 1) {
2009
- hash ^= input.charCodeAt(index);
2010
- hash = Math.imul(hash, 16777619);
2011
- }
2012
- return Math.abs(hash >>> 0).toString(36);
2013
- }
2014
- function createScopeAttr(scopeId) {
2015
- return scopeId ? `data-o-scope-${scopeId}` : null;
2016
- }
2017
- function scopeSelectorList(selectorList, scopeAttr) {
2018
- const applyScopeToSegment = (segment) => {
2019
- const trimmed = segment.trim();
2020
- if (!trimmed) {
2021
- return trimmed;
2022
- }
2023
- if (trimmed.includes(scopeAttr)) {
2024
- return trimmed;
2025
- }
2026
- const globalPattern = /:global\(([^()]+)\)/g;
2027
- const localCandidate = trimmed.replace(globalPattern, "").trim();
2028
- const normalized = trimmed.replace(globalPattern, "$1");
2029
- if (!localCandidate) {
2030
- return normalized;
2031
- }
2032
- if (normalized === ":root") {
2033
- return `[${scopeAttr}]`;
2034
- }
2035
- const pseudoIndex = normalized.search(/(?<!:):(?!:)/);
2036
- if (pseudoIndex === -1) {
2037
- return `${normalized}[${scopeAttr}]`;
2038
- }
2039
- return `${normalized.slice(0, pseudoIndex)}[${scopeAttr}]${normalized.slice(pseudoIndex)}`;
2040
- };
2041
- return selectorList.split(",").map((selector) => selector.trim()).filter(Boolean).map((selector) => {
2042
- if (selector.startsWith("@") || selector.includes(scopeAttr)) {
2043
- return selector;
2044
- }
2045
- return selector.split(/(\s+|[>+~])/).map((part) => {
2046
- if (!part || /^(\s+|[>+~])$/.test(part)) {
2047
- return part;
2048
- }
2049
- return applyScopeToSegment(part);
2050
- }).join("");
2051
- }).join(", ");
2052
- }
2053
- function scopeCss(css, scopeAttr) {
2054
- let output = "";
2055
- let index = 0;
2056
- while (index < css.length) {
2057
- const open = css.indexOf("{", index);
2058
- if (open < 0) {
2059
- output += css.slice(index);
2060
- break;
2061
- }
2062
- const selector = css.slice(index, open).trim();
2063
- let depth = 1;
2064
- let cursor = open + 1;
2065
- while (cursor < css.length && depth > 0) {
2066
- if (css[cursor] === "{") {
2067
- depth += 1;
2068
- } else if (css[cursor] === "}") {
2069
- depth -= 1;
2070
- }
2071
- cursor += 1;
2072
- }
2073
- const body = css.slice(open + 1, cursor - 1);
2074
- if (/^@(?:media|supports|container|layer)\b/i.test(selector)) {
2075
- output += `${selector} {${scopeCss(body, scopeAttr)}}`;
2076
- } else if (/^@(?:keyframes|-webkit-keyframes)\b/i.test(selector)) {
2077
- output += `${selector} {${body}}`;
2078
- } else {
2079
- output += `${scopeSelectorList(selector, scopeAttr)} {${body}}`;
2080
- }
2081
- index = cursor;
2185
+ function analyzeOlovaPhases(source, options) {
2186
+ const filename = options?.filename ?? "component.olova";
2187
+ try {
2188
+ const parsed = runCompilePhase(
2189
+ "parse-file",
2190
+ () => parseOlovaFile(source)
2191
+ );
2192
+ const script = runCompilePhase(
2193
+ "analyze-script",
2194
+ () => collectScriptInfo(parsed.script)
2195
+ );
2196
+ const scopeId = parsed.styles.some((style) => !("global" in style.attrs)) ? hashScopeSeed(`${filename}:${options?.exportName ?? "default"}`) : void 0;
2197
+ const template = runCompilePhase(
2198
+ "transform-template",
2199
+ () => transformTemplate(
2200
+ parsed.template,
2201
+ script.stateNames,
2202
+ script.allSignalNames,
2203
+ script.jsxFunctionNames,
2204
+ script.componentNames,
2205
+ {
2206
+ text: 0,
2207
+ attr: 0,
2208
+ event: 0,
2209
+ comp: 0,
2210
+ slot: 0,
2211
+ if: 0
2212
+ },
2213
+ scopeId,
2214
+ options?.dev ? /* @__PURE__ */ new Set() : void 0,
2215
+ filename
2216
+ )
2217
+ );
2218
+ return {
2219
+ parsed,
2220
+ script,
2221
+ template,
2222
+ scopeId
2223
+ };
2224
+ } catch (error) {
2225
+ return normalizeCompileError(error, source, filename);
2082
2226
  }
2083
- return output;
2084
- }
2085
- function collectCss(blocks, scopeId) {
2086
- const scopeAttr = createScopeAttr(scopeId);
2087
- return blocks.map((block) => {
2088
- const content = block.content.trim();
2089
- if (!content) {
2090
- return "";
2091
- }
2092
- if (!("global" in block.attrs) && scopeAttr) {
2093
- return scopeCss(content, scopeAttr);
2094
- }
2095
- return content;
2096
- }).filter(Boolean).join("\n\n");
2097
2227
  }
2098
2228
  function compileOlovaScriptModule(source, options) {
2099
2229
  try {
2100
- const scriptInfo = collectScriptInfo(source);
2101
- return transpileTypeScript(
2102
- [scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
2103
- options?.filename ?? "module.ts"
2104
- );
2230
+ return runCompilePhase("analyze-script", () => {
2231
+ const scriptInfo = collectScriptInfo(source);
2232
+ return runCompilePhase(
2233
+ "transpile",
2234
+ () => transpileTypeScript(
2235
+ [scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
2236
+ options?.filename ?? "module.ts"
2237
+ )
2238
+ );
2239
+ });
2105
2240
  } catch (error) {
2106
2241
  return normalizeCompileError(error, source, options?.filename ?? "module.ts");
2107
2242
  }
2108
2243
  }
2109
2244
  function compileOlova(source, options) {
2245
+ const filename = options?.filename ?? "component.olova";
2110
2246
  try {
2111
- const parsed = parseOlovaFile(source);
2112
- if (!parsed.template.trim() && isModuleOnlyScript(parsed.script)) {
2113
- return {
2114
- ...compileModuleScript(parsed.script, options?.filename ?? "module.olova.ts"),
2115
- css: collectCss(parsed.styles)
2116
- };
2117
- }
2118
- const scopeId = parsed.styles.some((style) => !("global" in style.attrs)) ? hashScopeSeed(`${options?.filename ?? "component.olova"}:${options?.exportName ?? "default"}`) : void 0;
2119
- const compiled = buildComponentCode(parsed, {
2120
- scopeId,
2121
- hmrId: options?.filename ?? "component.olova",
2122
- dev: options?.dev
2123
- });
2124
- const runtimeImport = options?.dev ? `import { defineComponent as __defineComponent, replaceComponent as __replaceComponent } from 'olova/runtime';` : `import { defineComponent as __defineComponent } from 'olova/runtime';`;
2125
- const result = transpileTypeScript(`
2247
+ return runCompilePhase("parse-file", () => {
2248
+ const parsed = parseOlovaFile(source);
2249
+ if (!parsed.template.trim() && isModuleOnlyScript(parsed.script)) {
2250
+ const moduleResult = compileModuleScript(parsed.script, `${filename}.ts`);
2251
+ const css2 = runCompilePhase(
2252
+ "generate-module",
2253
+ () => collectCss(parsed.styles)
2254
+ );
2255
+ return {
2256
+ ...moduleResult,
2257
+ css: css2
2258
+ };
2259
+ }
2260
+ const scopeId = parsed.styles.some((style) => !("global" in style.attrs)) ? hashScopeSeed(`${filename}:${options?.exportName ?? "default"}`) : void 0;
2261
+ const compiled = runCompilePhase(
2262
+ "generate-module",
2263
+ () => buildComponentCode(parsed, {
2264
+ scopeId,
2265
+ hmrId: filename,
2266
+ dev: options?.dev
2267
+ })
2268
+ );
2269
+ const runtimeImport = options?.dev ? `import { defineComponent as __defineComponent, replaceComponent as __replaceComponent } from 'olova/runtime';` : `import { defineComponent as __defineComponent } from 'olova/runtime';`;
2270
+ const result = runCompilePhase("transpile", () => transpileTypeScript(`
2126
2271
  ${runtimeImport}
2127
2272
  ${compiled.importsCode}
2128
2273
  ${compiled.code}
2129
- `, options?.filename ?? "component.olova.ts");
2130
- return {
2131
- ...result,
2132
- css: collectCss(parsed.styles, scopeId)
2133
- };
2274
+ `, `${filename}.ts`));
2275
+ const css = runCompilePhase(
2276
+ "generate-module",
2277
+ () => collectCss(parsed.styles, scopeId)
2278
+ );
2279
+ return {
2280
+ ...result,
2281
+ css
2282
+ };
2283
+ });
2134
2284
  } catch (error) {
2135
- return normalizeCompileError(
2136
- error,
2137
- source,
2138
- options?.filename ?? "component.olova"
2139
- );
2285
+ return normalizeCompileError(error, source, filename);
2140
2286
  }
2141
2287
  }
2142
2288
 
2143
- export { compileOlova, compileOlovaScriptModule, generateBuildFunction, parseHTML };
2289
+ export { analyzeOlovaPhases, compileOlova, compileOlovaScriptModule, generateBuildFunction, parseHTML };
2144
2290
  //# sourceMappingURL=compiler.js.map
2145
2291
  //# sourceMappingURL=compiler.js.map