mikuru 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 1.0.21 - 2026-05-13
6
+
7
+ - Added SSR and hydration support for initial `<ErrorBoundary>` children.
8
+ - Added SSR and hydration support for initial `<Transition>` children and v-if chains.
9
+ - Added SSR and hydration support for initial `<TransitionGroup>` keyed lists.
10
+ - Added hydration structural mismatch recovery with a remount fallback.
11
+ - Added `renderToStream()` for async iterable SSR output.
12
+ - Added SSR loader resolution and error fallback rendering for `defineAsyncComponent()`.
13
+ - Added Vite `.mikuru?ssr` and `.mikuru?hydrate` imports plus an SSR/hydration example and recovery E2E coverage.
14
+ - Added recovery-disabled E2E coverage plus nested built-in wrapper SSR/hydration tests.
15
+ - Added a router SSR/hydration example and browser E2E coverage for route rendering, hydration, redirects, guards, nested routes, and lazy route components.
16
+ - Added SSR and hydration support for generated `<RouterView>` and `<RouterLink>` usage in route components.
17
+ - Added hydration slot forwarding for component children so SSR-rendered `<RouterLink>` child content is preserved during hydration.
18
+ - Expanded RouterLink SSR/hydration E2E coverage for custom active classes, replace links, and child content.
19
+ - Stabilized the dogfood Debug Panel event clearing flow in browser E2E runs.
20
+
3
21
  ## 1.0.20 - 2026-05-13
4
22
 
5
23
  - Added SSR phase 1 with `compileSsr()` and `mikuru/server` helpers for escaped HTML, attributes, `v-if` chains, and `v-for` output.
package/README.md CHANGED
@@ -142,10 +142,10 @@ declare const Greeting: MikuruComponent<GreetingProps>;
142
142
  - Built-in `<Teleport to="#target">` for rendering content outside the current DOM position
143
143
  - Built-in `<AsyncBoundary :loading :fallback :delay :timeout>` for grouped async loading, delayed loading UI, boundary timeouts, and retryable async failures with aggregated fallback errors
144
144
  - Built-in `<ErrorBoundary :fallback>` for local component mount, descendant event handler, lifecycle, and cleanup fallbacks, with `errorInfo`, `retry`, `reset`, and `:reset-key` recovery
145
- - Runtime helpers including `ref`, `isRef`, `unref`, `toRef`, `toRefs`, `reactive`, `readonly`, lazy cached read-only and writable `computed`, `effect` with optional scheduling, `queueJob`/`flushJobs`, `watch`, `watchEffect` with cleanup callbacks, `nextTick`, lifecycle callbacks including KeepAlive activation hooks, `provide`, `inject`, and `defineAsyncComponent` with ErrorBoundary handoff
146
- - Routing through `mikuru/router` with route matching, history/hash/memory histories, guards, router context helpers, `RouterView`, and `RouterLink`
147
- - SSR through `compileSsr()` and `mikuru/server`, covering escaped text, static and bound attributes, content directives, `v-pre`, `v-cloak`, `v-if` chains, `v-for`, async child components, props, named/default slots, scoped slot props, component tree context, Teleport collection, and router route rendering with context propagation
148
- - Hydration through `compileHydration()` and `hydrateRoute()`, reusing existing SSR DOM while attaching events, syncing text/attributes, hydrating component context/lifecycle hooks, `v-show`, DOM and component `v-model`, `v-pre`, `v-cloak`, initial `v-if` / `v-for` DOM, Teleport target and disabled inline content, delegating child and route components to `hydrate()` when available, and optionally starting router history listening after route hydration
145
+ - Runtime helpers including `ref`, `isRef`, `unref`, `toRef`, `toRefs`, `reactive`, `readonly`, lazy cached read-only and writable `computed`, `effect` with optional scheduling, `queueJob`/`flushJobs`, `watch`, `watchEffect` with cleanup callbacks, `nextTick`, lifecycle callbacks including KeepAlive activation hooks, `provide`, `inject`, and `defineAsyncComponent` with ErrorBoundary handoff and SSR loader resolution
146
+ - Routing through `mikuru/router` with route matching, history/hash/memory histories, guards, router context helpers, and `RouterView` / `RouterLink` across mount, SSR, and hydration
147
+ - SSR through `compileSsr()` and `mikuru/server`, covering escaped text, static and bound attributes, content directives, `v-pre`, `v-cloak`, `v-if` chains, `v-for`, async child components, props, named/default slots, scoped slot props, component tree context, Teleport collection, string and async iterable stream rendering, and router route rendering with context propagation
148
+ - Hydration through `compileHydration()` and `hydrateRoute()`, reusing existing SSR DOM while attaching events, syncing text/attributes, recovering structural mismatches with an opt-out remount fallback, hydrating component context/lifecycle hooks, `v-show`, DOM and component `v-model`, `v-pre`, `v-cloak`, initial `v-if` / `v-for` DOM, Teleport target and disabled inline content, delegating child and route components to `hydrate()` when available, and optionally starting router history listening after route hydration
149
149
  - Style injection and basic `<style scoped>` selector rewriting
