mikuru 1.0.20 → 1.0.22

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.
@@ -1,5 +1,6 @@
1
+ import { createCompileError } from "./errors.js";
1
2
  import { compileTemplateExpression, parseForExpression, validateAssignableExpression } from "./parseExpression.js";
2
- export function generateHydration(descriptor, root) {
3
+ export function generateHydration(descriptor, root, options = {}) {
3
4
  const context = {
4
5
  lines: [],
5
6
  index: 0,
@@ -8,11 +9,13 @@ export function generateHydration(descriptor, root) {
8
9
  filename: descriptor.filename
9
10
  };
10
11
  const script = splitScript(descriptor.script ?? "");
11
- for (const importLine of script.imports) {
12
- emit(context, 0, importLine);
12
+ if (options.includeImports !== false) {
13
+ for (const importLine of script.imports) {
14
+ emit(context, 0, importLine);
15
+ }
16
+ emit(context, 0, "import { effect, setAttribute, unwrap } from \"mikuru/runtime\";");
17
+ emit(context, 0, "");
13
18
  }
14
- emit(context, 0, "import { effect, setAttribute, unwrap } from \"mikuru/runtime\";");
15
- emit(context, 0, "");
16
19
  emit(context, 0, "export function hydrate(target, props = {}) {");
17
20
  emit(context, 1, `const __mikuru_componentInfo = { component: ${quote(descriptor.filename ?? "anonymous.mikuru")}, filename: ${quote(descriptor.filename ?? "anonymous.mikuru")} };`);
18
21
  emit(context, 1, "const __mikuru_cleanup = [];");
@@ -34,8 +37,23 @@ export function generateHydration(descriptor, root) {
34
37
  emit(context, 2, "},");
35
38
  emit(context, 2, "registerEffect: (fn) => Promise.resolve().then(fn)");
36
39
  emit(context, 1, "};");
37
- emit(context, 1, "const __mikuru_warn = (message) => { if (typeof console !== \"undefined\" && console.warn) console.warn(`[Mikuru hydration] ${message}`); };");
40
+ emit(context, 1, "const __mikuru_emitDebug = (type, payload) => { const hook = globalThis.__MIKURU_DEVTOOLS__; if (!hook) return; const event = { type, timestamp: Date.now(), payload }; hook.events ??= []; hook.events.push(event); if (typeof hook.emit === \"function\") hook.emit(event); for (const listener of hook.listeners ?? []) { try { listener(event); } catch (error) { setTimeout(() => { throw error; }); } } };");
41
+ emit(context, 1, "const __mikuru_hydrationDiagnostic = (message, details = {}) => ({ ...__mikuru_componentInfo, phase: \"hydration\", message, ...details });");
42
+ emit(context, 1, "const __mikuru_warn = (message, details = {}) => { const diagnostic = __mikuru_hydrationDiagnostic(message, details); __mikuru_emitDebug(\"hydration:warning\", diagnostic); if (typeof console !== \"undefined\" && console.warn) console.warn(`[Mikuru hydration] ${message} (phase: ${diagnostic.phase}, component: ${diagnostic.component}, file: ${diagnostic.filename})`); };");
38
43
  emit(context, 1, "const __mikuru_describeNode = (node) => { if (!node) return \"missing\"; if (node.nodeType === 1) return `<${node.tagName?.toLowerCase?.() ?? \"element\"}>`; if (node.nodeType === 3) return `text(${JSON.stringify(node.nodeValue ?? \"\")})`; if (node.nodeType === 8) return `comment(${JSON.stringify(node.nodeValue ?? \"\")})`; return `nodeType(${node.nodeType})`; };");
44
+ emit(context, 1, "const __mikuru_restoreRegistrar = () => { if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; } };");
45
+ emit(context, 1, "const __mikuru_recovery = {};");
46
+ emit(context, 1, "let __mikuru_recovered;");
47
+ emit(context, 1, "const __mikuru_recover = (message) => {");
48
+ emit(context, 2, "if (props.__mikuru_hydration?.recover === false) { __mikuru_warn(message + \".\"); return; }");
49
+ emit(context, 2, "__mikuru_warn(message + \"; remounting.\");");
50
+ emit(context, 2, "for (const cleanup of __mikuru_cleanup.splice(0).reverse()) __mikuru_try(cleanup);");
51
+ emit(context, 2, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) __mikuru_try(cb);");
52
+ emit(context, 2, "__mikuru_restoreRegistrar();");
53
+ emit(context, 2, "if (target.nodeType === 1) { target.innerHTML = \"\"; }");
54
+ emit(context, 2, "__mikuru_recovered = mount(target, props);");
55
+ emit(context, 2, "throw __mikuru_recovery;");
56
+ emit(context, 1, "};");
39
57
  emit(context, 1, "const __mikuru_findComment = (parent, value) => Array.from(parent.childNodes ?? []).find((node) => node.nodeType === 8 && node.nodeValue === value);");
40
58
  emit(context, 1, "const __mikuru_findNextComment = (node, value) => { for (let cursor = node.nextSibling; cursor; cursor = cursor.nextSibling) { if (cursor.nodeType === 8 && cursor.nodeValue === value) return cursor; } return undefined; };");
41
59
  emit(context, 1, "const __mikuru_setRef = (target, value, multiple = false) => {");
@@ -60,10 +78,15 @@ export function generateHydration(descriptor, root) {
60
78
  emit(context, 1, "");
61
79
  }
62
80
  emit(context, 1, "const __mikuru_root = target.nodeType === 1 && target.tagName?.toLowerCase() === " + quote(root.tag.toLowerCase()) + " ? target : target.firstElementChild;");
63
- emit(context, 1, `if (!__mikuru_root || __mikuru_root.tagName?.toLowerCase() !== ${quote(root.tag.toLowerCase())}) { __mikuru_warn("Root mismatch: expected <${root.tag.toLowerCase()}>, got " + __mikuru_describeNode(__mikuru_root) + "; falling back to mount()."); if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; } return mount(target, props); }`);
64
- hydrateElement(context, root, "__mikuru_root", 1);
81
+ emit(context, 1, `if (!__mikuru_root || __mikuru_root.tagName?.toLowerCase() !== ${quote(root.tag.toLowerCase())}) { __mikuru_warn("Root mismatch: expected <${root.tag.toLowerCase()}>, got " + __mikuru_describeNode(__mikuru_root) + "; falling back to mount()."); __mikuru_restoreRegistrar(); return mount(target, props); }`);
82
+ emit(context, 1, "try {");
83
+ hydrateElement(context, root, "__mikuru_root", 2);
84
+ emit(context, 1, "} catch (error) {");
85
+ emit(context, 2, "if (error === __mikuru_recovery) { return __mikuru_recovered; }");
86
+ emit(context, 2, "throw error;");
87
+ emit(context, 1, "}");
65
88
  emit(context, 1, "for (const cb of __mikuru_mounted.splice(0)) { __mikuru_try(cb); }");
66
- emit(context, 1, "if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; }");
89
+ emit(context, 1, "__mikuru_restoreRegistrar();");
67
90
  emit(context, 1, "return {");
68
91
  emit(context, 2, "element: __mikuru_root,");
69
92
  emit(context, 2, "unmount() { for (const cleanup of __mikuru_cleanup.splice(0).reverse()) __mikuru_try(cleanup); for (const cb of __mikuru_afterUnmount.splice(0).reverse()) __mikuru_try(cb); }");
@@ -95,6 +118,18 @@ function hydrateElement(context, node, elementVar, indent) {
95
118
  hydrateAsyncBoundaryElement(context, node, elementVar, indent);
96
119
  return;
97
120
  }
121
+ if (node.tag === "ErrorBoundary") {
122
+ hydrateErrorBoundaryElement(context, node, elementVar, indent);
123
+ return;
124
+ }
125
+ if (node.tag === "TransitionGroup") {
126
+ hydrateTransitionGroupElement(context, node, elementVar, indent);
127
+ return;
128
+ }
129
+ if (node.tag === "Transition") {
130
+ hydrateTransitionElement(context, node, elementVar, indent);
131
+ return;
132
+ }
98
133
  if (isComponentTag(node.tag)) {
99
134
  hydrateComponent(context, node, elementVar, indent);
100
135
  return;
@@ -102,6 +137,7 @@ function hydrateElement(context, node, elementVar, indent) {
102
137
  hydrateAttrs(context, node, elementVar, indent);
103
138
  hydrateEvents(context, node, elementVar, indent);
104
139
  const contentDirective = getContentDirectiveAttr(node);
140
+ const textareaModel = node.tag === "textarea" && hasElementModel(node);
105
141
  const hydrateChildrenBeforeModel = node.tag === "select" && !contentDirective;
106
142
  if (hydrateChildrenBeforeModel) {
107
143
  hydrateChildren(context, node.children, elementVar, indent);
@@ -109,7 +145,10 @@ function hydrateElement(context, node, elementVar, indent) {
109
145
  hydrateModelAndShow(context, node, elementVar, indent);
110
146
  hydrateContentDirective(context, node, elementVar, indent);
111
147
  hydrateTemplateRef(context, node, elementVar, indent);
112
- if (!contentDirective && !hydrateChildrenBeforeModel) {
148
+ if (textareaModel) {
149
+ emit(context, indent, `if (${elementVar}.childNodes.length > 0) { const __mikuru_textarea_value = ${elementVar}.value; ${elementVar}.textContent = ""; ${elementVar}.value = __mikuru_textarea_value; }`);
150
+ }
151
+ if (!contentDirective && !hydrateChildrenBeforeModel && !textareaModel) {
113
152
  hydrateChildren(context, node.children, elementVar, indent);
114
153
  }
115
154
  }
@@ -134,6 +173,18 @@ function hydrateChildren(context, rawChildren, parentVar, indent) {
134
173
  hydrateAsyncBoundaryAtIndex(context, child, parentVar, domIndexVar, indent);
135
174
  return;
136
175
  }
176
+ if (child.type === "element" && child.tag === "ErrorBoundary") {
177
+ hydrateErrorBoundaryAtIndex(context, child, parentVar, domIndexVar, indent);
178
+ return;
179
+ }
180
+ if (child.type === "element" && child.tag === "TransitionGroup") {
181
+ hydrateTransitionGroupAtIndex(context, child, parentVar, domIndexVar, indent);
182
+ return;
183
+ }
184
+ if (child.type === "element" && child.tag === "Transition") {
185
+ hydrateTransitionAtIndex(context, child, parentVar, domIndexVar, indent);
186
+ return;
187
+ }
137
188
  if (child.type === "element" && getAttr(child, "v-if") && !getAttr(child, "v-pre")) {
138
189
  hydrateIf(context, child, parentVar, domIndexVar, indent);
139
190
  emit(context, indent, `${domIndexVar} += 1;`);
@@ -149,12 +200,12 @@ function hydrateChildren(context, rawChildren, parentVar, indent) {
149
200
  const elementCheck = isComponentTag(child.tag)
150
201
  ? `!${childVar} || ${childVar}.nodeType !== 1`
151
202
  : `!${childVar} || ${childVar}.nodeType !== 1 || ${childVar}.tagName?.toLowerCase() !== ${quote(child.tag.toLowerCase())}`;
152
- emit(context, indent, `if (${elementCheck}) { __mikuru_warn(${quote(`Element mismatch: expected <${child.tag.toLowerCase()}>, got `)} + __mikuru_describeNode(${childVar}) + "."); } else {`);
203
+ emit(context, indent, `if (${elementCheck}) { __mikuru_recover(${quote(`Element mismatch: expected <${child.tag.toLowerCase()}>, got `)} + __mikuru_describeNode(${childVar})); } else {`);
153
204
  hydrateNode(context, child, childVar, indent + 1);
154
205
  emit(context, indent, "}");
155
206
  }
156
207
  else {
157
- emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_warn("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar}) + "."); } else {`);
208
+ emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_recover("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar})); } else {`);
158
209
  hydrateNode(context, child, childVar, indent + 1);
159
210
  emit(context, indent, "}");
160
211
  }
@@ -179,14 +230,14 @@ function hydrateTeleportAtIndex(context, node, parentVar, domIndex, indent) {
179
230
  emit(context, indent, `const ${startVar} = ${parentVar}.childNodes[${domIndex}];`);
180
231
  emit(context, indent, `const ${endVar} = ${startVar} ? __mikuru_findNextComment(${startVar}, ${quote(`/teleport:${id}`)}) : undefined;`);
181
232
  emit(context, indent, `if (!${startVar} || ${startVar}.nodeType !== 8 || ${startVar}.nodeValue !== ${quote(`teleport:${id}`)} || !${endVar}) {`);
182
- emit(context, indent + 1, "__mikuru_warn(\"Teleport marker mismatch.\");");
233
+ emit(context, indent + 1, "__mikuru_recover(\"Teleport marker mismatch\");");
183
234
  emit(context, indent, "} else {");
184
235
  emit(context, indent + 1, `const ${disabledVar} = Boolean(unwrap(${disabledExpression}));`);
185
236
  emit(context, indent + 1, `const ${targetVar} = ${disabledVar} ? undefined : (typeof unwrap(${toExpression}) === "string" ? document.querySelector(unwrap(${toExpression})) : unwrap(${toExpression}));`);
186
237
  emit(context, indent + 1, `if (!${disabledVar} && !${targetVar}) { __mikuru_warn("Teleport target was not found."); } else {`);
187
238
  emit(context, indent + 2, `const ${contentStartVar} = ${disabledVar} ? ${startVar} : __mikuru_findComment(${targetVar}, ${quote(`teleport content:${id}`)});`);
188
239
  emit(context, indent + 2, `const ${contentEndVar} = ${disabledVar} ? ${endVar} : __mikuru_findComment(${targetVar}, ${quote(`/teleport content:${id}`)});`);
189
- emit(context, indent + 2, `if (!${contentStartVar} || !${contentEndVar}) { __mikuru_warn("Teleport content marker mismatch."); } else {`);
240
+ emit(context, indent + 2, `if (!${contentStartVar} || !${contentEndVar}) { __mikuru_recover("Teleport content marker mismatch"); } else {`);
190
241
  emit(context, indent + 3, `const ${nodesVar} = [];`);
191
242
  emit(context, indent + 3, `let ${cursorVar} = ${contentStartVar}.nextSibling;`);
192
243
  emit(context, indent + 3, `while (${cursorVar} && ${cursorVar} !== ${contentEndVar}) { ${nodesVar}.push(${cursorVar}); ${cursorVar} = ${cursorVar}.nextSibling; }`);
@@ -211,11 +262,11 @@ function hydrateIf(context, node, parentVar, domIndex, indent) {
211
262
  : `!${childVar} || ${childVar}.nodeType !== 1 || ${childVar}.tagName?.toLowerCase() !== ${quote(node.tag.toLowerCase())}`;
212
263
  emit(context, indent, `const ${childVar} = ${parentVar}.childNodes[${domIndex}];`);
213
264
  emit(context, indent, `if (unwrap(${compileHydrationExpression(context, condition, "v-if")})) {`);
214
- emit(context, indent + 1, `if (${elementCheck}) { __mikuru_warn("Branch mismatch; dynamic v-if hydration will remount in a future phase."); } else {`);
265
+ emit(context, indent + 1, `if (${elementCheck}) { __mikuru_recover("Branch mismatch: dynamic v-if expected " + __mikuru_describeNode(${childVar})); } else {`);
215
266
  hydrateElement(context, withoutAttrs(node, ["v-if"]), childVar, indent + 2);
216
267
  emit(context, indent + 1, "}");
217
268
  emit(context, indent, `} else if (${childVar}) {`);
218
- emit(context, indent + 1, "__mikuru_warn(\"Branch mismatch; expected no v-if DOM for initial state.\");");
269
+ emit(context, indent + 1, "__mikuru_recover(\"Branch mismatch: expected no v-if DOM for initial state\");");
219
270
  emit(context, indent, "}");
220
271
  }
221
272
  function hydrateFor(context, node, parentVar, domIndex, indent, advanceVar) {
@@ -237,7 +288,7 @@ function hydrateFor(context, node, parentVar, domIndex, indent, advanceVar) {
237
288
  emit(context, indent + 1, `const ${forExpression.index} = __mikuru_index;`);
238
289
  }
239
290
  emit(context, indent + 1, `const ${childVar} = ${parentVar}.childNodes[${domIndex} + __mikuru_index];`);
240
- emit(context, indent + 1, `if (${elementCheck}) { __mikuru_warn("List mismatch; dynamic v-for hydration will remount in a future phase."); } else {`);
291
+ emit(context, indent + 1, `if (${elementCheck}) { __mikuru_recover("List mismatch: dynamic v-for expected row, got " + __mikuru_describeNode(${childVar})); } else {`);
241
292
  withTemplateRefMode(context, "array", () => {
242
293
  hydrateElement(context, withoutAttrs(node, ["v-for"]), childVar, indent + 2);
243
294
  });
@@ -374,6 +425,8 @@ function hydrateComponent(context, node, elementVar, indent) {
374
425
  emit(context, indent, `const ${propsVar} = {};`);
375
426
  hydrateComponentProps(context, node, propsVar, indent);
376
427
  emit(context, indent, `${propsVar}.__mikuru_context = __mikuru_context;`);
428
+ emitHydrationComponentSlots(context, node, propsVar, indent);
429
+ emitRouterViewRouteSlot(context, node, propsVar, indent);
377
430
  emit(context, indent, `if (${node.tag} && typeof ${node.tag}.hydrate === "function") {`);
378
431
  emit(context, indent + 1, `const __mikuru_child = ${node.tag}.hydrate(${elementVar}, ${propsVar});`);
379
432
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
@@ -391,9 +444,246 @@ function hydrateComponent(context, node, elementVar, indent) {
391
444
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
392
445
  hydrateTemplateRef(context, node, "__mikuru_child", indent + 1);
393
446
  emit(context, indent, "} else {");
394
- emit(context, indent + 1, `__mikuru_warn(${quote(`Component mismatch at <${node.tag}>.`)});`);
447
+ emit(context, indent + 1, `__mikuru_recover(${quote(`Component mismatch at <${node.tag}>`)});`);
395
448
  emit(context, indent, "}");
396
449
  }
450
+ function emitRouterViewRouteSlot(context, node, propsVar, indent) {
451
+ if (node.tag !== "RouterView")
452
+ return;
453
+ emit(context, indent, `if (typeof props.children === "function") { ${propsVar}.children = props.children; ${propsVar}.slots = { ...(${propsVar}.slots ?? {}), default: props.children }; }`);
454
+ }
455
+ function emitHydrationComponentSlots(context, node, propsVar, indent) {
456
+ const slots = collectHydrationComponentSlots(context, node);
457
+ if (slots.length === 0) {
458
+ return;
459
+ }
460
+ const defaultSlot = slots.find((slot) => !slot.nameExpression && slot.name === "default");
461
+ if (defaultSlot) {
462
+ emitHydrationSlotFunction(context, `${propsVar}.children`, defaultSlot, indent);
463
+ }
464
+ emit(context, indent, `${propsVar}.slots = { ...(${propsVar}.slots ?? {}) };`);
465
+ for (const slot of slots) {
466
+ const property = slot.nameExpression ? `[${slot.nameExpression}]` : `[${quote(slot.name)}]`;
467
+ emitHydrationSlotFunction(context, `${propsVar}.slots${property}`, slot, indent);
468
+ }
469
+ }
470
+ function emitHydrationSlotFunction(context, target, slot, indent) {
471
+ const slotTargetVar = nextName(context, "slotTarget");
472
+ const slotPropsVar = nextName(context, "slotProps");
473
+ emit(context, indent, `${target} = (${slotTargetVar}, ${slotPropsVar} = {}) => {`);
474
+ emitHydrationSlotScopeBindings(context, slot, slotPropsVar, indent + 1);
475
+ hydrateChildren(context, slot.children, slotTargetVar, indent + 1);
476
+ emit(context, indent, "};");
477
+ }
478
+ function emitHydrationSlotScopeBindings(context, slot, slotPropsVar, indent) {
479
+ for (const binding of parseHydrationSlotScopeBindings(slot.scope, context, slot.scopeLoc ?? slot.loc)) {
480
+ if (binding.kind === "props") {
481
+ emit(context, indent, `const ${binding.alias} = ${slotPropsVar};`);
482
+ continue;
483
+ }
484
+ if (binding.kind === "rest") {
485
+ emit(context, indent, `const ${binding.alias} = { get value() { const rest = { ...${slotPropsVar} }; ${binding.exclude.map((key) => `delete rest[${quote(key)}];`).join(" ")} return rest; } };`);
486
+ continue;
487
+ }
488
+ const valueExpression = slotScopePathExpression(slotPropsVar, binding.path);
489
+ if (binding.defaultValue) {
490
+ emit(context, indent, `const ${binding.alias} = { get value() { const value = ${valueExpression}; return value === undefined ? (${binding.defaultValue}) : value; } };`);
491
+ continue;
492
+ }
493
+ emit(context, indent, `const ${binding.alias} = { get value() { return ${valueExpression}; } };`);
494
+ }
495
+ }
496
+ function slotScopePathExpression(rootVar, path) {
497
+ return path.reduce((expression, key, index) => {
498
+ const property = /^[A-Za-z_$][\w$]*$/.test(key) ? `.${key}` : `[${quote(key)}]`;
499
+ return `${expression}${index === 0 ? property : `?.${property.slice(1)}`}`;
500
+ }, rootVar);
501
+ }
502
+ function collectHydrationComponentSlots(context, node) {
503
+ const slots = [];
504
+ const usedNames = new Set();
505
+ const defaultChildren = [];
506
+ for (const child of node.children) {
507
+ if (child.type === "element" && child.tag === "template") {
508
+ const slotDirective = getHydrationSlotTemplateDirective(child, context);
509
+ if (slotDirective) {
510
+ if (usedNames.has(slotDirective.name)) {
511
+ throwTemplateError(`Duplicate slot template: ${slotDirective.name}`, context, slotDirective.loc);
512
+ }
513
+ usedNames.add(slotDirective.name);
514
+ slots.push({
515
+ name: slotDirective.name,
516
+ nameExpression: slotDirective.nameExpression,
517
+ children: child.children,
518
+ scope: slotDirective.scope,
519
+ loc: child.loc,
520
+ scopeLoc: slotDirective.scopeLoc
521
+ });
522
+ continue;
523
+ }
524
+ }
525
+ defaultChildren.push(child);
526
+ }
527
+ if (hasMeaningfulHydrationChildren(defaultChildren)) {
528
+ if (usedNames.has("default")) {
529
+ throwTemplateError("Duplicate slot template: default", context, node.loc);
530
+ }
531
+ slots.unshift({
532
+ name: "default",
533
+ children: defaultChildren,
534
+ scope: true,
535
+ loc: node.loc
536
+ });
537
+ }
538
+ return slots;
539
+ }
540
+ function getHydrationSlotTemplateDirective(node, context) {
541
+ const slotAttrs = node.attrs.filter((attr) => isSlotTemplateAttr(attr));
542
+ if (slotAttrs.length === 0) {
543
+ return undefined;
544
+ }
545
+ if (slotAttrs.length > 1) {
546
+ throwTemplateError("A slot template can only declare one slot target", context, slotAttrs[1]?.loc);
547
+ }
548
+ const attr = slotAttrs[0];
549
+ const name = getHydrationSlotTemplateName(attr, context);
550
+ return {
551
+ ...name,
552
+ scope: attr.value,
553
+ loc: attr.loc,
554
+ scopeLoc: attr.valueLoc
555
+ };
556
+ }
557
+ function getHydrationSlotTemplateName(attr, context) {
558
+ if (attr.name === "v-slot") {
559
+ return { name: "default" };
560
+ }
561
+ if (attr.name.startsWith("v-slot:")) {
562
+ return parseHydrationSlotTemplateName(attr.name.slice("v-slot:".length) || "default", attr, context);
563
+ }
564
+ if (attr.name.startsWith("#")) {
565
+ return parseHydrationSlotTemplateName(attr.name.slice(1) || "default", attr, context);
566
+ }
567
+ return { name: "default" };
568
+ }
569
+ function parseHydrationSlotTemplateName(rawName, attr, context) {
570
+ const name = rawName.trim();
571
+ const dynamicStart = name.indexOf("[");
572
+ const dynamicEnd = name.lastIndexOf("]");
573
+ if (dynamicStart >= 0 && dynamicEnd > dynamicStart) {
574
+ const expression = name.slice(dynamicStart + 1, dynamicEnd).trim();
575
+ if (!expression) {
576
+ throwTemplateError("Dynamic slot name requires an expression", context, attr.loc);
577
+ }
578
+ return {
579
+ name: `[${expression}]`,
580
+ nameExpression: compileTemplateExpression(expression, attr.name, toExpressionContext(context, attr.loc))
581
+ };
582
+ }
583
+ return { name };
584
+ }
585
+ function isSlotTemplateAttr(attr) {
586
+ return attr.name === "v-slot" || attr.name.startsWith("v-slot:") || attr.name.startsWith("#");
587
+ }
588
+ function hasMeaningfulHydrationChildren(children) {
589
+ return children.some((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
590
+ }
591
+ function parseHydrationSlotScopeBindings(scope, context, location) {
592
+ if (scope === true || !scope.trim()) {
593
+ return [];
594
+ }
595
+ const source = scope.trim();
596
+ if (isIdentifier(source)) {
597
+ return [{ kind: "props", alias: source }];
598
+ }
599
+ if (!source.startsWith("{") || !source.endsWith("}")) {
600
+ throwTemplateError("Slot scope must be an identifier or object destructuring pattern", context, location);
601
+ }
602
+ const body = source.slice(1, -1).trim();
603
+ if (!body) {
604
+ return [];
605
+ }
606
+ return parseHydrationSlotScopeObjectPattern(body, context, location, []);
607
+ }
608
+ function parseHydrationSlotScopeObjectPattern(body, context, location, pathPrefix) {
609
+ const bindings = [];
610
+ const excludedTopLevelKeys = [];
611
+ for (const part of splitTopLevel(body, ",")) {
612
+ const sourcePart = part.trim();
613
+ if (!sourcePart) {
614
+ continue;
615
+ }
616
+ if (sourcePart.startsWith("...")) {
617
+ const alias = sourcePart.slice(3).trim();
618
+ if (pathPrefix.length > 0) {
619
+ throwTemplateError("Slot scope rest destructuring is only supported at the top level", context, location);
620
+ }
621
+ if (!isIdentifier(alias)) {
622
+ throwTemplateError("Slot scope rest destructuring must use a simple identifier like ...rest", context, location);
623
+ }
624
+ bindings.push({ kind: "rest", alias, exclude: excludedTopLevelKeys });
625
+ continue;
626
+ }
627
+ const { left, right } = splitHydrationSlotScopeEntry(sourcePart, context, location);
628
+ if (!isIdentifier(left)) {
629
+ throwTemplateError(`Unsupported slot scope key "${left}". Use identifier keys in slot scope destructuring`, context, location);
630
+ }
631
+ if (pathPrefix.length === 0) {
632
+ excludedTopLevelKeys.push(left);
633
+ }
634
+ const path = [...pathPrefix, left];
635
+ if (right === undefined) {
636
+ bindings.push(...hydrationSlotScopeLeafBindings(left, path, context, location));
637
+ continue;
638
+ }
639
+ const value = right.trim();
640
+ if (value.startsWith("{") && value.endsWith("}")) {
641
+ bindings.push(...parseHydrationSlotScopeObjectPattern(value.slice(1, -1), context, location, path));
642
+ continue;
643
+ }
644
+ if (value.startsWith("[") || value.includes("{")) {
645
+ throwTemplateError("Slot scope destructuring supports nested object patterns only; array and mixed patterns are not supported", context, location);
646
+ }
647
+ bindings.push(...hydrationSlotScopeLeafBindings(value, path, context, location));
648
+ }
649
+ return bindings;
650
+ }
651
+ function splitHydrationSlotScopeEntry(source, context, location) {
652
+ const colonIndex = findTopLevelToken(source, ":");
653
+ if (colonIndex >= 0) {
654
+ return {
655
+ left: source.slice(0, colonIndex).trim(),
656
+ right: source.slice(colonIndex + 1).trim()
657
+ };
658
+ }
659
+ const equalsIndex = findTopLevelToken(source, "=");
660
+ if (equalsIndex >= 0) {
661
+ const left = source.slice(0, equalsIndex).trim();
662
+ if (!isIdentifier(left)) {
663
+ throwTemplateError("Slot scope default values can only be assigned to simple identifiers", context, location);
664
+ }
665
+ return { left, right: source };
666
+ }
667
+ return { left: source.trim() };
668
+ }
669
+ function hydrationSlotScopeLeafBindings(source, path, context, location) {
670
+ const equalsIndex = findTopLevelToken(source, "=");
671
+ const localSource = equalsIndex >= 0 ? source.slice(0, equalsIndex).trim() : source.trim();
672
+ const defaultSource = equalsIndex >= 0 ? source.slice(equalsIndex + 1).trim() : undefined;
673
+ if (!isIdentifier(localSource)) {
674
+ throwTemplateError(`Unsupported slot scope binding "${source}". Use identifiers, aliases, defaults, nested objects, or top-level ...rest`, context, location);
675
+ }
676
+ return [
677
+ {
678
+ kind: "property",
679
+ path,
680
+ alias: localSource,
681
+ defaultValue: defaultSource
682
+ ? compileTemplateExpression(defaultSource, "slot scope default", toExpressionContext(context, location))
683
+ : undefined
684
+ }
685
+ ];
686
+ }
397
687
  function hydrateDynamicComponentAtIndex(context, node, parentVar, domIndex, indent) {
398
688
  const isAttr = getDynamicComponentIsAttr(node);
399
689
  if (!isAttr) {
@@ -404,7 +694,7 @@ function hydrateDynamicComponentAtIndex(context, node, parentVar, domIndex, inde
404
694
  emit(context, indent, `const ${componentTypeVar} = unwrap(${compileHydrationExpression(context, requireAttrValue(isAttr), isAttr.name)});`);
405
695
  emit(context, indent, `if (${componentTypeVar}) {`);
406
696
  emit(context, indent + 1, `const ${childVar} = ${parentVar}.childNodes[${domIndex}];`);
407
- emit(context, indent + 1, `if (!${childVar} || ${childVar}.nodeType !== 1) { __mikuru_warn("Element mismatch: expected dynamic component root, got " + __mikuru_describeNode(${childVar}) + "."); } else {`);
697
+ emit(context, indent + 1, `if (!${childVar} || ${childVar}.nodeType !== 1) { __mikuru_recover("Element mismatch: expected dynamic component root, got " + __mikuru_describeNode(${childVar})); } else {`);
408
698
  hydrateDynamicComponent(context, node, childVar, componentTypeVar, indent + 2);
409
699
  emit(context, indent + 1, "}");
410
700
  emit(context, indent + 1, `${domIndex} += 1;`);
@@ -416,6 +706,8 @@ function hydrateDynamicComponent(context, node, elementVar, componentType, inden
416
706
  emit(context, indent, `const ${propsVar} = {};`);
417
707
  hydrateComponentProps(context, dynamicNode, propsVar, indent);
418
708
  emit(context, indent, `${propsVar}.__mikuru_context = __mikuru_context;`);
709
+ emitHydrationComponentSlots(context, dynamicNode, propsVar, indent);
710
+ emitRouterViewRouteSlot(context, dynamicNode, propsVar, indent);
419
711
  emit(context, indent, `if (${componentType} && typeof ${componentType}.hydrate === "function") {`);
420
712
  emit(context, indent + 1, `const __mikuru_child = ${componentType}.hydrate(${elementVar}, ${propsVar});`);
421
713
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
@@ -433,7 +725,7 @@ function hydrateDynamicComponent(context, node, elementVar, componentType, inden
433
725
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
434
726
  hydrateTemplateRef(context, dynamicNode, "__mikuru_child", indent + 1);
435
727
  emit(context, indent, "} else {");
436
- emit(context, indent + 1, "__mikuru_warn(\"Dynamic component mismatch; :is did not resolve to hydrate() or mount().\");");
728
+ emit(context, indent + 1, "__mikuru_recover(\"Dynamic component mismatch: :is did not resolve to hydrate() or mount()\");");
437
729
  emit(context, indent, "}");
438
730
  }
439
731
  function hydrateKeepAliveAtIndex(context, node, parentVar, domIndex, indent) {
@@ -474,6 +766,84 @@ function hydrateAsyncBoundaryElement(context, node, elementVar, indent) {
474
766
  hydrateFragmentChildrenAtIndex(context, getAsyncBoundaryChildren(node), parentVar, indexVar, indent + 1);
475
767
  emit(context, indent, "}");
476
768
  }
769
+ function hydrateErrorBoundaryAtIndex(context, node, parentVar, domIndex, indent) {
770
+ validateErrorBoundaryAttributes(node);
771
+ getErrorBoundaryFallbackAttr(node);
772
+ hydrateFragmentChildrenAtIndex(context, [getSingleElementChild(node, "<ErrorBoundary>")], parentVar, domIndex, indent);
773
+ }
774
+ function hydrateErrorBoundaryElement(context, node, elementVar, indent) {
775
+ validateErrorBoundaryAttributes(node);
776
+ getErrorBoundaryFallbackAttr(node);
777
+ const parentVar = nextName(context, "errorBoundaryParent");
778
+ const indexVar = nextName(context, "errorBoundaryIndex");
779
+ emit(context, indent, `const ${parentVar} = ${elementVar}.parentNode;`);
780
+ emit(context, indent, `let ${indexVar} = ${parentVar} ? Array.prototype.indexOf.call(${parentVar}.childNodes, ${elementVar}) : 0;`);
781
+ emit(context, indent, `if (${parentVar}) {`);
782
+ hydrateFragmentChildrenAtIndex(context, [getSingleElementChild(node, "<ErrorBoundary>")], parentVar, indexVar, indent + 1);
783
+ emit(context, indent, "}");
784
+ }
785
+ function hydrateTransitionAtIndex(context, node, parentVar, domIndex, indent) {
786
+ validateTransitionAttributes(node);
787
+ const children = getTransitionChildren(node);
788
+ if (getAttr(children[0], "v-if")) {
789
+ hydrateTransitionIfChainAtIndex(context, children, parentVar, domIndex, indent);
790
+ return;
791
+ }
792
+ hydrateFragmentChildrenAtIndex(context, [children[0]], parentVar, domIndex, indent);
793
+ }
794
+ function hydrateTransitionElement(context, node, elementVar, indent) {
795
+ validateTransitionAttributes(node);
796
+ const parentVar = nextName(context, "transitionParent");
797
+ const indexVar = nextName(context, "transitionIndex");
798
+ emit(context, indent, `const ${parentVar} = ${elementVar}.parentNode;`);
799
+ emit(context, indent, `let ${indexVar} = ${parentVar} ? Array.prototype.indexOf.call(${parentVar}.childNodes, ${elementVar}) : 0;`);
800
+ emit(context, indent, `if (${parentVar}) {`);
801
+ hydrateTransitionAtIndex(context, node, parentVar, indexVar, indent + 1);
802
+ emit(context, indent, "}");
803
+ }
804
+ function hydrateTransitionIfChainAtIndex(context, children, parentVar, domIndex, indent) {
805
+ children.forEach((child, index) => {
806
+ if (index === 0) {
807
+ emit(context, indent, `if (unwrap(${compileHydrationExpression(context, getAttrValue(child, "v-if"), "v-if")})) {`);
808
+ }
809
+ else if (getAttr(child, "v-else-if")) {
810
+ emit(context, indent, `else if (unwrap(${compileHydrationExpression(context, getAttrValue(child, "v-else-if"), "v-else-if")})) {`);
811
+ }
812
+ else {
813
+ emit(context, indent, "else {");
814
+ }
815
+ hydrateFragmentChildrenAtIndex(context, [withoutAttrs(child, ["v-if", "v-else-if", "v-else"])], parentVar, domIndex, indent + 1);
816
+ emit(context, indent, "}");
817
+ });
818
+ }
819
+ function hydrateTransitionGroupAtIndex(context, node, parentVar, domIndex, indent) {
820
+ validateTransitionGroupAttributes(node);
821
+ const child = getTransitionGroupChild(node);
822
+ const groupVar = nextName(context, "transitionGroup");
823
+ const expectedTagVar = nextName(context, "transitionGroupTag");
824
+ const groupIndexVar = nextName(context, "transitionGroupIndex");
825
+ emit(context, indent, `const ${groupVar} = ${parentVar}.childNodes[${domIndex}];`);
826
+ emit(context, indent, `const ${expectedTagVar} = String(unwrap(${getTransitionGroupTagExpression(context, node)}) ?? "span").toLowerCase();`);
827
+ emit(context, indent, `if (!${groupVar} || ${groupVar}.nodeType !== 1 || ${groupVar}.tagName?.toLowerCase() !== ${expectedTagVar}) {`);
828
+ emit(context, indent + 1, `__mikuru_recover("TransitionGroup mismatch: expected <" + ${expectedTagVar} + ">, got " + __mikuru_describeNode(${groupVar}));`);
829
+ emit(context, indent, "} else {");
830
+ emit(context, indent + 1, `let ${groupIndexVar} = 0;`);
831
+ hydrateFor(context, withoutAttrs(child, [":key", "v-bind:key"]), groupVar, groupIndexVar, indent + 1, groupIndexVar);
832
+ emit(context, indent + 1, `if (${groupVar}.childNodes.length > ${groupIndexVar}) { __mikuru_warn("Extra DOM nodes after TransitionGroup hydration: " + Array.from(${groupVar}.childNodes).slice(${groupIndexVar}).map(__mikuru_describeNode).join(", ") + "."); }`);
833
+ emit(context, indent, "}");
834
+ emit(context, indent, `${domIndex} += 1;`);
835
+ }
836
+ function hydrateTransitionGroupElement(context, node, elementVar, indent) {
837
+ validateTransitionGroupAttributes(node);
838
+ const child = getTransitionGroupChild(node);
839
+ const expectedTagVar = nextName(context, "transitionGroupTag");
840
+ const groupIndexVar = nextName(context, "transitionGroupIndex");
841
+ emit(context, indent, `const ${expectedTagVar} = String(unwrap(${getTransitionGroupTagExpression(context, node)}) ?? "span").toLowerCase();`);
842
+ emit(context, indent, `if (${elementVar}.tagName?.toLowerCase() !== ${expectedTagVar}) { __mikuru_recover("TransitionGroup mismatch: expected <" + ${expectedTagVar} + ">, got " + __mikuru_describeNode(${elementVar})); }`);
843
+ emit(context, indent, `let ${groupIndexVar} = 0;`);
844
+ hydrateFor(context, withoutAttrs(child, [":key", "v-bind:key"]), elementVar, groupIndexVar, indent, groupIndexVar);
845
+ emit(context, indent, `if (${elementVar}.childNodes.length > ${groupIndexVar}) { __mikuru_warn("Extra DOM nodes after TransitionGroup hydration: " + Array.from(${elementVar}.childNodes).slice(${groupIndexVar}).map(__mikuru_describeNode).join(", ") + "."); }`);
846
+ }
477
847
  function hydrateFragmentChildrenAtIndex(context, rawChildren, parentVar, domIndex, indent) {
478
848
  const children = rawChildren.filter(isHydratableNode);
479
849
  children.forEach((child) => {
@@ -493,6 +863,18 @@ function hydrateFragmentChildrenAtIndex(context, rawChildren, parentVar, domInde
493
863
  hydrateAsyncBoundaryAtIndex(context, child, parentVar, domIndex, indent);
494
864
  return;
495
865
  }
866
+ if (child.type === "element" && child.tag === "ErrorBoundary") {
867
+ hydrateErrorBoundaryAtIndex(context, child, parentVar, domIndex, indent);
868
+ return;
869
+ }
870
+ if (child.type === "element" && child.tag === "TransitionGroup") {
871
+ hydrateTransitionGroupAtIndex(context, child, parentVar, domIndex, indent);
872
+ return;
873
+ }
874
+ if (child.type === "element" && child.tag === "Transition") {
875
+ hydrateTransitionAtIndex(context, child, parentVar, domIndex, indent);
876
+ return;
877
+ }
496
878
  if (child.type === "element" && getAttr(child, "v-if") && !getAttr(child, "v-pre")) {
497
879
  hydrateIf(context, child, parentVar, domIndex, indent);
498
880
  emit(context, indent, `${domIndex} += 1;`);
@@ -508,12 +890,12 @@ function hydrateFragmentChildrenAtIndex(context, rawChildren, parentVar, domInde
508
890
  const elementCheck = isComponentTag(child.tag)
509
891
  ? `!${childVar} || ${childVar}.nodeType !== 1`
510
892
  : `!${childVar} || ${childVar}.nodeType !== 1 || ${childVar}.tagName?.toLowerCase() !== ${quote(child.tag.toLowerCase())}`;
511
- emit(context, indent, `if (${elementCheck}) { __mikuru_warn(${quote(`Element mismatch: expected <${child.tag.toLowerCase()}>, got `)} + __mikuru_describeNode(${childVar}) + "."); } else {`);
893
+ emit(context, indent, `if (${elementCheck}) { __mikuru_recover(${quote(`Element mismatch: expected <${child.tag.toLowerCase()}>, got `)} + __mikuru_describeNode(${childVar})); } else {`);
512
894
  hydrateNode(context, child, childVar, indent + 1);
513
895
  emit(context, indent, "}");
514
896
  }
515
897
  else {
516
- emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_warn("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar}) + "."); } else {`);
898
+ emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_recover("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar})); } else {`);
517
899
  hydrateNode(context, child, childVar, indent + 1);
518
900
  emit(context, indent, "}");
519
901
  }
@@ -892,6 +1274,62 @@ function validateAsyncBoundaryAttributes(node) {
892
1274
  throw new Error(`Unsupported attribute "${attr.name}" on <AsyncBoundary>. Supported attributes: loading, fallback, delay, and timeout.`);
893
1275
  }
894
1276
  }
1277
+ function validateErrorBoundaryAttributes(node) {
1278
+ for (const attr of node.attrs) {
1279
+ const name = parseBindDirective(attr.name)?.name ?? attr.name;
1280
+ if (name === "fallback" || name === "reset-key") {
1281
+ continue;
1282
+ }
1283
+ throw new Error(`Unsupported attribute "${attr.name}" on <ErrorBoundary>. Supported attributes: fallback and reset-key.`);
1284
+ }
1285
+ }
1286
+ function validateTransitionAttributes(node) {
1287
+ const supported = new Set([
1288
+ "name",
1289
+ "appear",
1290
+ "mode",
1291
+ "enter-from-class",
1292
+ "enter-active-class",
1293
+ "enter-to-class",
1294
+ "leave-from-class",
1295
+ "leave-active-class",
1296
+ "leave-to-class"
1297
+ ]);
1298
+ for (const attr of node.attrs) {
1299
+ const name = parseBindDirective(attr.name)?.name ?? attr.name;
1300
+ if (supported.has(name)) {
1301
+ continue;
1302
+ }
1303
+ throw new Error(`Unsupported attribute "${attr.name}" on <Transition>. Supported attributes: name, appear, mode, and CSS class override attributes.`);
1304
+ }
1305
+ }
1306
+ function validateTransitionGroupAttributes(node) {
1307
+ const supported = new Set([
1308
+ "name",
1309
+ "tag",
1310
+ "enter-from-class",
1311
+ "enter-active-class",
1312
+ "enter-to-class",
1313
+ "leave-from-class",
1314
+ "leave-active-class",
1315
+ "leave-to-class",
1316
+ "move-class"
1317
+ ]);
1318
+ for (const attr of node.attrs) {
1319
+ const name = parseBindDirective(attr.name)?.name ?? attr.name;
1320
+ if (supported.has(name)) {
1321
+ continue;
1322
+ }
1323
+ throw new Error(`Unsupported attribute "${attr.name}" on <TransitionGroup>. Supported attributes: name, tag, and CSS class override attributes.`);
1324
+ }
1325
+ }
1326
+ function getErrorBoundaryFallbackAttr(node) {
1327
+ const attr = node.attrs.find((candidate) => parseBindDirective(candidate.name)?.name === "fallback");
1328
+ if (!attr) {
1329
+ throw new Error("<ErrorBoundary> requires :fallback to resolve to a component object");
1330
+ }
1331
+ return attr;
1332
+ }
895
1333
  function getAsyncBoundaryChildren(node) {
896
1334
  const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
897
1335
  if (meaningful.length === 0) {
@@ -899,6 +1337,42 @@ function getAsyncBoundaryChildren(node) {
899
1337
  }
900
1338
  return node.children;
901
1339
  }
1340
+ function getTransitionChildren(node) {
1341
+ const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
1342
+ if (meaningful.length === 0 || meaningful.some((child) => child.type !== "element")) {
1343
+ throw new Error("<Transition> requires exactly one element/component child or one v-if chain");
1344
+ }
1345
+ const children = meaningful;
1346
+ if (children.length === 1) {
1347
+ return children;
1348
+ }
1349
+ if (!getAttr(children[0], "v-if")) {
1350
+ throw new Error("<Transition> requires exactly one element/component child or one v-if chain");
1351
+ }
1352
+ for (const child of children.slice(1)) {
1353
+ if (!getAttr(child, "v-else-if") && !getAttr(child, "v-else")) {
1354
+ throw new Error("<Transition> only accepts multiple children when they form a v-if chain");
1355
+ }
1356
+ }
1357
+ return children;
1358
+ }
1359
+ function getTransitionGroupChild(node) {
1360
+ const child = getSingleElementChild(node, "<TransitionGroup>");
1361
+ if (!getAttr(child, "v-for") || !getKeyExpression(child)) {
1362
+ throw new Error("<TransitionGroup> requires a single keyed v-for child in v1");
1363
+ }
1364
+ return child;
1365
+ }
1366
+ function getTransitionGroupTagExpression(context, node) {
1367
+ const dynamicTag = node.attrs.find((attr) => parseBindDirective(attr.name)?.name === "tag");
1368
+ if (dynamicTag) {
1369
+ return compileHydrationExpression(context, requireAttrValue(dynamicTag), dynamicTag.name);
1370
+ }
1371
+ return quote(getStaticAttrValue(node, "tag") ?? "span");
1372
+ }
1373
+ function getKeyExpression(node) {
1374
+ return getStaticAttrValue(node, ":key") ?? getStaticAttrValue(node, "v-bind:key");
1375
+ }
902
1376
  function getSingleElementChild(node, label) {
903
1377
  const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
904
1378
  if (meaningful.length !== 1 || meaningful[0]?.type !== "element") {
@@ -1166,6 +1640,9 @@ function parseModelDirective(name) {
1166
1640
  const [argument = "", ...modifiers] = name.slice("v-model:".length).split(".");
1167
1641
  return { argument, modifiers: modifiers.filter(Boolean) };
1168
1642
  }
1643
+ function hasElementModel(node) {
1644
+ return node.attrs.some((attr) => Boolean(parseModelDirective(attr.name)));
1645
+ }
1169
1646
  function toComponentEventProp(eventName) {
1170
1647
  return `on${eventName.split(":").map((part) => part ? part[0].toUpperCase() + part.slice(1) : "").join("")}`;
1171
1648
  }
@@ -1238,9 +1715,90 @@ function compileHydrationExpression(context, expression, usage) {
1238
1715
  filename: context.filename
1239
1716
  });
1240
1717
  }
1718
+ function toExpressionContext(context, location) {
1719
+ if (!context.source || !location) {
1720
+ return undefined;
1721
+ }
1722
+ return {
1723
+ source: context.source,
1724
+ offset: location.offset,
1725
+ filename: context.filename
1726
+ };
1727
+ }
1728
+ function throwTemplateError(message, context, location) {
1729
+ if (context.source && location) {
1730
+ throw createCompileError(message, context.source, location.offset, context.filename);
1731
+ }
1732
+ throw new Error(message);
1733
+ }
1241
1734
  function isIdentifier(value) {
1242
1735
  return /^[A-Za-z_$][\w$]*$/.test(value);
1243
1736
  }
1737
+ function splitTopLevel(source, delimiter) {
1738
+ const parts = [];
1739
+ let depth = 0;
1740
+ let quoteChar = "";
1741
+ let start = 0;
1742
+ for (let index = 0; index < source.length; index += 1) {
1743
+ const char = source[index];
1744
+ const previous = source[index - 1];
1745
+ if (quoteChar) {
1746
+ if (char === quoteChar && previous !== "\\") {
1747
+ quoteChar = "";
1748
+ }
1749
+ continue;
1750
+ }
1751
+ if (char === "\"" || char === "'" || char === "`") {
1752
+ quoteChar = char;
1753
+ continue;
1754
+ }
1755
+ if (char === "{" || char === "[" || char === "(") {
1756
+ depth += 1;
1757
+ continue;
1758
+ }
1759
+ if (char === "}" || char === "]" || char === ")") {
1760
+ depth -= 1;
1761
+ continue;
1762
+ }
1763
+ if (depth === 0 && source.startsWith(delimiter, index)) {
1764
+ parts.push(source.slice(start, index));
1765
+ start = index + delimiter.length;
1766
+ index += delimiter.length - 1;
1767
+ }
1768
+ }
1769
+ parts.push(source.slice(start));
1770
+ return parts;
1771
+ }
1772
+ function findTopLevelToken(source, token) {
1773
+ let depth = 0;
1774
+ let quoteChar = "";
1775
+ for (let index = 0; index < source.length; index += 1) {
1776
+ const char = source[index];
1777
+ const previous = source[index - 1];
1778
+ if (quoteChar) {
1779
+ if (char === quoteChar && previous !== "\\") {
1780
+ quoteChar = "";
1781
+ }
1782
+ continue;
1783
+ }
1784
+ if (char === "\"" || char === "'" || char === "`") {
1785
+ quoteChar = char;
1786
+ continue;
1787
+ }
1788
+ if (char === "{" || char === "[" || char === "(") {
1789
+ depth += 1;
1790
+ continue;
1791
+ }
1792
+ if (char === "}" || char === "]" || char === ")") {
1793
+ depth -= 1;
1794
+ continue;
1795
+ }
1796
+ if (depth === 0 && source.startsWith(token, index)) {
1797
+ return index;
1798
+ }
1799
+ }
1800
+ return -1;
1801
+ }
1244
1802
  function withTemplateRefMode(context, mode, callback) {
1245
1803
  const previousMode = context.templateRefMode;
1246
1804
  context.templateRefMode = mode;