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 +18 -0
- package/README.md +9 -5
- package/dist/compiler/compileHydration.js +2 -2
- package/dist/compiler/compileHydration.js.map +1 -1
- package/dist/compiler/generateHydration.d.ts +5 -1
- package/dist/compiler/generateHydration.js +292 -20
- package/dist/compiler/generateHydration.js.map +1 -1
- package/dist/compiler/generateSsr.js +151 -0
- package/dist/compiler/generateSsr.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/router/index.d.ts +8 -0
- package/dist/router/index.js +117 -0
- package/dist/router/index.js.map +1 -1
- package/dist/runtime/asyncComponent.d.ts +5 -1
- package/dist/runtime/asyncComponent.js +30 -0
- package/dist/runtime/asyncComponent.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -1
- package/dist/vite.js +14 -10
- package/dist/vite.js.map +1 -1
- package/package.json +8 -2
- package/types/env.d.ts +58 -29
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
|
|
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}\
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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().");
|
|
64
|
-
|
|
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, "
|
|
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}) {
|
|
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) {
|
|
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, "
|
|
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}) {
|
|
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}) {
|
|
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, "
|
|
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}) {
|
|
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, `
|
|
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) {
|
|
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, "
|
|
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}) {
|
|
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) {
|
|
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") {
|