150
150
  - Compile errors with filenames, line/column information, code frames, and typo suggestions for built-in attributes, directives, and modifiers
151
151
 
@@ -180,7 +180,7 @@ Compiler, runtime, and server entries are public for lower-level integrations:
180
180
  ```ts
181
181
  import { compile, compileHydration, compileSsr } from "mikuru/compiler";
182
182
  import { effect, isRef, nextTick, reactive, readonly, ref, toRef, toRefs, unref, unwrap, watch } from "mikuru/runtime";
183
- import { hydrateRoute, renderComponentToString, renderRouteToString, renderToString } from "mikuru/server";
183
+ import { hydrateRoute, renderComponentToString, renderRouteToString, renderToStream, renderToString } from "mikuru/server";
184
184
  import type { MikuruAsyncBoundaryFallbackProps, MikuruErrorBoundaryFallbackProps, MikuruErrorInfo, MikuruErrorPhase } from "mikuru/runtime";
185
185
  ```
186
186
 
@@ -246,6 +246,8 @@ npm run test:create
246
246
  npm run test:package
247
247
  npm run test:pack
248
248
  npm run test:e2e
249
+ npm run test:e2e:router-ssr-hydration
250
+ npm run test:e2e:ssr-hydration
249
251
  ```
250
252
 
251
253
  Examples can be run from the repository root:
@@ -254,6 +256,8 @@ Examples can be run from the repository root:
254
256
  npm run dev:basic
255
257
  npm run dev:realworld
256
258
  npm run dev:dogfood
259
+ npm run dev:router-ssr-hydration
260
+ npm run dev:ssr-hydration
257
261
  npm run dev:mikuru-sample
258
262
  npm run dev:mikuru-vue-like
259
263
  ```
@@ -13,8 +13,8 @@ export function compileHydration(source, options = {}) {
13
13
  });
14
14
  const bindings = analyzeTemplate(ast, { source, filename: options.filename });
15
15
  const mountCode = generate(descriptor, ast, { debug: options.debug === true, batchedUpdates: options.batchedUpdates === true });
16
- const hydrationCode = generateHydration(descriptor, ast);
17
- const code = mountCode.replace("export default __mikuru_component;", `${hydrationCode}\nexport { hydrate };\nconst __mikuru_hydrationComponent = { ...__mikuru_component, hydrate };\nexport default __mikuru_hydrationComponent;`);
16
+ const hydrationCode = generateHydration(descriptor, ast, { includeImports: false });
17
+ const code = mountCode.replace("export default __mikuru_component;", `${hydrationCode}\nconst __mikuru_hydrationComponent = { ...__mikuru_component, hydrate };\nexport default __mikuru_hydrationComponent;`);
18
18
  const map = createSourceMap(code, descriptor, ast);
