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/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 scriptBlock = collectTopLevelBlocks(source, "script")[0] ?? null;
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
- if (scriptBlock) {
343
+ for (const scriptBlock of scriptBlocks) {
191
344
  ranges.push({ start: scriptBlock.fullStart, end: scriptBlock.closeEnd });
192
345
  }
193
346
  return {
194
- script: scriptBlock?.inner.trim() ?? "",
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
- // compiler/html-parser.ts
204
- function isWhitespace2(char) {
205
- return /\s/.test(char);
206
- }
207
- function findTagEnd2(source, start) {
208
- let index = start;
209
- while (index < source.length) {
210
- const char = source[index];
211
- if (char === '"' || char === "'") {
212
- const quoted = readQuotedValue(source, index, char);
213
- index = quoted.end;
214
- continue;
215
- }
216
- if (char === "{") {
217
- const braced = readBraceValue(source, index);
218
- index = braced.end;
219
- continue;
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 readBraceValue(source, start) {
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 parseAttributes2(source) {
277
- const attrs = {};
404
+ function protectOlovaAttrExpressions(html) {
405
+ const placeholders = /* @__PURE__ */ new Map();
406
+ let output = "";
278
407
  let index = 0;
279
- while (index < source.length) {
280
- while (index < source.length && isWhitespace2(source[index])) {
281
- index += 1;
282
- }
283
- if (index >= source.length) {
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
- while (index < source.length && !isWhitespace2(source[index])) {
326
- index += 1;
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 attrs;
468
+ return output;
331
469
  }
332
470
  function parseHTML(html) {
333
- const root = [];
334
- const stack = [
335
- { node: null, children: root }
336
- ];
337
- let i = 0;
338
- while (i < html.length) {
339
- const textStart = i;
340
- const tagStart = html.indexOf("<", i);
341
- if (tagStart === -1) {
342
- if (textStart < html.length) {
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
- if (html.startsWith("</", i)) {
374
- const tagEnd2 = html.indexOf(">", i + 2);
375
- if (tagEnd2 === -1) {
376
- i += 2;
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 tag2 = html.slice(i + 2, tagEnd2).trim().toLowerCase();
380
- let closeIdx = stack.length - 1;
381
- while (closeIdx > 0 && stack[closeIdx].node?.tag.toLowerCase() !== tag2) {
382
- closeIdx--;
383
- }
384
- if (closeIdx > 0) {
385
- stack.length = closeIdx;
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
- i = tagEnd2 + 1;
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
- i = tagEnd + 1;
426
- }
427
- return root;
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)}, "${tag}");` : `const ${varName} = document.createElement("${tag}");`
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(`${varName}.setAttribute("${attr}", "");`);
575
+ buildCtx.statements.push(
576
+ `${varName}.setAttribute(${JSON.stringify(attr)}, "");`
577
+ );
500
578
  } else {
501
579
  buildCtx.statements.push(
502
- `${varName}.setAttribute("${attr}", ${JSON.stringify(val)});`
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 generate = generatorModule.default ?? generatorModule;
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 indexToLocation(source, index) {
568
- const safeIndex = Math.max(0, Math.min(index, source.length));
569
- const lines = source.slice(0, safeIndex).split("\n");
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
- const ast = parseModule(script || "");
635
- return transpileTypeScript(
636
- ast.program.body.map((node) => generate(node).code).join("\n"),
637
- filename
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
- const ast = parseModule(script);
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(`(${generate(node).code})`), {
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
- generate(path2.node).code,
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
- generate(path2.node).code,
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) => generate(node).code).join("\n");
1045
- const scriptBodyCode = bodyNodes.map((node) => generate(node).code).join("\n");
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(`(${generate(input).code})`), {
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(`(${generate(input).code})`), {
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(`(${generate(input).code})`);
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: generate(statement.expression).code, containsJsx };
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 walk = (n) => t.isJSXIdentifier(n) ? n.name : `${walk(n.object)}.${n.property.name}`;
1389
- tag = walk(opening.name);
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 += ` {...${generate(attr.argument).code}}`;
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}={${generate(attr.value.expression).code}}`;
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 `{${generate(node.expression).code}}`;
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 start = input.indexOf("{", index);
1434
- if (start < 0) {
1435
- output += input.slice(index);
1436
- break;
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
- let prevIndex = start - 1;
1439
- while (prevIndex >= 0 && /\s/.test(input[prevIndex])) {
1440
- prevIndex -= 1;
1519
+ if (char === "<") {
1520
+ inTag = true;
1521
+ output += char;
1522
+ index += 1;
1523
+ continue;
1441
1524
  }
1442
- if (prevIndex >= 0 && input[prevIndex] === "=") {
1443
- output += input.slice(index, start + 1);
1444
- index = start + 1;
1525
+ if (char !== "{") {
1526
+ output += char;
1527
+ index += 1;
1445
1528
  continue;
1446
1529
  }
1447
- if (start > 0 && input[start - 1] === "$") {
1448
- output += input.slice(index, start + 1);
1449
- index = start + 1;
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 = start + 1;
1536
+ let cursor = index + 1;
1455
1537
  let quote = null;
1456
1538
  while (cursor < input.length && depth > 0) {
1457
- const char = input[cursor];
1458
- const prev = input[cursor - 1];
1539
+ const char2 = input[cursor];
1540
+ const prev2 = input[cursor - 1];
1459
1541
  if (quote) {
1460
- if (char === quote && prev !== "\\") {
1542
+ if (char2 === quote && prev2 !== "\\") {
1461
1543
  quote = null;
1462
1544
  }
1463
1545
  cursor += 1;
1464
1546
  continue;
1465
1547
  }
1466
- if (char === "'" || char === '"' || char === "`") {
1467
- quote = char;
1548
+ if (char2 === "'" || char2 === '"' || char2 === "`") {
1549
+ quote = char2;
1468
1550
  cursor += 1;
1469
1551
  continue;
1470
1552
  }
1471
- if (char === "{") {
1553
+ if (char2 === "{") {
1472
1554
  depth += 1;
1473
- } else if (char === "}") {
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, start);
1561
+ throw createCompileError("[olova] Unclosed template expression block.", input, index);
1480
1562
  }
1481
- const expression = input.slice(start + 1, cursor - 1);
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(`(${generate(n).code})`), {
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 = generate(node.left).code;
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 = generate(node.test).code;
1572
- const trueBranchHtml = containsJsxNode(node.consequent) ? jsxToTemplateHtml(node.consequent) : `{${generate(node.consequent).code}}`;
1573
- const falseBranchHtml = containsJsxNode(node.alternate) ? jsxToTemplateHtml(node.alternate) : `{${generate(node.alternate).code}}`;
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, "&quot;")}"`;
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.attrs,
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
- escapeTemplate(html),
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 transformed = transformTemplate(
1877
- parsed.template,
1878
- scriptInfo.stateNames,
1879
- scriptInfo.allSignalNames,
1880
- scriptInfo.jsxFunctionNames,
1881
- scriptInfo.componentNames,
1882
- counters,
1883
- options?.scopeId
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
- const scriptInfo = collectScriptInfo(source);
2105
- return transpileTypeScript(
2106
- [scriptInfo.importsCode, scriptInfo.scriptBodyCode].filter(Boolean).join("\n"),
2107
- options?.filename ?? "module.ts"
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
- const parsed = parseOlovaFile(source);
2116
- if (!parsed.template.trim() && isModuleOnlyScript(parsed.script)) {
2117
- return {
2118
- ...compileModuleScript(parsed.script, options?.filename ?? "module.olova.ts"),
2119
- css: collectCss(parsed.styles)
2120
- };
2121
- }
2122
- const scopeId = parsed.styles.some((style) => !("global" in style.attrs)) ? hashScopeSeed(`${options?.filename ?? "component.olova"}:${options?.exportName ?? "default"}`) : void 0;
2123
- const compiled = buildComponentCode(parsed, {
2124
- scopeId,
2125
- hmrId: options?.filename ?? "component.olova",
2126
- dev: options?.dev
2127
- });
2128
- const runtimeImport = options?.dev ? `import { defineComponent as __defineComponent, replaceComponent as __replaceComponent } from 'olova/runtime';` : `import { defineComponent as __defineComponent } from 'olova/runtime';`;
2129
- const result = transpileTypeScript(`
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
- `, options?.filename ?? "component.olova.ts");
2134
- return {
2135
- ...result,
2136
- css: collectCss(parsed.styles, scopeId)
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.error(err, err.loc?.start?.line);
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
- ctx.server.ws.send({ type: "full-reload", path: "*" });
2293
- return [];
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
  }