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.
- package/CHANGELOG.md +34 -0
- package/README.md +11 -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 +580 -22
- package/dist/compiler/generateHydration.js.map +1 -1
- package/dist/compiler/generateSsr.js +339 -8
- 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 +6 -1
- package/dist/runtime/asyncComponent.js +125 -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 +7 -1
- package/dist/server.js +21 -6
- 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
|
@@ -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
|
-
|
|
12
|
-
|
|
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
|
|
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().");
|
|
64
|
-
|
|
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, "
|
|
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 (
|
|
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}) {
|
|
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) {
|
|
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, "
|
|
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}) {
|
|
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}) {
|
|
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, "
|
|
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}) {
|
|
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, `
|
|
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) {
|
|
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, "
|
|
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}) {
|
|
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) {
|
|
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;
|