19
19
  return {
20
20
  code,
@@ -1 +1 @@
1
- {"version":3,"file":"compileHydration.js","sourceRoot":"","sources":["../../src/compiler/compileHydration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,UAA0B,EAAE;IAC3E,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,CAAC,QAAQ,EAAE;QAC7C,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM;QACN,MAAM,EAAE,UAAU,CAAC,cAAc;KAClC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC,CAAC;IAChI,MAAM,aAAa,GAAG,iBAAiB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,oCAAoC,EAAE,GAAG,aAAa,6IAA6I,CAAC,CAAC;IACpO,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IAEnD,OAAO;QACL,IAAI;QACJ,GAAG;QACH,UAAU;QACV,GAAG;QACH,QAAQ;KACT,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"compileHydration.js","sourceRoot":"","sources":["../../src/compiler/compileHydration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,UAA0B,EAAE;IAC3E,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,CAAC,QAAQ,EAAE;QAC7C,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM;QACN,MAAM,EAAE,UAAU,CAAC,cAAc;KAClC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC,CAAC;IAChI,MAAM,aAAa,GAAG,iBAAiB,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,oCAAoC,EAAE,GAAG,aAAa,wHAAwH,CAAC,CAAC;IAC/M,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IAEnD,OAAO;QACL,IAAI;QACJ,GAAG;QACH,UAAU;QACV,GAAG;QACH,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -1,2 +1,6 @@
1
1
  import type { ElementNode, SfcDescriptor } from "./types.js";
2
- export declare function generateHydration(descriptor: SfcDescriptor, root: ElementNode): string;
2
+ type GenerateHydrationOptions = {
3
+ includeImports?: boolean;
4
+ };
5
+ export declare function generateHydration(descriptor: SfcDescriptor, root: ElementNode, options?: GenerateHydrationOptions): string;
6
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { compileTemplateExpression, parseForExpression, validateAssignableExpression } from "./parseExpression.js";
2
- export function generateHydration(descriptor, root) {
2
+ export function generateHydration(descriptor, root, options = {}) {
3
3
  const context = {
4
4
  lines: [],
5
5
  index: 0,
@@ -8,11 +8,13 @@ export function generateHydration(descriptor, root) {
8
8
  filename: descriptor.filename
9
9
  };
10
10
  const script = splitScript(descriptor.script ?? "");
11
- for (const importLine of script.imports) {
12
- emit(context, 0, importLine);
11
+ if (options.includeImports !== false) {
12
+ for (const importLine of script.imports) {
13
+ emit(context, 0, importLine);
14
+ }
15
+ emit(context, 0, "import { effect, setAttribute, unwrap } from \"mikuru/runtime\";");
16
+ emit(context, 0, "");
13
17
  }
14
- emit(context, 0, "import { effect, setAttribute, unwrap } from \"mikuru/runtime\";");
15
- emit(context, 0, "");
16
18
  emit(context, 0, "export function hydrate(target, props = {}) {");
17
19
  emit(context, 1, `const __mikuru_componentInfo = { component: ${quote(descriptor.filename ?? "anonymous.mikuru")}, filename: ${quote(descriptor.filename ?? "anonymous.mikuru")} };`);
18
20
  emit(context, 1, "const __mikuru_cleanup = [];");
@@ -36,6 +38,19 @@ export function generateHydration(descriptor, root) {
36
38
  emit(context, 1, "};");
37
39
  emit(context, 1, "const __mikuru_warn = (message) => { if (typeof console !== \"undefined\" && console.warn) console.warn(`[Mikuru hydration] ${message}`); };");
38
40
  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})`; };");
41
+ emit(context, 1, "const __mikuru_restoreRegistrar = () => { if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; } };");
42
+ emit(context, 1, "const __mikuru_recovery = {};");
43
+ emit(context, 1, "let __mikuru_recovered;");
44
+ emit(context, 1, "const __mikuru_recover = (message) => {");
45
+ emit(context, 2, "if (props.__mikuru_hydration?.recover === false) { __mikuru_warn(message + \".\"); return; }");
46
+ emit(context, 2, "__mikuru_warn(message + \"; remounting.\");");
47
+ emit(context, 2, "for (const cleanup of __mikuru_cleanup.splice(0).reverse()) __mikuru_try(cleanup);");
48
+ emit(context, 2, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) __mikuru_try(cb);");
49
+ emit(context, 2, "__mikuru_restoreRegistrar();");
50
+ emit(context, 2, "if (target.nodeType === 1) { target.innerHTML = \"\"; }");
51
+ emit(context, 2, "__mikuru_recovered = mount(target, props);");
52
+ emit(context, 2, "throw __mikuru_recovery;");
53
+ emit(context, 1, "};");
39
54
  emit(context, 1, "const __mikuru_findComment = (parent, value) => Array.from(parent.childNodes ?? []).find((node) => node.nodeType === 8 && node.nodeValue === value);");
40
55
  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
56
  emit(context, 1, "const __mikuru_setRef = (target, value, multiple = false) => {");
@@ -60,10 +75,15 @@ export function generateHydration(descriptor, root) {
60
75
  emit(context, 1, "");
61
76
  }
62
77
  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);
78
+ 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); }`);
79
+ emit(context, 1, "try {");
80
+ hydrateElement(context, root, "__mikuru_root", 2);
81
+ emit(context, 1, "} catch (error) {");
82
+ emit(context, 2, "if (error === __mikuru_recovery) { return __mikuru_recovered; }");
83
+ emit(context, 2, "throw error;");
84
+ emit(context, 1, "}");
65
85
  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; }");
86
+ emit(context, 1, "__mikuru_restoreRegistrar();");
67
87
  emit(context, 1, "return {");
68
88
  emit(context, 2, "element: __mikuru_root,");
69
89
  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 +115,18 @@ function hydrateElement(context, node, elementVar, indent) {
95
115
  hydrateAsyncBoundaryElement(context, node, elementVar, indent);
96
116
  return;
97
117
  }
118
+ if (node.tag === "ErrorBoundary") {
119
+ hydrateErrorBoundaryElement(context, node, elementVar, indent);
120
+ return;
121
+ }
122
+ if (node.tag === "TransitionGroup") {
123
+ hydrateTransitionGroupElement(context, node, elementVar, indent);
124
+ return;
125
+ }
126
+ if (node.tag === "Transition") {
127
+ hydrateTransitionElement(context, node, elementVar, indent);
128
+ return;
129
+ }
98
130
  if (isComponentTag(node.tag)) {
99
131
  hydrateComponent(context, node, elementVar, indent);
100
132
  return;
@@ -134,6 +166,18 @@ function hydrateChildren(context, rawChildren, parentVar, indent) {
134
166
  hydrateAsyncBoundaryAtIndex(context, child, parentVar, domIndexVar, indent);
135
167
  return;
136
168
  }
169
+ if (child.type === "element" && child.tag === "ErrorBoundary") {
170
+ hydrateErrorBoundaryAtIndex(context, child, parentVar, domIndexVar, indent);
171
+ return;
172
+ }
173
+ if (child.type === "element" && child.tag === "TransitionGroup") {
174
+ hydrateTransitionGroupAtIndex(context, child, parentVar, domIndexVar, indent);
175
+ return;
176
+ }
177
+ if (child.type === "element" && child.tag === "Transition") {
178
+ hydrateTransitionAtIndex(context, child, parentVar, domIndexVar, indent);
179
+ return;
180
+ }
137
181
  if (child.type === "element" && getAttr(child, "v-if") && !getAttr(child, "v-pre")) {
138
182
  hydrateIf(context, child, parentVar, domIndexVar, indent);
139
183
  emit(context, indent, `${domIndexVar} += 1;`);
@@ -149,12 +193,12 @@ function hydrateChildren(context, rawChildren, parentVar, indent) {
149
193
  const elementCheck = isComponentTag(child.tag)
150
194
  ? `!${childVar} || ${childVar}.nodeType !== 1`
151
195
  : `!${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 {`);
196
+ emit(context, indent, `if (${elementCheck}) { __mikuru_recover(${quote(`Element mismatch: expected <${child.tag.toLowerCase()}>, got `)} + __mikuru_describeNode(${childVar})); } else {`);
153
197
  hydrateNode(context, child, childVar, indent + 1);
154
198
  emit(context, indent, "}");
155
199
  }
156
200
  else {
157
- emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_warn("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar}) + "."); } else {`);
201
+ emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_recover("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar})); } else {`);
158
202
  hydrateNode(context, child, childVar, indent + 1);
159
203
  emit(context, indent, "}");
160
204
  }
@@ -179,14 +223,14 @@ function hydrateTeleportAtIndex(context, node, parentVar, domIndex, indent) {
179
223
  emit(context, indent, `const ${startVar} = ${parentVar}.childNodes[${domIndex}];`);
180
224
  emit(context, indent, `const ${endVar} = ${startVar} ? __mikuru_findNextComment(${startVar}, ${quote(`/teleport:${id}`)}) : undefined;`);
181
225
  emit(context, indent, `if (!${startVar} || ${startVar}.nodeType !== 8 || ${startVar}.nodeValue !== ${quote(`teleport:${id}`)} || !${endVar}) {`);
182
- emit(context, indent + 1, "__mikuru_warn(\"Teleport marker mismatch.\");");
226
+ emit(context, indent + 1, "__mikuru_recover(\"Teleport marker mismatch\");");
183
227
  emit(context, indent, "} else {");
184
228
  emit(context, indent + 1, `const ${disabledVar} = Boolean(unwrap(${disabledExpression}));`);
185
229
  emit(context, indent + 1, `const ${targetVar} = ${disabledVar} ? undefined : (typeof unwrap(${toExpression}) === "string" ? document.querySelector(unwrap(${toExpression})) : unwrap(${toExpression}));`);
186
230
  emit(context, indent + 1, `if (!${disabledVar} && !${targetVar}) { __mikuru_warn("Teleport target was not found."); } else {`);
187
231
  emit(context, indent + 2, `const ${contentStartVar} = ${disabledVar} ? ${startVar} : __mikuru_findComment(${targetVar}, ${quote(`teleport content:${id}`)});`);
188
232
  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 {`);
233
+ emit(context, indent + 2, `if (!${contentStartVar} || !${contentEndVar}) { __mikuru_recover("Teleport content marker mismatch"); } else {`);
190
234
  emit(context, indent + 3, `const ${nodesVar} = [];`);
191
235
  emit(context, indent + 3, `let ${cursorVar} = ${contentStartVar}.nextSibling;`);
192
236
  emit(context, indent + 3, `while (${cursorVar} && ${cursorVar} !== ${contentEndVar}) { ${nodesVar}.push(${cursorVar}); ${cursorVar} = ${cursorVar}.nextSibling; }`);
@@ -211,11 +255,11 @@ function hydrateIf(context, node, parentVar, domIndex, indent) {
211
255
  : `!${childVar} || ${childVar}.nodeType !== 1 || ${childVar}.tagName?.toLowerCase() !== ${quote(node.tag.toLowerCase())}`;
212
256
  emit(context, indent, `const ${childVar} = ${parentVar}.childNodes[${domIndex}];`);
213
257
  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 {`);
258
+ emit(context, indent + 1, `if (${elementCheck}) { __mikuru_recover("Branch mismatch: dynamic v-if expected " + __mikuru_describeNode(${childVar})); } else {`);
215
259
  hydrateElement(context, withoutAttrs(node, ["v-if"]), childVar, indent + 2);
216
260
  emit(context, indent + 1, "}");
217
261
  emit(context, indent, `} else if (${childVar}) {`);
218
- emit(context, indent + 1, "__mikuru_warn(\"Branch mismatch; expected no v-if DOM for initial state.\");");
262
+ emit(context, indent + 1, "__mikuru_recover(\"Branch mismatch: expected no v-if DOM for initial state\");");
219
263
  emit(context, indent, "}");
220
264
  }
221
265
  function hydrateFor(context, node, parentVar, domIndex, indent, advanceVar) {
@@ -237,7 +281,7 @@ function hydrateFor(context, node, parentVar, domIndex, indent, advanceVar) {
237
281
  emit(context, indent + 1, `const ${forExpression.index} = __mikuru_index;`);
238
282
  }
239
283
  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 {`);
284
+ emit(context, indent + 1, `if (${elementCheck}) { __mikuru_recover("List mismatch: dynamic v-for expected row, got " + __mikuru_describeNode(${childVar})); } else {`);
241
285
  withTemplateRefMode(context, "array", () => {
242
286
  hydrateElement(context, withoutAttrs(node, ["v-for"]), childVar, indent + 2);
243
287
  });
@@ -374,6 +418,8 @@ function hydrateComponent(context, node, elementVar, indent) {
374
418
  emit(context, indent, `const ${propsVar} = {};`);
375
419
  hydrateComponentProps(context, node, propsVar, indent);
376
420
  emit(context, indent, `${propsVar}.__mikuru_context = __mikuru_context;`);
421
+ emitHydrationComponentSlots(context, node, propsVar, indent);
422
+ emitRouterViewRouteSlot(context, node, propsVar, indent);
377
423
  emit(context, indent, `if (${node.tag} && typeof ${node.tag}.hydrate === "function") {`);
378
424
  emit(context, indent + 1, `const __mikuru_child = ${node.tag}.hydrate(${elementVar}, ${propsVar});`);
379
425
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
@@ -391,9 +437,51 @@ function hydrateComponent(context, node, elementVar, indent) {
391
437
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
392
438
  hydrateTemplateRef(context, node, "__mikuru_child", indent + 1);
393
439
  emit(context, indent, "} else {");
394
- emit(context, indent + 1, `__mikuru_warn(${quote(`Component mismatch at <${node.tag}>.`)});`);
440
+ emit(context, indent + 1, `__mikuru_recover(${quote(`Component mismatch at <${node.tag}>`)});`);
395
441
  emit(context, indent, "}");
396
442
  }
443
+ function emitRouterViewRouteSlot(context, node, propsVar, indent) {
444
+ if (node.tag !== "RouterView")
445
+ return;
446
+ emit(context, indent, `if (typeof props.children === "function") { ${propsVar}.children = props.children; ${propsVar}.slots = { ...(${propsVar}.slots ?? {}), default: props.children }; }`);
447
+ }
448
+ function emitHydrationComponentSlots(context, node, propsVar, indent) {
449
+ const children = getDefaultHydrationSlotChildren(node);
450
+ if (!children || !hasMeaningfulHydrationChildren(children)) {
451
+ return;
452
+ }
453
+ const slotTargetVar = nextName(context, "slotTarget");
454
+ const slotPropsVar = nextName(context, "slotProps");
455
+ emit(context, indent, `${propsVar}.children = (${slotTargetVar}, ${slotPropsVar} = {}) => {`);
456
+ hydrateChildren(context, children, slotTargetVar, indent + 1);
457
+ emit(context, indent, "};");
458
+ emit(context, indent, `${propsVar}.slots = { ...(${propsVar}.slots ?? {}), default: ${propsVar}.children };`);
459
+ }
460
+ function getDefaultHydrationSlotChildren(node) {
461
+ const defaultChildren = [];
462
+ for (const child of node.children) {
463
+ if (child.type === "element" && child.tag === "template") {
464
+ const defaultSlotAttr = child.attrs.find(isDefaultSlotTemplateAttr);
465
+ if (defaultSlotAttr) {
466
+ return child.children;
467
+ }
468
+ if (child.attrs.some(isSlotTemplateAttr)) {
469
+ continue;
470
+ }
471
+ }
472
+ defaultChildren.push(child);
473
+ }
474
+ return defaultChildren;
475
+ }
476
+ function isDefaultSlotTemplateAttr(attr) {
477
+ return attr.name === "v-slot" || attr.name === "v-slot:default" || attr.name === "#default";
478
+ }
479
+ function isSlotTemplateAttr(attr) {
480
+ return attr.name === "v-slot" || attr.name.startsWith("v-slot:") || attr.name.startsWith("#");
481
+ }
482
+ function hasMeaningfulHydrationChildren(children) {
483
+ return children.some((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
484
+ }
397
485
  function hydrateDynamicComponentAtIndex(context, node, parentVar, domIndex, indent) {
398
486
  const isAttr = getDynamicComponentIsAttr(node);
399
487
  if (!isAttr) {
@@ -404,7 +492,7 @@ function hydrateDynamicComponentAtIndex(context, node, parentVar, domIndex, inde
404
492
  emit(context, indent, `const ${componentTypeVar} = unwrap(${compileHydrationExpression(context, requireAttrValue(isAttr), isAttr.name)});`);
405
493
  emit(context, indent, `if (${componentTypeVar}) {`);
406
494
  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 {`);
495
+ emit(context, indent + 1, `if (!${childVar} || ${childVar}.nodeType !== 1) { __mikuru_recover("Element mismatch: expected dynamic component root, got " + __mikuru_describeNode(${childVar})); } else {`);
408
496
  hydrateDynamicComponent(context, node, childVar, componentTypeVar, indent + 2);
409
497
  emit(context, indent + 1, "}");
410
498
  emit(context, indent + 1, `${domIndex} += 1;`);
@@ -416,6 +504,8 @@ function hydrateDynamicComponent(context, node, elementVar, componentType, inden
416
504
  emit(context, indent, `const ${propsVar} = {};`);
417
505
  hydrateComponentProps(context, dynamicNode, propsVar, indent);
418
506
  emit(context, indent, `${propsVar}.__mikuru_context = __mikuru_context;`);
507
+ emitHydrationComponentSlots(context, dynamicNode, propsVar, indent);
508
+ emitRouterViewRouteSlot(context, dynamicNode, propsVar, indent);
419
509
  emit(context, indent, `if (${componentType} && typeof ${componentType}.hydrate === "function") {`);
420
510
  emit(context, indent + 1, `const __mikuru_child = ${componentType}.hydrate(${elementVar}, ${propsVar});`);
421
511
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
@@ -433,7 +523,7 @@ function hydrateDynamicComponent(context, node, elementVar, componentType, inden
433
523
  emit(context, indent + 1, `if (__mikuru_child?.unmount) __mikuru_cleanup.push(() => __mikuru_child.unmount());`);
434
524
  hydrateTemplateRef(context, dynamicNode, "__mikuru_child", indent + 1);
435
525
  emit(context, indent, "} else {");
436
- emit(context, indent + 1, "__mikuru_warn(\"Dynamic component mismatch; :is did not resolve to hydrate() or mount().\");");
526
+ emit(context, indent + 1, "__mikuru_recover(\"Dynamic component mismatch: :is did not resolve to hydrate() or mount()\");");
437
527
  emit(context, indent, "}");
438
528
  }
439
529
  function hydrateKeepAliveAtIndex(context, node, parentVar, domIndex, indent) {
@@ -474,6 +564,84 @@ function hydrateAsyncBoundaryElement(context, node, elementVar, indent) {
474
564
  hydrateFragmentChildrenAtIndex(context, getAsyncBoundaryChildren(node), parentVar, indexVar, indent + 1);
475
565
  emit(context, indent, "}");
476
566
  }
567
+ function hydrateErrorBoundaryAtIndex(context, node, parentVar, domIndex, indent) {
568
+ validateErrorBoundaryAttributes(node);
569
+ getErrorBoundaryFallbackAttr(node);
570
+ hydrateFragmentChildrenAtIndex(context, [getSingleElementChild(node, "<ErrorBoundary>")], parentVar, domIndex, indent);
571
+ }
572
+ function hydrateErrorBoundaryElement(context, node, elementVar, indent) {
573
+ validateErrorBoundaryAttributes(node);
574
+ getErrorBoundaryFallbackAttr(node);
575
+ const parentVar = nextName(context, "errorBoundaryParent");
576
+ const indexVar = nextName(context, "errorBoundaryIndex");
577
+ emit(context, indent, `const ${parentVar} = ${elementVar}.parentNode;`);
578
+ emit(context, indent, `let ${indexVar} = ${parentVar} ? Array.prototype.indexOf.call(${parentVar}.childNodes, ${elementVar}) : 0;`);
579
+ emit(context, indent, `if (${parentVar}) {`);
580
+ hydrateFragmentChildrenAtIndex(context, [getSingleElementChild(node, "<ErrorBoundary>")], parentVar, indexVar, indent + 1);
581
+ emit(context, indent, "}");
582
+ }
583
+ function hydrateTransitionAtIndex(context, node, parentVar, domIndex, indent) {
584
+ validateTransitionAttributes(node);
585
+ const children = getTransitionChildren(node);
586
+ if (getAttr(children[0], "v-if")) {
587
+ hydrateTransitionIfChainAtIndex(context, children, parentVar, domIndex, indent);
588
+ return;
589
+ }
590
+ hydrateFragmentChildrenAtIndex(context, [children[0]], parentVar, domIndex, indent);
591
+ }
592
+ function hydrateTransitionElement(context, node, elementVar, indent) {
593
+ validateTransitionAttributes(node);
594
+ const parentVar = nextName(context, "transitionParent");
595
+ const indexVar = nextName(context, "transitionIndex");
596
+ emit(context, indent, `const ${parentVar} = ${elementVar}.parentNode;`);
597
+ emit(context, indent, `let ${indexVar} = ${parentVar} ? Array.prototype.indexOf.call(${parentVar}.childNodes, ${elementVar}) : 0;`);
598
+ emit(context, indent, `if (${parentVar}) {`);
599
+ hydrateTransitionAtIndex(context, node, parentVar, indexVar, indent + 1);
600
+ emit(context, indent, "}");
601
+ }
602
+ function hydrateTransitionIfChainAtIndex(context, children, parentVar, domIndex, indent) {
603
+ children.forEach((child, index) => {
604
+ if (index === 0) {
605
+ emit(context, indent, `if (unwrap(${compileHydrationExpression(context, getAttrValue(child, "v-if"), "v-if")})) {`);
606
+ }
607
+ else if (getAttr(child, "v-else-if")) {
608
+ emit(context, indent, `else if (unwrap(${compileHydrationExpression(context, getAttrValue(child, "v-else-if"), "v-else-if")})) {`);
609
+ }
610
+ else {
611
+ emit(context, indent, "else {");
612
+ }
613
+ hydrateFragmentChildrenAtIndex(context, [withoutAttrs(child, ["v-if", "v-else-if", "v-else"])], parentVar, domIndex, indent + 1);
614
+ emit(context, indent, "}");
615
+ });
616
+ }
617
+ function hydrateTransitionGroupAtIndex(context, node, parentVar, domIndex, indent) {
618
+ validateTransitionGroupAttributes(node);
619
+ const child = getTransitionGroupChild(node);
620
+ const groupVar = nextName(context, "transitionGroup");
621
+ const expectedTagVar = nextName(context, "transitionGroupTag");
622
+ const groupIndexVar = nextName(context, "transitionGroupIndex");
623
+ emit(context, indent, `const ${groupVar} = ${parentVar}.childNodes[${domIndex}];`);
624
+ emit(context, indent, `const ${expectedTagVar} = String(unwrap(${getTransitionGroupTagExpression(context, node)}) ?? "span").toLowerCase();`);
625
+ emit(context, indent, `if (!${groupVar} || ${groupVar}.nodeType !== 1 || ${groupVar}.tagName?.toLowerCase() !== ${expectedTagVar}) {`);
626
+ emit(context, indent + 1, `__mikuru_recover("TransitionGroup mismatch: expected <" + ${expectedTagVar} + ">, got " + __mikuru_describeNode(${groupVar}));`);
627
+ emit(context, indent, "} else {");
628
+ emit(context, indent + 1, `let ${groupIndexVar} = 0;`);
629
+ hydrateFor(context, withoutAttrs(child, [":key", "v-bind:key"]), groupVar, groupIndexVar, indent + 1, groupIndexVar);
630
+ 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(", ") + "."); }`);
631
+ emit(context, indent, "}");
632
+ emit(context, indent, `${domIndex} += 1;`);
633
+ }
634
+ function hydrateTransitionGroupElement(context, node, elementVar, indent) {
635
+ validateTransitionGroupAttributes(node);
636
+ const child = getTransitionGroupChild(node);
637
+ const expectedTagVar = nextName(context, "transitionGroupTag");
638
+ const groupIndexVar = nextName(context, "transitionGroupIndex");
639
+ emit(context, indent, `const ${expectedTagVar} = String(unwrap(${getTransitionGroupTagExpression(context, node)}) ?? "span").toLowerCase();`);
640
+ emit(context, indent, `if (${elementVar}.tagName?.toLowerCase() !== ${expectedTagVar}) { __mikuru_recover("TransitionGroup mismatch: expected <" + ${expectedTagVar} + ">, got " + __mikuru_describeNode(${elementVar})); }`);
641
+ emit(context, indent, `let ${groupIndexVar} = 0;`);
642
+ hydrateFor(context, withoutAttrs(child, [":key", "v-bind:key"]), elementVar, groupIndexVar, indent, groupIndexVar);
643
+ 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(", ") + "."); }`);
644
+ }
477
645
  function hydrateFragmentChildrenAtIndex(context, rawChildren, parentVar, domIndex, indent) {
478
646
  const children = rawChildren.filter(isHydratableNode);
479
647
  children.forEach((child) => {
@@ -493,6 +661,18 @@ function hydrateFragmentChildrenAtIndex(context, rawChildren, parentVar, domInde
493
661
  hydrateAsyncBoundaryAtIndex(context, child, parentVar, domIndex, indent);
494
662
  return;
495
663
  }
664
+ if (child.type === "element" && child.tag === "ErrorBoundary") {
665
+ hydrateErrorBoundaryAtIndex(context, child, parentVar, domIndex, indent);
666
+ return;
667
+ }
668
+ if (child.type === "element" && child.tag === "TransitionGroup") {
669
+ hydrateTransitionGroupAtIndex(context, child, parentVar, domIndex, indent);
670
+ return;
671
+ }
672
+ if (child.type === "element" && child.tag === "Transition") {
673
+ hydrateTransitionAtIndex(context, child, parentVar, domIndex, indent);
674
+ return;
675
+ }
496
676
  if (child.type === "element" && getAttr(child, "v-if") && !getAttr(child, "v-pre")) {
497
677
  hydrateIf(context, child, parentVar, domIndex, indent);
498
678
  emit(context, indent, `${domIndex} += 1;`);
@@ -508,12 +688,12 @@ function hydrateFragmentChildrenAtIndex(context, rawChildren, parentVar, domInde
508
688
  const elementCheck = isComponentTag(child.tag)
509
689
  ? `!${childVar} || ${childVar}.nodeType !== 1`
510
690
  : `!${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 {`);
691
+ emit(context, indent, `if (${elementCheck}) { __mikuru_recover(${quote(`Element mismatch: expected <${child.tag.toLowerCase()}>, got `)} + __mikuru_describeNode(${childVar})); } else {`);
512
692
  hydrateNode(context, child, childVar, indent + 1);
513
693
  emit(context, indent, "}");
514
694
  }
515
695
  else {
516
- emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_warn("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar}) + "."); } else {`);
696
+ emit(context, indent, `if (!${childVar} || ${childVar}.nodeType !== 3) { __mikuru_recover("Text mismatch: expected text, got " + __mikuru_describeNode(${childVar})); } else {`);
517
697
  hydrateNode(context, child, childVar, indent + 1);
518
698
  emit(context, indent, "}");
519
699
  }
@@ -892,6 +1072,62 @@ function validateAsyncBoundaryAttributes(node) {
892
1072
  throw new Error(`Unsupported attribute "${attr.name}" on <AsyncBoundary>. Supported attributes: loading, fallback, delay, and timeout.`);
893
1073
  }
894
1074
  }
1075
+ function validateErrorBoundaryAttributes(node) {
1076
+ for (const attr of node.attrs) {
1077
+ const name = parseBindDirective(attr.name)?.name ?? attr.name;
1078
+ if (name === "fallback" || name === "reset-key") {
1079
+ continue;
1080
+ }
1081
+ throw new Error(`Unsupported attribute "${attr.name}" on <ErrorBoundary>. Supported attributes: fallback and reset-key.`);
1082
+ }
1083
+ }
1084
+ function validateTransitionAttributes(node) {
1085
+ const supported = new Set([
1086
+ "name",
1087
+ "appear",
1088
+ "mode",
1089
+ "enter-from-class",
1090
+ "enter-active-class",
1091
+ "enter-to-class",
1092
+ "leave-from-class",
1093
+ "leave-active-class",
1094
+ "leave-to-class"
1095
+ ]);
1096
+ for (const attr of node.attrs) {
1097
+ const name = parseBindDirective(attr.name)?.name ?? attr.name;
1098
+ if (supported.has(name)) {
1099
+ continue;
1100
+ }
1101
+ throw new Error(`Unsupported attribute "${attr.name}" on <Transition>. Supported attributes: name, appear, mode, and CSS class override attributes.`);
1102
+ }
1103
+ }
1104
+ function validateTransitionGroupAttributes(node) {
1105
+ const supported = new Set([
1106
+ "name",
1107
+ "tag",
1108
+ "enter-from-class",
1109
+ "enter-active-class",
1110
+ "enter-to-class",
1111
+ "leave-from-class",
1112
+ "leave-active-class",
1113
+ "leave-to-class",
1114
+ "move-class"
1115
+ ]);
1116
+ for (const attr of node.attrs) {
1117
+ const name = parseBindDirective(attr.name)?.name ?? attr.name;
1118
+ if (supported.has(name)) {
1119
+ continue;
1120
+ }
1121
+ throw new Error(`Unsupported attribute "${attr.name}" on <TransitionGroup>. Supported attributes: name, tag, and CSS class override attributes.`);
1122
+ }
1123
+ }
1124
+ function getErrorBoundaryFallbackAttr(node) {
1125
+ const attr = node.attrs.find((candidate) => parseBindDirective(candidate.name)?.name === "fallback");
1126
+ if (!attr) {
1127
+ throw new Error("<ErrorBoundary> requires :fallback to resolve to a component object");
1128
+ }
1129
+ return attr;
1130
+ }
895
1131
  function getAsyncBoundaryChildren(node) {
896
1132
  const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
897
1133
  if (meaningful.length === 0) {
@@ -899,6 +1135,42 @@ function getAsyncBoundaryChildren(node) {
899
1135
  }
900
1136
  return node.children;
901
1137
  }
1138
+ function getTransitionChildren(node) {
1139
+ const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
1140
+ if (meaningful.length === 0 || meaningful.some((child) => child.type !== "element")) {
1141
+ throw new Error("<Transition> requires exactly one element/component child or one v-if chain");
1142
+ }
1143
+ const children = meaningful;
1144
+ if (children.length === 1) {
1145
+ return children;
1146
+ }
1147
+ if (!getAttr(children[0], "v-if")) {
1148
+ throw new Error("<Transition> requires exactly one element/component child or one v-if chain");
1149
+ }
1150
+ for (const child of children.slice(1)) {
1151
+ if (!getAttr(child, "v-else-if") && !getAttr(child, "v-else")) {
1152
+ throw new Error("<Transition> only accepts multiple children when they form a v-if chain");
1153
+ }
1154
+ }
1155
+ return children;
1156
+ }
1157
+ function getTransitionGroupChild(node) {
1158
+ const child = getSingleElementChild(node, "<TransitionGroup>");
1159
+ if (!getAttr(child, "v-for") || !getKeyExpression(child)) {
1160
+ throw new Error("<TransitionGroup> requires a single keyed v-for child in v1");
1161
+ }
1162
+ return child;
1163
+ }
1164
+ function getTransitionGroupTagExpression(context, node) {
1165
+ const dynamicTag = node.attrs.find((attr) => parseBindDirective(attr.name)?.name === "tag");
1166
+ if (dynamicTag) {
1167
+ return compileHydrationExpression(context, requireAttrValue(dynamicTag), dynamicTag.name);
1168
+ }
1169
+ return quote(getStaticAttrValue(node, "tag") ?? "span");
1170
+ }
1171
+ function getKeyExpression(node) {
1172
+ return getStaticAttrValue(node, ":key") ?? getStaticAttrValue(node, "v-bind:key");
1173
+ }
902
1174
  function getSingleElementChild(node, label) {
903
1175
  const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
904
1176
  if (meaningful.length !== 1 || meaningful[0]?.type !== "element") {