mikuru 1.0.19 → 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 +92 -0
- package/README.md +23 -13
- package/dist/compiler/analyzeTemplate.js +116 -12
- package/dist/compiler/analyzeTemplate.js.map +1 -1
- package/dist/compiler/compile.js +2 -2
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/compileHydration.d.ts +2 -0
- package/dist/compiler/compileHydration.js +27 -0
- package/dist/compiler/compileHydration.js.map +1 -0
- package/dist/compiler/compileSsr.d.ts +2 -0
- package/dist/compiler/compileSsr.js +21 -0
- package/dist/compiler/compileSsr.js.map +1 -0
- package/dist/compiler/generate.d.ts +6 -1
- package/dist/compiler/generate.js +1399 -93
- package/dist/compiler/generate.js.map +1 -1
- package/dist/compiler/generateHydration.d.ts +6 -0
- package/dist/compiler/generateHydration.js +1553 -0
- package/dist/compiler/generateHydration.js.map +1 -0
- package/dist/compiler/generateSsr.d.ts +2 -0
- package/dist/compiler/generateSsr.js +915 -0
- package/dist/compiler/generateSsr.js.map +1 -0
- package/dist/compiler/index.d.ts +3 -1
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/index.js.map +1 -1
- package/dist/compiler/parseTemplate.js +16 -1
- package/dist/compiler/parseTemplate.js.map +1 -1
- package/dist/compiler/sourceMap.d.ts +2 -2
- package/dist/compiler/sourceMap.js +110 -6
- package/dist/compiler/sourceMap.js.map +1 -1
- package/dist/compiler/types.d.ts +8 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/router/index.d.ts +8 -0
- package/dist/router/index.js +133 -3
- package/dist/router/index.js.map +1 -1
- package/dist/runtime/asyncComponent.d.ts +22 -2
- package/dist/runtime/asyncComponent.js +84 -2
- package/dist/runtime/asyncComponent.js.map +1 -1
- package/dist/runtime/devtools.d.ts +51 -0
- package/dist/runtime/devtools.js +113 -0
- package/dist/runtime/devtools.js.map +1 -0
- package/dist/runtime/dom.d.ts +5 -1
- package/dist/runtime/dom.js +100 -3
- package/dist/runtime/dom.js.map +1 -1
- package/dist/runtime/index.d.ts +7 -5
- package/dist/runtime/index.js +3 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/lifecycle.d.ts +6 -0
- package/dist/runtime/lifecycle.js +66 -10
- package/dist/runtime/lifecycle.js.map +1 -1
- package/dist/runtime/reactivity.d.ts +33 -1
- package/dist/runtime/reactivity.js +254 -13
- package/dist/runtime/reactivity.js.map +1 -1
- package/dist/server.d.ts +33 -0
- package/dist/server.js +285 -0
- package/dist/server.js.map +1 -0
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +39 -18
- package/dist/vite.js.map +1 -1
- package/package.json +110 -100
- package/types/env.d.ts +58 -29
|
@@ -1,33 +1,53 @@
|
|
|
1
|
-
import { parse } from "acorn";
|
|
1
|
+
import { parse, parseExpressionAt } from "acorn";
|
|
2
2
|
import { createCompileError } from "./errors.js";
|
|
3
3
|
import { compileTemplateExpression, parseForExpression, validateAssignableExpression, validateTemplateExpression } from "./parseExpression.js";
|
|
4
|
-
export function generate(descriptor, root) {
|
|
4
|
+
export function generate(descriptor, root, options = {}) {
|
|
5
5
|
const context = {
|
|
6
6
|
lines: [],
|
|
7
7
|
index: 0,
|
|
8
8
|
source: descriptor.source,
|
|
9
9
|
filename: descriptor.filename,
|
|
10
|
-
scopeAttr: descriptor.styleScoped ? createScopeAttr(descriptor) : undefined
|
|
10
|
+
scopeAttr: descriptor.styleScoped ? createScopeAttr(descriptor) : undefined,
|
|
11
|
+
debug: options.debug === true,
|
|
12
|
+
batchedUpdates: options.batchedUpdates === true
|
|
11
13
|
};
|
|
12
14
|
const script = normalizeScript(descriptor);
|
|
13
15
|
for (const importLine of script.imports) {
|
|
14
16
|
emit(context, 0, importLine);
|
|
15
17
|
}
|
|
16
|
-
const
|
|
18
|
+
const runtimeBaseImports = ["computed", "effect", "ref", "setAttribute", "unwrap"];
|
|
19
|
+
if (context.batchedUpdates) {
|
|
20
|
+
runtimeBaseImports.push("queueJob");
|
|
21
|
+
}
|
|
22
|
+
if (context.debug) {
|
|
23
|
+
runtimeBaseImports.push("emitDebugEvent", "registerDebugComponent");
|
|
24
|
+
}
|
|
25
|
+
const runtimeImports = mergeRuntimeImports(runtimeBaseImports, script.runtimeImports);
|
|
17
26
|
emit(context, 0, `import { ${runtimeImports.join(", ")} } from "mikuru/runtime";`);
|
|
18
27
|
emit(context, 0, "");
|
|
28
|
+
if (context.batchedUpdates) {
|
|
29
|
+
emit(context, 0, "const __mikuru_effect = (fn) => effect(fn, { scheduler: queueJob });");
|
|
30
|
+
emit(context, 0, "");
|
|
31
|
+
}
|
|
19
32
|
emit(context, 0, "export function mount(target, props = {}) {");
|
|
20
33
|
emit(context, 1, `const __mikuru_componentInfo = { component: ${quote(descriptor.filename ?? "anonymous.mikuru")}, filename: ${quote(descriptor.filename ?? "anonymous.mikuru")} };`);
|
|
34
|
+
emitDevtoolsRegistration(context, 1);
|
|
21
35
|
emit(context, 1, "const __mikuru_cleanup = [];");
|
|
22
36
|
emit(context, 1, "const __mikuru_afterUnmount = [];");
|
|
23
37
|
emit(context, 1, "const __mikuru_mounted = [];");
|
|
24
|
-
emit(context, 1, "const
|
|
38
|
+
emit(context, 1, "const __mikuru_activated = [];");
|
|
39
|
+
emit(context, 1, "const __mikuru_deactivated = [];");
|
|
40
|
+
emit(context, 1, `const __mikuru_context = { parent: props.__mikuru_context, provides: new Map(), errorHandler: props.__mikuru_context?.errorHandler${context.debug ? ", debugId: __mikuru_debug.id" : ""}, ...__mikuru_componentInfo };`);
|
|
25
41
|
emit(context, 1, "const __mikuru_errorInfo = (phase) => ({ ...__mikuru_componentInfo, phase });");
|
|
26
42
|
emit(context, 1, "const __mikuru_reportError = (error, errorHandler = __mikuru_context.errorHandler, phase = \"runtime\") => {");
|
|
43
|
+
if (context.debug) {
|
|
44
|
+
emit(context, 2, "emitDebugEvent(\"component:error\", { component: __mikuru_componentInfo, error, errorInfo: __mikuru_errorInfo(phase), componentId: __mikuru_debug.id });");
|
|
45
|
+
}
|
|
27
46
|
emit(context, 2, "if (typeof errorHandler === \"function\") { Promise.resolve().then(() => errorHandler(error, __mikuru_errorInfo(phase))); return; }");
|
|
28
47
|
emit(context, 2, "setTimeout(() => { throw error; });");
|
|
29
48
|
emit(context, 1, "};");
|
|
30
49
|
emit(context, 1, "const __mikuru_try = (fn, errorHandler, phase) => { try { return fn(); } catch (error) { __mikuru_reportError(error, errorHandler, phase); } };");
|
|
50
|
+
emit(context, 1, "const __mikuru_memoEqual = (previous, next) => Array.isArray(previous) && Array.isArray(next) && previous.length === next.length && previous.every((value, index) => Object.is(value, next[index]));");
|
|
31
51
|
emit(context, 1, "const __mikuru_guardEventHandler = (fn, errorHandler = __mikuru_context.errorHandler) => (...args) => __mikuru_try(() => fn(...args), errorHandler, \"event\");");
|
|
32
52
|
emit(context, 1, "const __mikuru_runCleanup = (cleanups) => {");
|
|
33
53
|
emit(context, 2, "for (const cleanup of cleanups.splice(0).reverse()) {");
|
|
@@ -66,6 +86,12 @@ export function generate(descriptor, root) {
|
|
|
66
86
|
emit(context, 3, "__mikuru_transitionDone(element, () => element.classList.remove(active, to));");
|
|
67
87
|
emit(context, 2, "});");
|
|
68
88
|
emit(context, 1, "};");
|
|
89
|
+
emit(context, 1, "const __mikuru_applyTransitionMove = (element, transition) => {");
|
|
90
|
+
emit(context, 2, "if (!element || element.nodeType !== 1 || !transition?.name) { return; }");
|
|
91
|
+
emit(context, 2, "const move = transition.moveClass || `${transition.name}-move`;");
|
|
92
|
+
emit(context, 2, "element.classList.add(move);");
|
|
93
|
+
emit(context, 2, "__mikuru_transitionFrame(() => __mikuru_transitionDone(element, () => element.classList.remove(move)));");
|
|
94
|
+
emit(context, 1, "};");
|
|
69
95
|
emit(context, 1, "const __mikuru_removeNode = (node) => {");
|
|
70
96
|
emit(context, 2, "if (!node || !node.parentNode) { return; }");
|
|
71
97
|
emit(context, 2, "if (node.nodeType !== 1 || !node.__mikuru_transition || node.__mikuru_transitionLeaving) { node.remove(); return; }");
|
|
@@ -102,6 +128,8 @@ export function generate(descriptor, root) {
|
|
|
102
128
|
emit(context, 1, "const __mikuru_previousRegistrar = globalThis.__mikuru_currentRegistrar;");
|
|
103
129
|
emit(context, 1, "globalThis.__mikuru_currentRegistrar = {");
|
|
104
130
|
emit(context, 2, "registerMounted: (fn) => __mikuru_mounted.push(fn),");
|
|
131
|
+
emit(context, 2, "registerActivated: (fn) => __mikuru_activated.push(fn),");
|
|
132
|
+
emit(context, 2, "registerDeactivated: (fn) => __mikuru_deactivated.push(fn),");
|
|
105
133
|
emit(context, 2, "registerBeforeUnmount: (fn) => __mikuru_cleanup.push(fn),");
|
|
106
134
|
emit(context, 2, "registerUnmounted: (fn) => __mikuru_afterUnmount.push(fn),");
|
|
107
135
|
emit(context, 2, "provide: (key, value) => __mikuru_context.provides.set(key, value),");
|
|
@@ -140,12 +168,23 @@ export function generate(descriptor, root) {
|
|
|
140
168
|
emit(context, 1, "");
|
|
141
169
|
}
|
|
142
170
|
const rootVar = generateNode(context, root, "target", "__mikuru_cleanup", 1);
|
|
171
|
+
emitDevtoolsRootUpdate(context, rootVar, 1);
|
|
143
172
|
emit(context, 1, "// call mounted callbacks registered during setup and remove registrar");
|
|
144
173
|
emit(context, 1, "for (const cb of __mikuru_mounted.splice(0)) { __mikuru_try(cb, undefined, \"mounted\"); }");
|
|
145
174
|
emit(context, 1, "if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; }");
|
|
146
175
|
emit(context, 1, "return {");
|
|
147
176
|
emit(context, 2, `element: ${rootVar},`);
|
|
177
|
+
emit(context, 2, "activate() {");
|
|
178
|
+
emit(context, 3, "for (const cb of __mikuru_activated) { __mikuru_try(cb, undefined, \"activated\"); }");
|
|
179
|
+
emit(context, 2, "},");
|
|
180
|
+
emit(context, 2, "deactivate() {");
|
|
181
|
+
emit(context, 3, "for (const cb of __mikuru_deactivated) { __mikuru_try(cb, undefined, \"deactivated\"); }");
|
|
182
|
+
emit(context, 2, "},");
|
|
148
183
|
emit(context, 2, "unmount() {");
|
|
184
|
+
if (context.debug) {
|
|
185
|
+
emit(context, 3, "emitDebugEvent(\"component:unmount\", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id });");
|
|
186
|
+
emit(context, 3, "__mikuru_unregisterDevtools();");
|
|
187
|
+
}
|
|
149
188
|
emit(context, 3, "__mikuru_runCleanup(__mikuru_cleanup);");
|
|
150
189
|
emit(context, 3, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) { __mikuru_try(cb, undefined, \"unmounted\"); }");
|
|
151
190
|
emit(context, 3, `__mikuru_removeNode(${rootVar});`);
|
|
@@ -155,7 +194,10 @@ export function generate(descriptor, root) {
|
|
|
155
194
|
emit(context, 0, "");
|
|
156
195
|
emit(context, 0, `const __mikuru_component = { mount${script.inheritAttrs ? "" : ", inheritAttrs: false"} };`);
|
|
157
196
|
emit(context, 0, "export default __mikuru_component;");
|
|
158
|
-
|
|
197
|
+
const lines = context.batchedUpdates
|
|
198
|
+
? context.lines.map((line) => line.replace("= effect(() => {", "= __mikuru_effect(() => {"))
|
|
199
|
+
: context.lines;
|
|
200
|
+
return `${lines.join("\n")}\n`;
|
|
159
201
|
}
|
|
160
202
|
function emitStyleInjection(context, descriptor, indent) {
|
|
161
203
|
const styleId = `mikuru-${hash(`${descriptor.filename ?? ""}\n${descriptor.style ?? ""}`)}`;
|
|
@@ -169,10 +211,42 @@ function emitStyleInjection(context, descriptor, indent) {
|
|
|
169
211
|
emit(context, indent + 1, "document.head.appendChild(style);");
|
|
170
212
|
emit(context, indent, "}");
|
|
171
213
|
}
|
|
214
|
+
function emitDevtoolsRegistration(context, indent) {
|
|
215
|
+
if (!context.debug) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
emit(context, indent, "const __mikuru_publicProps = Object.fromEntries(Object.entries(props).filter(([key]) => !key.startsWith(\"__mikuru_\")));");
|
|
219
|
+
emit(context, indent, "const __mikuru_debugAttrs = props.__mikuru_attrs && typeof props.__mikuru_attrs === \"object\" ? { ...props.__mikuru_attrs } : {};");
|
|
220
|
+
emit(context, indent, "const __mikuru_debug = registerDebugComponent({");
|
|
221
|
+
emit(context, indent + 1, "name: __mikuru_componentInfo.component,");
|
|
222
|
+
emit(context, indent + 1, "filename: __mikuru_componentInfo.filename,");
|
|
223
|
+
emit(context, indent + 1, "parentId: props.__mikuru_context?.debugId,");
|
|
224
|
+
emit(context, indent + 1, "props: __mikuru_publicProps,");
|
|
225
|
+
emit(context, indent + 1, "propKeys: Object.keys(__mikuru_publicProps),");
|
|
226
|
+
emit(context, indent + 1, "attrs: __mikuru_debugAttrs,");
|
|
227
|
+
emit(context, indent + 1, "attrKeys: Object.keys(__mikuru_debugAttrs)");
|
|
228
|
+
emit(context, indent, "});");
|
|
229
|
+
emit(context, indent, "const __mikuru_unregisterDevtools = () => __mikuru_debug.unregister();");
|
|
230
|
+
}
|
|
231
|
+
function emitDevtoolsRootUpdate(context, rootVar, indent) {
|
|
232
|
+
if (!context.debug) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
emit(context, indent, `__mikuru_debug.update({ root: ${rootVar} });`);
|
|
236
|
+
emit(context, indent, "emitDebugEvent(\"component:mount\", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, root: __mikuru_debug.metadata.root, props: __mikuru_debug.metadata.props, attrs: __mikuru_debug.metadata.attrs });");
|
|
237
|
+
}
|
|
172
238
|
function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
173
239
|
if (node.type === "text") {
|
|
174
240
|
return generateText(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
175
241
|
}
|
|
242
|
+
if (hasAttr(node, "v-pre")) {
|
|
243
|
+
validatePreAttribute(context, node);
|
|
244
|
+
return generatePreElement(context, node, parentVar, indent, beforeVar);
|
|
245
|
+
}
|
|
246
|
+
if (hasAttr(node, "v-once") && !hasAttr(node, "v-for")) {
|
|
247
|
+
validateOnceAttribute(context, node);
|
|
248
|
+
return withOnceMode(context, () => generateNode(context, withoutAttrs(node, ["v-once"]), parentVar, cleanupVar, indent, beforeVar));
|
|
249
|
+
}
|
|
176
250
|
const forExpression = getStringAttr(node, "v-for");
|
|
177
251
|
if (forExpression) {
|
|
178
252
|
return generateFor(context, node, parentVar, cleanupVar, indent, forExpression, beforeVar);
|
|
@@ -191,12 +265,21 @@ function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
|
191
265
|
if (node.tag === "Transition") {
|
|
192
266
|
return generateTransition(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
193
267
|
}
|
|
268
|
+
if (node.tag === "TransitionGroup") {
|
|
269
|
+
return generateTransitionGroup(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
270
|
+
}
|
|
194
271
|
if (node.tag === "Teleport") {
|
|
195
272
|
return generateTeleport(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
196
273
|
}
|
|
274
|
+
if (node.tag === "AsyncBoundary") {
|
|
275
|
+
return generateAsyncBoundary(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
276
|
+
}
|
|
197
277
|
if (node.tag === "ErrorBoundary") {
|
|
198
278
|
return generateErrorBoundary(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
199
279
|
}
|
|
280
|
+
if (node.tag === "KeepAlive") {
|
|
281
|
+
return generateKeepAlive(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
282
|
+
}
|
|
200
283
|
if (isComponentTag(node.tag)) {
|
|
201
284
|
return generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
202
285
|
}
|
|
@@ -221,6 +304,30 @@ function generateTransition(context, node, parentVar, cleanupVar, indent, before
|
|
|
221
304
|
emitTransitionRegistration(context, childVar, transitionVar, indent);
|
|
222
305
|
return childVar;
|
|
223
306
|
}
|
|
307
|
+
function generateTransitionGroup(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
308
|
+
validateTransitionGroupAttributes(context, node);
|
|
309
|
+
const children = getSingleElementChild(context, node, "<TransitionGroup>");
|
|
310
|
+
const child = children[0];
|
|
311
|
+
const forExpression = getStringAttr(child, "v-for");
|
|
312
|
+
const keyExpression = getKeyExpression(child);
|
|
313
|
+
if (!forExpression || !keyExpression) {
|
|
314
|
+
throwTemplateError("<TransitionGroup> requires a single keyed v-for child in v1", context, child.loc);
|
|
315
|
+
}
|
|
316
|
+
const { item: itemName, index: indexName, source: sourceExpression } = parseForExpression(forExpression, toExpressionContext(context, getStringAttrLocation(child, "v-for")));
|
|
317
|
+
const groupVar = nextVar(context, "transitionGroup");
|
|
318
|
+
const groupCleanupVar = nextVar(context, "transitionGroupCleanup");
|
|
319
|
+
const transitionVar = nextVar(context, "transition");
|
|
320
|
+
emit(context, indent, `const ${transitionVar} = ${getTransitionOptionsExpression(context, node)};`);
|
|
321
|
+
emit(context, indent, `const ${groupVar} = document.createElement(String(unwrap(${getTransitionAttrExpression(context, node, "tag", "span")}) ?? "span"));`);
|
|
322
|
+
appendNode(context, parentVar, groupVar, indent, beforeVar);
|
|
323
|
+
emit(context, indent, `const ${groupCleanupVar} = [];`);
|
|
324
|
+
generateKeyedFor(context, child, groupVar, groupCleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, undefined, transitionVar);
|
|
325
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
326
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${groupCleanupVar});`);
|
|
327
|
+
emit(context, indent + 1, `__mikuru_removeNode(${groupVar});`);
|
|
328
|
+
emit(context, indent, "});");
|
|
329
|
+
return groupVar;
|
|
330
|
+
}
|
|
224
331
|
function generateTeleport(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
225
332
|
validateTeleportAttributes(context, node);
|
|
226
333
|
const toExpression = getTeleportToExpression(context, node);
|
|
@@ -312,6 +419,9 @@ function generateErrorBoundary(context, node, parentVar, cleanupVar, indent, bef
|
|
|
312
419
|
emit(context, indent + 1, `const ${fallbackVar} = unwrap(${fallbackExpression});`);
|
|
313
420
|
emit(context, indent + 1, `if (!${fallbackVar} || typeof ${fallbackVar}.mount !== "function") { throw ${errorVar}; }`);
|
|
314
421
|
emit(context, indent + 1, `const ${normalizedErrorInfoVar} = ${errorInfoVar} && typeof ${errorInfoVar} === "object" ? ${errorInfoVar} : {};`);
|
|
422
|
+
if (context.debug) {
|
|
423
|
+
emit(context, indent + 1, `emitDebugEvent("component:error", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, error: ${errorVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo } });`);
|
|
424
|
+
}
|
|
315
425
|
emit(context, indent + 1, `const ${fallbackFragmentVar} = document.createDocumentFragment();`);
|
|
316
426
|
emit(context, indent + 1, `const ${fallbackInstanceVar} = ${fallbackVar}.mount(${fallbackFragmentVar}, { error: ${errorVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo }, retry: ${renderVar}, reset: ${renderVar}, __mikuru_context });`);
|
|
317
427
|
emit(context, indent + 1, `${boundaryCleanupVar}.push(() => ${fallbackInstanceVar}.unmount());`);
|
|
@@ -354,6 +464,171 @@ function generateErrorBoundary(context, node, parentVar, cleanupVar, indent, bef
|
|
|
354
464
|
emit(context, indent, "});");
|
|
355
465
|
return startVar;
|
|
356
466
|
}
|
|
467
|
+
function generateAsyncBoundary(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
468
|
+
validateAsyncBoundaryAttributes(context, node);
|
|
469
|
+
const children = getAsyncBoundaryChildren(context, node);
|
|
470
|
+
const loadingExpression = getAsyncBoundaryLoadingExpression(context, node);
|
|
471
|
+
const fallbackExpression = getAsyncBoundaryFallbackExpression(context, node);
|
|
472
|
+
const delayExpression = getAsyncBoundaryDelayExpression(context, node);
|
|
473
|
+
const timeoutExpression = getAsyncBoundaryTimeoutExpression(context, node);
|
|
474
|
+
const startVar = nextVar(context, "asyncBoundaryStart");
|
|
475
|
+
const endVar = nextVar(context, "asyncBoundaryEnd");
|
|
476
|
+
const boundaryCleanupVar = nextVar(context, "asyncBoundaryCleanup");
|
|
477
|
+
const loadingCleanupVar = nextVar(context, "asyncBoundaryLoadingCleanup");
|
|
478
|
+
const fallbackCleanupVar = nextVar(context, "asyncBoundaryFallbackCleanup");
|
|
479
|
+
const pendingVar = nextVar(context, "asyncBoundaryPending");
|
|
480
|
+
const failedVar = nextVar(context, "asyncBoundaryFailed");
|
|
481
|
+
const lastRetryVar = nextVar(context, "asyncBoundaryRetry");
|
|
482
|
+
const renderLoadingVar = nextVar(context, "renderAsyncBoundaryLoading");
|
|
483
|
+
const clearLoadingVar = nextVar(context, "clearAsyncBoundaryLoading");
|
|
484
|
+
const renderFallbackVar = nextVar(context, "renderAsyncBoundaryFallback");
|
|
485
|
+
const clearFallbackVar = nextVar(context, "clearAsyncBoundaryFallback");
|
|
486
|
+
const scheduleLoadingVar = nextVar(context, "scheduleAsyncBoundaryLoading");
|
|
487
|
+
const scheduleTimeoutVar = nextVar(context, "scheduleAsyncBoundaryTimeout");
|
|
488
|
+
const clearTimersVar = nextVar(context, "clearAsyncBoundaryTimers");
|
|
489
|
+
const renderVar = nextVar(context, "renderAsyncBoundary");
|
|
490
|
+
const loadingVar = nextVar(context, "asyncBoundaryLoading");
|
|
491
|
+
const loadingFragmentVar = nextVar(context, "asyncBoundaryLoading");
|
|
492
|
+
const loadingInstanceVar = nextVar(context, "asyncBoundaryLoading");
|
|
493
|
+
const fallbackVar = nextVar(context, "asyncBoundaryFallback");
|
|
494
|
+
const fallbackFragmentVar = nextVar(context, "asyncBoundaryFallback");
|
|
495
|
+
const fallbackInstanceVar = nextVar(context, "asyncBoundaryFallback");
|
|
496
|
+
const errorVar = nextVar(context, "error");
|
|
497
|
+
const errorInfoVar = nextVar(context, "errorInfo");
|
|
498
|
+
const errorsVar = nextVar(context, "asyncBoundaryErrors");
|
|
499
|
+
const normalizedErrorInfoVar = nextVar(context, "errorInfo");
|
|
500
|
+
const retryVar = nextVar(context, "retry");
|
|
501
|
+
const settledVar = nextVar(context, "settled");
|
|
502
|
+
const delayVar = nextVar(context, "asyncBoundaryDelay");
|
|
503
|
+
const timeoutVar = nextVar(context, "asyncBoundaryTimeout");
|
|
504
|
+
const delayTimerVar = nextVar(context, "asyncBoundaryDelayTimer");
|
|
505
|
+
const timeoutTimerVar = nextVar(context, "asyncBoundaryTimeoutTimer");
|
|
506
|
+
const timeoutErrorVar = nextVar(context, "asyncBoundaryTimeoutError");
|
|
507
|
+
const asyncContextVar = nextVar(context, "asyncBoundaryContext");
|
|
508
|
+
const previousComponentContextVar = context.componentContextVar;
|
|
509
|
+
emit(context, indent, `const ${startVar} = document.createComment("async-boundary");`);
|
|
510
|
+
emit(context, indent, `const ${endVar} = document.createComment("/async-boundary");`);
|
|
511
|
+
appendNode(context, parentVar, startVar, indent, beforeVar);
|
|
512
|
+
appendNode(context, parentVar, endVar, indent, beforeVar);
|
|
513
|
+
emit(context, indent, `const ${boundaryCleanupVar} = [];`);
|
|
514
|
+
emit(context, indent, `const ${loadingCleanupVar} = [];`);
|
|
515
|
+
emit(context, indent, `const ${fallbackCleanupVar} = [];`);
|
|
516
|
+
emit(context, indent, `let ${pendingVar} = 0;`);
|
|
517
|
+
emit(context, indent, `let ${failedVar} = false;`);
|
|
518
|
+
emit(context, indent, `let ${errorsVar} = [];`);
|
|
519
|
+
emit(context, indent, `let ${lastRetryVar} = () => {};`);
|
|
520
|
+
emit(context, indent, `let ${delayTimerVar};`);
|
|
521
|
+
emit(context, indent, `let ${timeoutTimerVar};`);
|
|
522
|
+
emit(context, indent, `const ${clearLoadingVar} = () => __mikuru_runCleanup(${loadingCleanupVar});`);
|
|
523
|
+
emit(context, indent, `const ${clearFallbackVar} = () => __mikuru_runCleanup(${fallbackCleanupVar});`);
|
|
524
|
+
emit(context, indent, `const ${clearTimersVar} = () => {`);
|
|
525
|
+
emit(context, indent + 1, `if (${delayTimerVar}) { clearTimeout(${delayTimerVar}); ${delayTimerVar} = undefined; }`);
|
|
526
|
+
emit(context, indent + 1, `if (${timeoutTimerVar}) { clearTimeout(${timeoutTimerVar}); ${timeoutTimerVar} = undefined; }`);
|
|
527
|
+
emit(context, indent, "};");
|
|
528
|
+
emit(context, indent, `const ${renderLoadingVar} = () => {`);
|
|
529
|
+
emit(context, indent + 1, `if (${failedVar}) { return; }`);
|
|
530
|
+
emit(context, indent + 1, `${clearLoadingVar}();`);
|
|
531
|
+
emit(context, indent + 1, `const ${loadingVar} = unwrap(${loadingExpression});`);
|
|
532
|
+
emit(context, indent + 1, `if (!${loadingVar} || typeof ${loadingVar}.mount !== "function") { return; }`);
|
|
533
|
+
emit(context, indent + 1, `const ${loadingFragmentVar} = document.createDocumentFragment();`);
|
|
534
|
+
emit(context, indent + 1, `const ${loadingInstanceVar} = ${loadingVar}.mount(${loadingFragmentVar}, { pending: ${pendingVar}, __mikuru_context });`);
|
|
535
|
+
emit(context, indent + 1, `${loadingCleanupVar}.push(() => ${loadingInstanceVar}.unmount());`);
|
|
536
|
+
appendNode(context, parentVar, loadingFragmentVar, indent + 1, endVar);
|
|
537
|
+
emit(context, indent, "};");
|
|
538
|
+
emit(context, indent, `const ${renderFallbackVar} = (${errorVar}, ${errorInfoVar} = __mikuru_errorInfo("async-loader")) => {`);
|
|
539
|
+
emit(context, indent + 1, `${failedVar} = true;`);
|
|
540
|
+
emit(context, indent + 1, `${clearTimersVar}();`);
|
|
541
|
+
emit(context, indent + 1, `${clearLoadingVar}();`);
|
|
542
|
+
emit(context, indent + 1, `${clearFallbackVar}();`);
|
|
543
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
|
|
544
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
545
|
+
emit(context, indent + 1, `const ${fallbackVar} = unwrap(${fallbackExpression});`);
|
|
546
|
+
emit(context, indent + 1, `if (!${fallbackVar} || typeof ${fallbackVar}.mount !== "function") { throw ${errorVar}; }`);
|
|
547
|
+
emit(context, indent + 1, `const ${normalizedErrorInfoVar} = ${errorInfoVar} && typeof ${errorInfoVar} === "object" ? ${errorInfoVar} : {};`);
|
|
548
|
+
if (context.debug) {
|
|
549
|
+
emit(context, indent + 1, `emitDebugEvent("async:rejected", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, error: ${errorVar}, errors: [...${errorsVar}], pending: ${pendingVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo } });`);
|
|
550
|
+
}
|
|
551
|
+
emit(context, indent + 1, `const ${fallbackFragmentVar} = document.createDocumentFragment();`);
|
|
552
|
+
emit(context, indent + 1, `const ${fallbackInstanceVar} = ${fallbackVar}.mount(${fallbackFragmentVar}, { error: ${errorVar}, errors: [...${errorsVar}], errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo }, pending: ${pendingVar}, retry: ${lastRetryVar}, reset: ${lastRetryVar}, __mikuru_context });`);
|
|
553
|
+
emit(context, indent + 1, `${fallbackCleanupVar}.push(() => ${fallbackInstanceVar}.unmount());`);
|
|
554
|
+
appendNode(context, parentVar, fallbackFragmentVar, indent + 1, endVar);
|
|
555
|
+
emit(context, indent, "};");
|
|
556
|
+
emit(context, indent, `const ${scheduleLoadingVar} = () => {`);
|
|
557
|
+
emit(context, indent + 1, `const ${delayVar} = Number(unwrap(${delayExpression}) ?? 0);`);
|
|
558
|
+
emit(context, indent + 1, `if (${delayVar} <= 0) { ${renderLoadingVar}(); return; }`);
|
|
559
|
+
emit(context, indent + 1, `if (${delayTimerVar}) { return; }`);
|
|
560
|
+
emit(context, indent + 1, `${delayTimerVar} = setTimeout(() => {`);
|
|
561
|
+
emit(context, indent + 2, `${delayTimerVar} = undefined;`);
|
|
562
|
+
emit(context, indent + 2, `if (${pendingVar} > 0 && !${failedVar}) { ${renderLoadingVar}(); }`);
|
|
563
|
+
emit(context, indent + 1, `}, ${delayVar});`);
|
|
564
|
+
emit(context, indent, "};");
|
|
565
|
+
emit(context, indent, `const ${scheduleTimeoutVar} = () => {`);
|
|
566
|
+
emit(context, indent + 1, `const ${timeoutVar} = unwrap(${timeoutExpression});`);
|
|
567
|
+
emit(context, indent + 1, `if (${timeoutVar} == null || Number(${timeoutVar}) <= 0 || ${timeoutTimerVar}) { return; }`);
|
|
568
|
+
emit(context, indent + 1, `${timeoutTimerVar} = setTimeout(() => {`);
|
|
569
|
+
emit(context, indent + 2, `${timeoutTimerVar} = undefined;`);
|
|
570
|
+
emit(context, indent + 2, `if (${pendingVar} <= 0 || ${failedVar}) { return; }`);
|
|
571
|
+
emit(context, indent + 2, `const ${timeoutErrorVar} = new Error("Async boundary timed out");`);
|
|
572
|
+
emit(context, indent + 2, `${errorsVar}.push(${timeoutErrorVar});`);
|
|
573
|
+
emit(context, indent + 2, `${renderFallbackVar}(${timeoutErrorVar}, __mikuru_errorInfo("async-timeout"));`);
|
|
574
|
+
emit(context, indent + 1, `}, Number(${timeoutVar}));`);
|
|
575
|
+
emit(context, indent, "};");
|
|
576
|
+
emit(context, indent, `const ${asyncContextVar} = { parent: __mikuru_context, provides: new Map(), errorHandler: __mikuru_context.errorHandler, asyncBoundary: {`);
|
|
577
|
+
emit(context, indent + 1, `start({ retry: ${retryVar} }) {`);
|
|
578
|
+
emit(context, indent + 2, `${pendingVar} += 1;`);
|
|
579
|
+
emit(context, indent + 2, `${lastRetryVar} = ${renderVar};`);
|
|
580
|
+
if (context.debug) {
|
|
581
|
+
emit(context, indent + 2, `emitDebugEvent("async:pending", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, pending: ${pendingVar} });`);
|
|
582
|
+
}
|
|
583
|
+
emit(context, indent + 2, `if (${pendingVar} === 1) { ${scheduleLoadingVar}(); ${scheduleTimeoutVar}(); } else if (${loadingCleanupVar}.length > 0) { ${renderLoadingVar}(); }`);
|
|
584
|
+
emit(context, indent + 2, `let ${settledVar} = false;`);
|
|
585
|
+
emit(context, indent + 2, "return {");
|
|
586
|
+
emit(context, indent + 3, "resolve() {");
|
|
587
|
+
emit(context, indent + 4, `if (${settledVar}) { return; }`);
|
|
588
|
+
emit(context, indent + 4, `${settledVar} = true;`);
|
|
589
|
+
emit(context, indent + 4, `${pendingVar} = Math.max(0, ${pendingVar} - 1);`);
|
|
590
|
+
if (context.debug) {
|
|
591
|
+
emit(context, indent + 4, `emitDebugEvent("async:resolved", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, pending: ${pendingVar} });`);
|
|
592
|
+
}
|
|
593
|
+
emit(context, indent + 4, `if (${pendingVar} === 0) { ${clearTimersVar}(); ${clearLoadingVar}(); } else if (${loadingCleanupVar}.length > 0) { ${renderLoadingVar}(); }`);
|
|
594
|
+
emit(context, indent + 3, "},");
|
|
595
|
+
emit(context, indent + 3, `reject(${errorVar}, ${errorInfoVar}) {`);
|
|
596
|
+
emit(context, indent + 4, `if (${settledVar}) { return; }`);
|
|
597
|
+
emit(context, indent + 4, `${settledVar} = true;`);
|
|
598
|
+
emit(context, indent + 4, `${pendingVar} = Math.max(0, ${pendingVar} - 1);`);
|
|
599
|
+
emit(context, indent + 4, `${errorsVar}.push(${errorVar});`);
|
|
600
|
+
emit(context, indent + 4, `${clearTimersVar}();`);
|
|
601
|
+
emit(context, indent + 4, `${renderFallbackVar}(${errorVar}, ${errorInfoVar});`);
|
|
602
|
+
emit(context, indent + 3, "}");
|
|
603
|
+
emit(context, indent + 2, "};");
|
|
604
|
+
emit(context, indent + 1, "}");
|
|
605
|
+
emit(context, indent, `}, ...__mikuru_componentInfo };`);
|
|
606
|
+
emit(context, indent, `const ${renderVar} = () => {`);
|
|
607
|
+
emit(context, indent + 1, `${failedVar} = false;`);
|
|
608
|
+
emit(context, indent + 1, `${pendingVar} = 0;`);
|
|
609
|
+
emit(context, indent + 1, `${errorsVar} = [];`);
|
|
610
|
+
emit(context, indent + 1, `${lastRetryVar} = ${renderVar};`);
|
|
611
|
+
emit(context, indent + 1, `${clearTimersVar}();`);
|
|
612
|
+
emit(context, indent + 1, `${clearLoadingVar}();`);
|
|
613
|
+
emit(context, indent + 1, `${clearFallbackVar}();`);
|
|
614
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
|
|
615
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
616
|
+
context.componentContextVar = asyncContextVar;
|
|
617
|
+
generateChildren(context, children, parentVar, boundaryCleanupVar, indent + 1, endVar);
|
|
618
|
+
context.componentContextVar = previousComponentContextVar;
|
|
619
|
+
emit(context, indent, "};");
|
|
620
|
+
emit(context, indent, `${renderVar}();`);
|
|
621
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
622
|
+
emit(context, indent + 1, `${clearTimersVar}();`);
|
|
623
|
+
emit(context, indent + 1, `${clearLoadingVar}();`);
|
|
624
|
+
emit(context, indent + 1, `${clearFallbackVar}();`);
|
|
625
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
|
|
626
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
627
|
+
emit(context, indent + 1, `${startVar}.remove();`);
|
|
628
|
+
emit(context, indent + 1, `${endVar}.remove();`);
|
|
629
|
+
emit(context, indent, "});");
|
|
630
|
+
return startVar;
|
|
631
|
+
}
|
|
357
632
|
function generateSlot(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
358
633
|
const slotCleanupVar = nextVar(context, "slotCleanup");
|
|
359
634
|
const slotCleanupResultVar = nextVar(context, "slotCleanup");
|
|
@@ -500,6 +775,149 @@ function generateDynamicComponent(context, node, parentVar, cleanupVar, indent,
|
|
|
500
775
|
emit(context, indent, "});");
|
|
501
776
|
return startVar;
|
|
502
777
|
}
|
|
778
|
+
function generateKeepAlive(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
779
|
+
validateKeepAliveAttributes(context, node);
|
|
780
|
+
const children = getSingleElementChild(context, node, "<KeepAlive>");
|
|
781
|
+
const child = children[0];
|
|
782
|
+
if (child.tag !== "component") {
|
|
783
|
+
throwTemplateError("<KeepAlive> requires a single <component :is=\"...\" /> child in v1", context, child.loc);
|
|
784
|
+
}
|
|
785
|
+
const isAttr = child.attrs.find((attr) => getBindingName(attr.name) === "is");
|
|
786
|
+
if (!isAttr) {
|
|
787
|
+
throwTemplateError("<KeepAlive> dynamic child requires :is to resolve to a component object", context, child.loc);
|
|
788
|
+
}
|
|
789
|
+
const expression = compileTemplateExpression(requireAttrValue(isAttr), isAttr.name, toExpressionContext(context, isAttr.valueLoc));
|
|
790
|
+
const includeExpression = getKeepAliveOptionExpression(context, node, "include");
|
|
791
|
+
const excludeExpression = getKeepAliveOptionExpression(context, node, "exclude");
|
|
792
|
+
const maxExpression = getKeepAliveOptionExpression(context, node, "max");
|
|
793
|
+
const dynamicNode = withoutAttrs(child, ["is", ":is", "v-bind:is"]);
|
|
794
|
+
const startVar = nextVar(context, "keepAliveStart");
|
|
795
|
+
const endVar = nextVar(context, "keepAliveEnd");
|
|
796
|
+
const cacheVar = nextVar(context, "keepAliveCache");
|
|
797
|
+
const currentTypeVar = nextVar(context, "keepAliveCurrent");
|
|
798
|
+
const currentRecordVar = nextVar(context, "keepAliveCurrent");
|
|
799
|
+
const componentTypeVar = nextVar(context, "keepAliveType");
|
|
800
|
+
const componentNameVar = nextVar(context, "keepAliveName");
|
|
801
|
+
const componentNameHelperVar = nextVar(context, "keepAliveName");
|
|
802
|
+
const matchHelperVar = nextVar(context, "keepAliveMatches");
|
|
803
|
+
const includeVar = nextVar(context, "keepAliveInclude");
|
|
804
|
+
const excludeVar = nextVar(context, "keepAliveExclude");
|
|
805
|
+
const maxVar = nextVar(context, "keepAliveMax");
|
|
806
|
+
const cacheableVar = nextVar(context, "keepAliveCacheable");
|
|
807
|
+
const recordVar = nextVar(context, "keepAliveRecord");
|
|
808
|
+
const recordNodesVar = nextVar(context, "keepAliveNodes");
|
|
809
|
+
const activateRecordVar = nextVar(context, "keepAliveActivate");
|
|
810
|
+
const deactivateRecordVar = nextVar(context, "keepAliveDeactivate");
|
|
811
|
+
const collectNodesVar = nextVar(context, "keepAliveCollectNodes");
|
|
812
|
+
const collectNodeVar = nextVar(context, "keepAliveNode");
|
|
813
|
+
const staleKeyVar = nextVar(context, "keepAliveStaleKey");
|
|
814
|
+
const staleRecordVar = nextVar(context, "keepAliveStaleRecord");
|
|
815
|
+
const fragmentVar = nextVar(context, "fragment");
|
|
816
|
+
const attrsVar = nextVar(context, "attrs");
|
|
817
|
+
const propsVar = nextVar(context, "props");
|
|
818
|
+
const componentVar = nextVar(context, "component");
|
|
819
|
+
const recordCleanupVar = nextVar(context, "keepAliveCleanup");
|
|
820
|
+
const stopVar = nextVar(context, "stop");
|
|
821
|
+
emit(context, indent, `const ${startVar} = document.createComment("keep-alive");`);
|
|
822
|
+
emit(context, indent, `const ${endVar} = document.createComment("/keep-alive");`);
|
|
823
|
+
appendNode(context, parentVar, startVar, indent, beforeVar);
|
|
824
|
+
appendNode(context, parentVar, endVar, indent, beforeVar);
|
|
825
|
+
emit(context, indent, `const ${cacheVar} = new Map();`);
|
|
826
|
+
emit(context, indent, `let ${currentTypeVar};`);
|
|
827
|
+
emit(context, indent, `let ${currentRecordVar};`);
|
|
828
|
+
emit(context, indent, `const ${componentNameHelperVar} = (component) => component?.name ?? component?.displayName ?? component?.__name ?? component?.constructor?.name ?? "";`);
|
|
829
|
+
emit(context, indent, `const ${matchHelperVar} = (pattern, name) => pattern == null ? false : typeof pattern === "string" ? pattern.split(",").map((part) => part.trim()).filter(Boolean).includes(name) : Array.isArray(pattern) ? pattern.some((entry) => ${matchHelperVar}(entry, name)) : pattern instanceof RegExp ? pattern.test(name) : false;`);
|
|
830
|
+
emit(context, indent, `const ${activateRecordVar} = (record) => { if (record && !record.active) { record.active = true; record.instance.activate?.(); } };`);
|
|
831
|
+
emit(context, indent, `const ${deactivateRecordVar} = (record) => { if (record?.active) { record.active = false; record.instance.deactivate?.(); } };`);
|
|
832
|
+
emit(context, indent, `const ${collectNodesVar} = () => { const nodes = []; for (let ${collectNodeVar} = ${startVar}.nextSibling; ${collectNodeVar} && ${collectNodeVar} !== ${endVar}; ${collectNodeVar} = ${collectNodeVar}.nextSibling) { nodes.push(${collectNodeVar}); } return nodes; };`);
|
|
833
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
834
|
+
emit(context, indent + 1, `const ${componentTypeVar} = unwrap(${expression});`);
|
|
835
|
+
emit(context, indent + 1, `if (${componentTypeVar} === ${currentTypeVar}) { return; }`);
|
|
836
|
+
emit(context, indent + 1, `if (${currentRecordVar}) { ${currentRecordVar}.nodes = ${collectNodesVar}(); }`);
|
|
837
|
+
emit(context, indent + 1, `if (${currentRecordVar}?.transient) { __mikuru_runCleanup(${currentRecordVar}.cleanups); } else { ${deactivateRecordVar}(${currentRecordVar}); }`);
|
|
838
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
839
|
+
emit(context, indent + 1, `if (!${componentTypeVar}) { ${currentTypeVar} = ${componentTypeVar}; ${currentRecordVar} = undefined; return; }`);
|
|
840
|
+
emit(context, indent + 1, `if (typeof ${componentTypeVar} !== "object" || typeof ${componentTypeVar}.mount !== "function") {`);
|
|
841
|
+
emit(context, indent + 2, `throw new Error("KeepAlive child :is must resolve to a component object with mount()");`);
|
|
842
|
+
emit(context, indent + 1, "}");
|
|
843
|
+
emit(context, indent + 1, `const ${componentNameVar} = ${componentNameHelperVar}(${componentTypeVar});`);
|
|
844
|
+
emit(context, indent + 1, `const ${includeVar} = unwrap(${includeExpression ?? "undefined"});`);
|
|
845
|
+
emit(context, indent + 1, `const ${excludeVar} = unwrap(${excludeExpression ?? "undefined"});`);
|
|
846
|
+
emit(context, indent + 1, `const ${maxVar} = Number(unwrap(${maxExpression ?? "undefined"}));`);
|
|
847
|
+
emit(context, indent + 1, `const ${cacheableVar} = (${includeVar} == null || ${matchHelperVar}(${includeVar}, ${componentNameVar})) && !${matchHelperVar}(${excludeVar}, ${componentNameVar});`);
|
|
848
|
+
emit(context, indent + 1, `if (!${cacheableVar}) {`);
|
|
849
|
+
emit(context, indent + 2, `const ${fragmentVar} = document.createDocumentFragment();`);
|
|
850
|
+
emit(context, indent + 2, `const ${recordCleanupVar} = [];`);
|
|
851
|
+
emitComponentAttrs(context, dynamicNode, attrsVar, indent + 2);
|
|
852
|
+
emitComponentProps(context, dynamicNode, propsVar, attrsVar, indent + 2);
|
|
853
|
+
emit(context, indent + 2, `const ${componentVar} = ${componentTypeVar}.mount(${fragmentVar}, ${propsVar});`);
|
|
854
|
+
if (context.scopeAttr) {
|
|
855
|
+
emit(context, indent + 2, `if (${componentVar}.element?.nodeType === 1) {`);
|
|
856
|
+
emit(context, indent + 3, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
|
|
857
|
+
emit(context, indent + 2, "}");
|
|
858
|
+
}
|
|
859
|
+
emit(context, indent + 2, `if (${componentTypeVar}.inheritAttrs !== false) {`);
|
|
860
|
+
emitComponentFallthrough(context, dynamicNode, componentVar, recordCleanupVar, indent + 3);
|
|
861
|
+
emit(context, indent + 2, "}");
|
|
862
|
+
emitComponentShow(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
|
|
863
|
+
emitTemplateRef(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
|
|
864
|
+
emit(context, indent + 2, `const ${recordNodesVar} = Array.from(${fragmentVar}.childNodes);`);
|
|
865
|
+
appendNode(context, parentVar, fragmentVar, indent + 2, endVar);
|
|
866
|
+
emit(context, indent + 2, `${currentTypeVar} = ${componentTypeVar};`);
|
|
867
|
+
emit(context, indent + 2, `${currentRecordVar} = { instance: ${componentVar}, element: ${componentVar}.element, nodes: ${recordNodesVar}, cleanups: ${recordCleanupVar}, transient: true };`);
|
|
868
|
+
emit(context, indent + 2, `${recordCleanupVar}.push(() => ${componentVar}.unmount());`);
|
|
869
|
+
emit(context, indent + 2, "return;");
|
|
870
|
+
emit(context, indent + 1, "}");
|
|
871
|
+
emit(context, indent + 1, `let ${recordVar} = ${cacheVar}.get(${componentTypeVar});`);
|
|
872
|
+
emit(context, indent + 1, `if (!${recordVar}) {`);
|
|
873
|
+
emit(context, indent + 2, `const ${fragmentVar} = document.createDocumentFragment();`);
|
|
874
|
+
emit(context, indent + 2, `const ${recordCleanupVar} = [];`);
|
|
875
|
+
emitComponentAttrs(context, dynamicNode, attrsVar, indent + 2);
|
|
876
|
+
emitComponentProps(context, dynamicNode, propsVar, attrsVar, indent + 2);
|
|
877
|
+
emit(context, indent + 2, `const ${componentVar} = ${componentTypeVar}.mount(${fragmentVar}, ${propsVar});`);
|
|
878
|
+
if (context.scopeAttr) {
|
|
879
|
+
emit(context, indent + 2, `if (${componentVar}.element?.nodeType === 1) {`);
|
|
880
|
+
emit(context, indent + 3, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
|
|
881
|
+
emit(context, indent + 2, "}");
|
|
882
|
+
}
|
|
883
|
+
emit(context, indent + 2, `if (${componentTypeVar}.inheritAttrs !== false) {`);
|
|
884
|
+
emitComponentFallthrough(context, dynamicNode, componentVar, recordCleanupVar, indent + 3);
|
|
885
|
+
emit(context, indent + 2, "}");
|
|
886
|
+
emitComponentShow(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
|
|
887
|
+
emitTemplateRef(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
|
|
888
|
+
emit(context, indent + 2, `const ${recordNodesVar} = Array.from(${fragmentVar}.childNodes);`);
|
|
889
|
+
emit(context, indent + 2, `${recordVar} = { instance: ${componentVar}, element: ${componentVar}.element, nodes: ${recordNodesVar}, cleanups: ${recordCleanupVar}, active: false };`);
|
|
890
|
+
emit(context, indent + 2, `${cacheVar}.set(${componentTypeVar}, ${recordVar});`);
|
|
891
|
+
emit(context, indent + 2, `if (Number.isFinite(${maxVar}) && ${maxVar} > 0) {`);
|
|
892
|
+
emit(context, indent + 3, `while (${cacheVar}.size > ${maxVar}) {`);
|
|
893
|
+
emit(context, indent + 4, `const ${staleKeyVar} = ${cacheVar}.keys().next().value;`);
|
|
894
|
+
emit(context, indent + 4, `if (${staleKeyVar} === ${componentTypeVar}) { break; }`);
|
|
895
|
+
emit(context, indent + 4, `const ${staleRecordVar} = ${cacheVar}.get(${staleKeyVar});`);
|
|
896
|
+
emit(context, indent + 4, `${cacheVar}.delete(${staleKeyVar});`);
|
|
897
|
+
emit(context, indent + 4, `if (${staleRecordVar}) { ${deactivateRecordVar}(${staleRecordVar}); __mikuru_runCleanup(${staleRecordVar}.cleanups); ${staleRecordVar}.instance.unmount(); }`);
|
|
898
|
+
emit(context, indent + 3, "}");
|
|
899
|
+
emit(context, indent + 2, "}");
|
|
900
|
+
appendNode(context, parentVar, fragmentVar, indent + 2, endVar);
|
|
901
|
+
emit(context, indent + 1, "} else {");
|
|
902
|
+
emit(context, indent + 2, `${cacheVar}.delete(${componentTypeVar});`);
|
|
903
|
+
emit(context, indent + 2, `${cacheVar}.set(${componentTypeVar}, ${recordVar});`);
|
|
904
|
+
emit(context, indent + 2, `for (const node of ${recordVar}.nodes) { ${parentVar}.insertBefore(node, ${endVar}); }`);
|
|
905
|
+
emit(context, indent + 1, "}");
|
|
906
|
+
emit(context, indent + 1, `${currentTypeVar} = ${componentTypeVar};`);
|
|
907
|
+
emit(context, indent + 1, `${currentRecordVar} = ${recordVar};`);
|
|
908
|
+
emit(context, indent + 1, `${activateRecordVar}(${recordVar});`);
|
|
909
|
+
emit(context, indent, "});");
|
|
910
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
911
|
+
emit(context, indent + 1, `${stopVar}();`);
|
|
912
|
+
emit(context, indent + 1, `if (${currentRecordVar}?.transient) { __mikuru_runCleanup(${currentRecordVar}.cleanups); } else { ${deactivateRecordVar}(${currentRecordVar}); }`);
|
|
913
|
+
emit(context, indent + 1, `for (const ${recordVar} of ${cacheVar}.values()) { __mikuru_runCleanup(${recordVar}.cleanups); ${recordVar}.instance.unmount(); }`);
|
|
914
|
+
emit(context, indent + 1, `${cacheVar}.clear();`);
|
|
915
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
916
|
+
emit(context, indent + 1, `${startVar}.remove();`);
|
|
917
|
+
emit(context, indent + 1, `${endVar}.remove();`);
|
|
918
|
+
emit(context, indent, "});");
|
|
919
|
+
return startVar;
|
|
920
|
+
}
|
|
503
921
|
function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
|
|
504
922
|
const showAttr = node.attrs.find((attr) => attr.name === "v-show");
|
|
505
923
|
if (!showAttr) {
|
|
@@ -521,7 +939,10 @@ function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
|
|
|
521
939
|
function emitComponentAttrs(context, node, attrsVar, indent) {
|
|
522
940
|
const objectBindExpressions = node.attrs
|
|
523
941
|
.filter((attr) => isObjectBindAttr(attr))
|
|
524
|
-
.map((attr) =>
|
|
942
|
+
.map((attr) => {
|
|
943
|
+
validateObjectBindModifiers(parseObjectBindDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
|
|
944
|
+
return compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
945
|
+
});
|
|
525
946
|
const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
|
|
526
947
|
const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
|
|
527
948
|
const directAttrs = componentDirectAttributeFallthroughs(context, node);
|
|
@@ -595,7 +1016,10 @@ function emitComponentAttrsProxy(context, attrsVar, baseVar, objectBindExpressio
|
|
|
595
1016
|
function emitComponentFallthrough(context, node, componentVar, cleanupVar, indent) {
|
|
596
1017
|
const objectBindExpressions = node.attrs
|
|
597
1018
|
.filter((attr) => isObjectBindAttr(attr))
|
|
598
|
-
.map((attr) =>
|
|
1019
|
+
.map((attr) => {
|
|
1020
|
+
validateObjectBindModifiers(parseObjectBindDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
|
|
1021
|
+
return compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
1022
|
+
});
|
|
599
1023
|
const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
|
|
600
1024
|
const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
|
|
601
1025
|
const directAttrs = componentDirectAttributeFallthroughs(context, node);
|
|
@@ -730,7 +1154,7 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
730
1154
|
if (slotDirectiveAttr) {
|
|
731
1155
|
throwTemplateError("v-slot must be used on a <template> child in Mikuru", context, slotDirectiveAttr.loc);
|
|
732
1156
|
}
|
|
733
|
-
validateAttributes(node);
|
|
1157
|
+
validateAttributes(context, node);
|
|
734
1158
|
const elementVar = nextVar(context, "el");
|
|
735
1159
|
emit(context, indent, `const ${elementVar} = document.createElement(${quote(node.tag)});`);
|
|
736
1160
|
if (context.scopeAttr) {
|
|
@@ -746,11 +1170,20 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
746
1170
|
emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
|
|
747
1171
|
}
|
|
748
1172
|
emitTemplateRef(context, node, elementVar, cleanupVar, indent);
|
|
749
|
-
|
|
1173
|
+
const contentDirective = getContentDirectiveAttr(node);
|
|
1174
|
+
if (contentDirective) {
|
|
1175
|
+
emitContentDirective(context, node, elementVar, contentDirective, cleanupVar, indent);
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
generateChildren(context, node.children, elementVar, cleanupVar, indent);
|
|
1179
|
+
}
|
|
750
1180
|
for (const attr of node.attrs) {
|
|
751
1181
|
const modelDirective = parseModelDirective(attr.name);
|
|
752
1182
|
if (modelDirective) {
|
|
753
1183
|
validateModelModifiers(modelDirective, attr, context);
|
|
1184
|
+
if (modelDirective.argument) {
|
|
1185
|
+
throwTemplateError("v-model arguments are only supported on components in v1", context, attr.loc);
|
|
1186
|
+
}
|
|
754
1187
|
const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
755
1188
|
const stopVar = nextVar(context, "stop");
|
|
756
1189
|
const handlerVar = nextVar(context, "handler");
|
|
@@ -768,15 +1201,17 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
768
1201
|
const eventName = modelMode === "text" && !modelDirective.modifiers.includes("lazy") ? "input" : "change";
|
|
769
1202
|
const propertyName = modelMode === "checkbox" || modelMode === "radio" ? "checked" : "value";
|
|
770
1203
|
const renderedValue = modelMode === "checkbox"
|
|
771
|
-
? `
|
|
1204
|
+
? `(() => { const value = unwrap(${expression}); const checkboxValue = ${modelElementValueExpression(`${elementVar}`, modelDirective.modifiers)}; return Array.isArray(value) ? value.some((item) => Object.is(item, checkboxValue)) : Boolean(value); })()`
|
|
772
1205
|
: modelMode === "radio"
|
|
773
|
-
? `(${
|
|
1206
|
+
? `Object.is(${modelElementValueExpression(`${elementVar}`, modelDirective.modifiers)}, unwrap(${expression}))`
|
|
774
1207
|
: modelMode === "select-multiple"
|
|
775
|
-
? `Array.from(${elementVar}.options).forEach((option) => { option.selected = (unwrap(${expression}) ?? []).
|
|
776
|
-
:
|
|
777
|
-
|
|
1208
|
+
? `Array.from(${elementVar}.options).forEach((option) => { const optionValue = ${modelElementValueExpression("option", modelDirective.modifiers)}; option.selected = (unwrap(${expression}) ?? []).some((item) => Object.is(item, optionValue)); })`
|
|
1209
|
+
: modelMode === "select"
|
|
1210
|
+
? `Array.from(${elementVar}.options).forEach((option) => { option.selected = Object.is(${modelElementValueExpression("option", modelDirective.modifiers)}, unwrap(${expression})); })`
|
|
1211
|
+
: `String(unwrap(${expression}) ?? "")`;
|
|
1212
|
+
const assignedValue = modelAssignedValue(modelMode, modelDirective.modifiers, expression);
|
|
778
1213
|
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
779
|
-
if (modelMode === "select-multiple") {
|
|
1214
|
+
if (modelMode === "select-multiple" || modelMode === "select") {
|
|
780
1215
|
emit(context, indent + 1, renderedValue);
|
|
781
1216
|
}
|
|
782
1217
|
else {
|
|
@@ -795,11 +1230,16 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
795
1230
|
}
|
|
796
1231
|
if (attr.name === "v-show") {
|
|
797
1232
|
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1233
|
+
if (context.once) {
|
|
1234
|
+
emit(context, indent, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
const stopVar = nextVar(context, "stop");
|
|
1238
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
1239
|
+
emit(context, indent + 1, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
|
|
1240
|
+
emit(context, indent, "});");
|
|
1241
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
1242
|
+
}
|
|
803
1243
|
continue;
|
|
804
1244
|
}
|
|
805
1245
|
if (isObjectBindAttr(attr)) {
|
|
@@ -813,7 +1253,7 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
813
1253
|
const event = parseEventDirective(attr.name);
|
|
814
1254
|
if (event) {
|
|
815
1255
|
validateEventModifiers(event, attr, context);
|
|
816
|
-
const handler =
|
|
1256
|
+
const handler = validateEventHandlerExpression(requireAttrValue(attr), context, attr.valueLoc);
|
|
817
1257
|
const handlerVar = nextVar(context, "handler");
|
|
818
1258
|
const handlerExpression = eventHandlerExpression(handler, context, attr.valueLoc);
|
|
819
1259
|
if (event.modifiers.length) {
|
|
@@ -825,6 +1265,10 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
825
1265
|
if (event.modifiers.includes("self")) {
|
|
826
1266
|
emit(context, indent + 1, `if ($event.target !== ${elementVar}) { return; }`);
|
|
827
1267
|
}
|
|
1268
|
+
const modifierGuard = eventModifierGuardExpression(event);
|
|
1269
|
+
if (modifierGuard) {
|
|
1270
|
+
emit(context, indent + 1, `if (${modifierGuard}) { return; }`);
|
|
1271
|
+
}
|
|
828
1272
|
if (event.modifiers.includes("prevent")) {
|
|
829
1273
|
emit(context, indent + 1, "$event.preventDefault();");
|
|
830
1274
|
}
|
|
@@ -837,30 +1281,133 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
|
|
|
837
1281
|
else {
|
|
838
1282
|
emit(context, indent, `const ${handlerVar} = __mikuru_guardEventHandler(${handlerExpression});`);
|
|
839
1283
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1284
|
+
if (event.nameExpression) {
|
|
1285
|
+
emitDynamicEventListener(context, elementVar, event, handlerVar, attr, cleanupVar, indent);
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
const eventOptions = eventListenerOptions(event);
|
|
1289
|
+
emit(context, indent, `${elementVar}.addEventListener(${quote(event.name ?? "")}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
|
|
1290
|
+
emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name ?? "")}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}));`);
|
|
1291
|
+
}
|
|
843
1292
|
continue;
|
|
844
1293
|
}
|
|
845
|
-
const
|
|
846
|
-
|
|
1294
|
+
const bindDirective = parseBindDirective(attr.name);
|
|
1295
|
+
const dynamicBinding = bindDirective?.nameExpression ? bindDirective : undefined;
|
|
1296
|
+
if (dynamicBinding) {
|
|
1297
|
+
validateBindModifiers(dynamicBinding, attr, context, "element");
|
|
1298
|
+
emitDynamicAttributeBinding(context, node, elementVar, attr, dynamicBinding, cleanupVar, indent);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const bindingName = bindDirective?.name;
|
|
1302
|
+
if (bindingName && bindDirective) {
|
|
1303
|
+
validateBindModifiers(bindDirective, attr, context, "element");
|
|
847
1304
|
if (bindingName === "ref") {
|
|
848
1305
|
continue;
|
|
849
1306
|
}
|
|
850
1307
|
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
851
1308
|
const stopVar = nextVar(context, "stop");
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1309
|
+
const staticClass = getStaticAttrValue(node, "class");
|
|
1310
|
+
const staticStyle = getStaticAttrValue(node, "style");
|
|
1311
|
+
const valueExpression = bindingName === "class" && staticClass
|
|
1312
|
+
? `[${quote(staticClass)}, ${expression}]`
|
|
1313
|
+
: bindingName === "style" && staticStyle
|
|
1314
|
+
? `[${quote(staticStyle)}, ${expression}]`
|
|
1315
|
+
: expression;
|
|
1316
|
+
if (context.once) {
|
|
1317
|
+
emit(context, indent, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression})${bindOptionsExpression(bindDirective)});`);
|
|
1318
|
+
}
|
|
1319
|
+
else {
|
|
1320
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
1321
|
+
emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression})${bindOptionsExpression(bindDirective)});`);
|
|
1322
|
+
emit(context, indent, "});");
|
|
1323
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
1324
|
+
}
|
|
859
1325
|
}
|
|
860
1326
|
}
|
|
861
1327
|
appendNode(context, parentVar, elementVar, indent, beforeVar);
|
|
862
1328
|
return elementVar;
|
|
863
1329
|
}
|
|
1330
|
+
function emitDynamicAttributeBinding(context, node, elementVar, attr, binding, cleanupVar, indent) {
|
|
1331
|
+
const compiledName = compileTemplateExpression(binding.nameExpression ?? "", attr.name, toExpressionContext(context, attr.loc));
|
|
1332
|
+
const compiledValue = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
1333
|
+
const staticClass = getStaticAttrValue(node, "class");
|
|
1334
|
+
const staticStyle = getStaticAttrValue(node, "style");
|
|
1335
|
+
const previousNameVar = nextVar(context, "attrName");
|
|
1336
|
+
const nameVar = nextVar(context, "attrName");
|
|
1337
|
+
const valueVar = nextVar(context, "attrValue");
|
|
1338
|
+
const stopVar = nextVar(context, "stop");
|
|
1339
|
+
emit(context, indent, `let ${previousNameVar};`);
|
|
1340
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
1341
|
+
emit(context, indent + 1, `const ${nameVar} = ${bindNameExpression(`String(unwrap(${compiledName}) ?? "")`, binding)};`);
|
|
1342
|
+
emit(context, indent + 1, `if (!${nameVar}) { if (${previousNameVar}) setAttribute(${elementVar}, ${previousNameVar}, null); ${previousNameVar} = undefined; return; }`);
|
|
1343
|
+
emit(context, indent + 1, `if (${previousNameVar} && ${previousNameVar} !== ${nameVar}) { setAttribute(${elementVar}, ${previousNameVar}, null); }`);
|
|
1344
|
+
emit(context, indent + 1, `const ${valueVar} = unwrap(${compiledValue});`);
|
|
1345
|
+
if (staticClass || staticStyle) {
|
|
1346
|
+
emit(context, indent + 1, `setAttribute(${elementVar}, ${nameVar}, ${nameVar} === "class" && ${staticClass ? "true" : "false"} ? [${quote(staticClass ?? "")}, ${valueVar}] : ${nameVar} === "style" && ${staticStyle ? "true" : "false"} ? [${quote(staticStyle ?? "")}, ${valueVar}] : ${valueVar}${bindOptionsExpression(binding)});`);
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
emit(context, indent + 1, `setAttribute(${elementVar}, ${nameVar}, ${valueVar}${bindOptionsExpression(binding)});`);
|
|
1350
|
+
}
|
|
1351
|
+
emit(context, indent + 1, `${previousNameVar} = ${nameVar};`);
|
|
1352
|
+
emit(context, indent, "});");
|
|
1353
|
+
emit(context, indent, `${cleanupVar}.push(() => { ${stopVar}(); if (${previousNameVar}) setAttribute(${elementVar}, ${previousNameVar}, null); });`);
|
|
1354
|
+
}
|
|
1355
|
+
function emitDynamicEventListener(context, elementVar, event, handlerVar, attr, cleanupVar, indent) {
|
|
1356
|
+
const expression = compileTemplateExpression(event.nameExpression ?? "\"\"", attr.name, toExpressionContext(context, attr.loc));
|
|
1357
|
+
const currentEventVar = nextVar(context, "eventName");
|
|
1358
|
+
const nextEventVar = nextVar(context, "eventName");
|
|
1359
|
+
const stopVar = nextVar(context, "stop");
|
|
1360
|
+
const eventOptions = eventListenerOptions(event);
|
|
1361
|
+
emit(context, indent, `let ${currentEventVar};`);
|
|
1362
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
1363
|
+
emit(context, indent + 1, `const ${nextEventVar} = String(unwrap(${expression}) ?? "");`);
|
|
1364
|
+
emit(context, indent + 1, `if (${nextEventVar} === ${currentEventVar}) { return; }`);
|
|
1365
|
+
emit(context, indent + 1, `if (${currentEventVar}) { ${elementVar}.removeEventListener(${currentEventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}); }`);
|
|
1366
|
+
emit(context, indent + 1, `${currentEventVar} = ${nextEventVar};`);
|
|
1367
|
+
emit(context, indent + 1, `if (${currentEventVar}) { ${elementVar}.addEventListener(${currentEventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}); }`);
|
|
1368
|
+
emit(context, indent, "});");
|
|
1369
|
+
emit(context, indent, `${cleanupVar}.push(() => { ${stopVar}(); if (${currentEventVar}) { ${elementVar}.removeEventListener(${currentEventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}); } });`);
|
|
1370
|
+
}
|
|
1371
|
+
function emitContentDirective(context, node, elementVar, attr, cleanupVar, indent) {
|
|
1372
|
+
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
1373
|
+
const property = attr.name === "v-html" ? "innerHTML" : "textContent";
|
|
1374
|
+
if (context.once) {
|
|
1375
|
+
emit(context, indent, `${elementVar}.${property} = String(unwrap(${expression}) ?? "");`);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const stopVar = nextVar(context, "stop");
|
|
1379
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
1380
|
+
emit(context, indent + 1, `${elementVar}.${property} = String(unwrap(${expression}) ?? "");`);
|
|
1381
|
+
emit(context, indent, "});");
|
|
1382
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
1383
|
+
}
|
|
1384
|
+
function generatePreNode(context, node, parentVar, indent, beforeVar) {
|
|
1385
|
+
if (node.type === "text") {
|
|
1386
|
+
const textVar = nextVar(context, "text");
|
|
1387
|
+
emit(context, indent, `const ${textVar} = document.createTextNode(${quote(node.parts.map((part) => part.value).join(""))});`);
|
|
1388
|
+
appendNode(context, parentVar, textVar, indent, beforeVar);
|
|
1389
|
+
return textVar;
|
|
1390
|
+
}
|
|
1391
|
+
return generatePreElement(context, node, parentVar, indent, beforeVar);
|
|
1392
|
+
}
|
|
1393
|
+
function generatePreElement(context, node, parentVar, indent, beforeVar) {
|
|
1394
|
+
const elementVar = nextVar(context, "el");
|
|
1395
|
+
emit(context, indent, `const ${elementVar} = document.createElement(${quote(node.tag)});`);
|
|
1396
|
+
if (context.scopeAttr) {
|
|
1397
|
+
emit(context, indent, `${elementVar}.setAttribute(${quote(context.scopeAttr)}, "");`);
|
|
1398
|
+
}
|
|
1399
|
+
for (const attr of node.attrs) {
|
|
1400
|
+
if (attr.name === "v-pre") {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
|
|
1404
|
+
}
|
|
1405
|
+
for (const child of node.children) {
|
|
1406
|
+
generatePreNode(context, child, elementVar, indent);
|
|
1407
|
+
}
|
|
1408
|
+
appendNode(context, parentVar, elementVar, indent, beforeVar);
|
|
1409
|
+
return elementVar;
|
|
1410
|
+
}
|
|
864
1411
|
function emitTemplateRef(context, node, valueExpression, cleanupVar, indent) {
|
|
865
1412
|
const refAttr = getTemplateRefAttr(node);
|
|
866
1413
|
if (!refAttr) {
|
|
@@ -892,11 +1439,16 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
|
892
1439
|
const textVar = nextVar(context, "text");
|
|
893
1440
|
emit(context, indent, `const ${textVar} = document.createTextNode("");`);
|
|
894
1441
|
if (node.parts.some((part) => part.type === "expression")) {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1442
|
+
if (context.once) {
|
|
1443
|
+
emit(context, indent, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
const stopVar = nextVar(context, "stop");
|
|
1447
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
1448
|
+
emit(context, indent + 1, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
|
|
1449
|
+
emit(context, indent, "});");
|
|
1450
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
1451
|
+
}
|
|
900
1452
|
}
|
|
901
1453
|
else {
|
|
902
1454
|
emit(context, indent, `${textVar}.textContent = ${quote(node.parts.map((part) => part.value).join(""))};`);
|
|
@@ -905,37 +1457,45 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
|
905
1457
|
return textVar;
|
|
906
1458
|
}
|
|
907
1459
|
function emitObjectBind(context, node, elementVar, attr, cleanupVar, indent) {
|
|
1460
|
+
const binding = parseObjectBindDirective(attr.name);
|
|
1461
|
+
if (!binding) {
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
validateObjectBindModifiers(binding, attr, context, "element");
|
|
908
1465
|
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
909
1466
|
const prevKeysVar = nextVar(context, "boundKeys");
|
|
910
1467
|
const stopVar = nextVar(context, "stop");
|
|
911
1468
|
const attrsVar = nextVar(context, "attrs");
|
|
912
1469
|
const nextKeysVar = nextVar(context, "boundKeys");
|
|
913
1470
|
const keyVar = nextVar(context, "key");
|
|
1471
|
+
const boundKeyVar = nextVar(context, "key");
|
|
914
1472
|
const valueVar = nextVar(context, "value");
|
|
915
1473
|
const staleKeyVar = nextVar(context, "key");
|
|
916
1474
|
const staticClass = getStaticAttrValue(node, "class");
|
|
1475
|
+
const staticStyle = getStaticAttrValue(node, "style");
|
|
917
1476
|
emit(context, indent, `const ${prevKeysVar} = new Set();`);
|
|
918
1477
|
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
919
1478
|
emit(context, indent + 1, `const ${attrsVar} = unwrap(${expression}) ?? {};`);
|
|
920
1479
|
emit(context, indent + 1, `const ${nextKeysVar} = new Set();`);
|
|
921
1480
|
emit(context, indent + 1, `if (${attrsVar} && typeof ${attrsVar} === "object") {`);
|
|
922
1481
|
emit(context, indent + 2, `for (const [${keyVar}, ${valueVar}] of Object.entries(${attrsVar})) {`);
|
|
923
|
-
emit(context, indent + 3,
|
|
924
|
-
|
|
925
|
-
|
|
1482
|
+
emit(context, indent + 3, `const ${boundKeyVar} = ${objectBindKeyExpression(keyVar, binding)};`);
|
|
1483
|
+
emit(context, indent + 3, `${nextKeysVar}.add(${boundKeyVar});`);
|
|
1484
|
+
if (staticClass || staticStyle) {
|
|
1485
|
+
emit(context, indent + 3, `setAttribute(${elementVar}, ${boundKeyVar}, ${boundKeyVar} === "class" && ${staticClass ? "true" : "false"} ? [${quote(staticClass ?? "")}, unwrap(${valueVar})] : ${boundKeyVar} === "style" && ${staticStyle ? "true" : "false"} ? [${quote(staticStyle ?? "")}, unwrap(${valueVar})] : unwrap(${valueVar})${bindOptionsExpression(binding)});`);
|
|
926
1486
|
}
|
|
927
1487
|
else {
|
|
928
|
-
emit(context, indent + 3, `setAttribute(${elementVar}, ${
|
|
1488
|
+
emit(context, indent + 3, `setAttribute(${elementVar}, ${boundKeyVar}, unwrap(${valueVar})${bindOptionsExpression(binding)});`);
|
|
929
1489
|
}
|
|
930
1490
|
emit(context, indent + 2, "}");
|
|
931
1491
|
emit(context, indent + 1, "}");
|
|
932
1492
|
emit(context, indent + 1, `for (const ${staleKeyVar} of ${prevKeysVar}) {`);
|
|
933
1493
|
emit(context, indent + 2, `if (!${nextKeysVar}.has(${staleKeyVar})) {`);
|
|
934
|
-
if (staticClass) {
|
|
935
|
-
emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, ${staleKeyVar} === "class" ? ${quote(staticClass)} : null);`);
|
|
1494
|
+
if (staticClass || staticStyle) {
|
|
1495
|
+
emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, ${staleKeyVar} === "class" && ${staticClass ? "true" : "false"} ? ${quote(staticClass ?? "")} : ${staleKeyVar} === "style" && ${staticStyle ? "true" : "false"} ? ${quote(staticStyle ?? "")} : null${bindOptionsExpression(binding)});`);
|
|
936
1496
|
}
|
|
937
1497
|
else {
|
|
938
|
-
emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null);`);
|
|
1498
|
+
emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null${bindOptionsExpression(binding)});`);
|
|
939
1499
|
}
|
|
940
1500
|
emit(context, indent + 2, "}");
|
|
941
1501
|
emit(context, indent + 1, "}");
|
|
@@ -947,6 +1507,12 @@ function emitObjectBind(context, node, elementVar, attr, cleanupVar, indent) {
|
|
|
947
1507
|
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
948
1508
|
}
|
|
949
1509
|
function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
|
|
1510
|
+
const event = parseObjectOnDirective(attr.name);
|
|
1511
|
+
if (!event) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
validateObjectOnModifiers(event, attr, context, "element");
|
|
1515
|
+
const eventOptions = eventListenerOptions(event);
|
|
950
1516
|
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
951
1517
|
const listenersVar = nextVar(context, "listeners");
|
|
952
1518
|
const stopVar = nextVar(context, "stop");
|
|
@@ -957,7 +1523,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
|
|
|
957
1523
|
emit(context, indent, `const ${listenersVar} = new Map();`);
|
|
958
1524
|
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
959
1525
|
emit(context, indent + 1, `for (const [${eventVar}, ${handlerVar}] of ${listenersVar}) {`);
|
|
960
|
-
emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar});`);
|
|
1526
|
+
emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
|
|
961
1527
|
emit(context, indent + 1, "}");
|
|
962
1528
|
emit(context, indent + 1, `${listenersVar}.clear();`);
|
|
963
1529
|
emit(context, indent + 1, `const ${sourceVar} = unwrap(${expression}) ?? {};`);
|
|
@@ -965,7 +1531,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
|
|
|
965
1531
|
emit(context, indent + 2, `for (const [${eventVar}, ${handlerVar}] of Object.entries(${sourceVar})) {`);
|
|
966
1532
|
emit(context, indent + 3, `if (typeof ${handlerVar} === "function") {`);
|
|
967
1533
|
emit(context, indent + 4, `const ${wrappedHandlerVar} = __mikuru_guardEventHandler(${handlerVar});`);
|
|
968
|
-
emit(context, indent + 4, `${elementVar}.addEventListener(${eventVar}, ${wrappedHandlerVar});`);
|
|
1534
|
+
emit(context, indent + 4, `${elementVar}.addEventListener(${eventVar}, ${wrappedHandlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
|
|
969
1535
|
emit(context, indent + 4, `${listenersVar}.set(${eventVar}, ${wrappedHandlerVar});`);
|
|
970
1536
|
emit(context, indent + 3, "}");
|
|
971
1537
|
emit(context, indent + 2, "}");
|
|
@@ -974,7 +1540,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
|
|
|
974
1540
|
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
975
1541
|
emit(context, indent + 1, `${stopVar}();`);
|
|
976
1542
|
emit(context, indent + 1, `for (const [${eventVar}, ${handlerVar}] of ${listenersVar}) {`);
|
|
977
|
-
emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar});`);
|
|
1543
|
+
emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
|
|
978
1544
|
emit(context, indent + 1, "}");
|
|
979
1545
|
emit(context, indent + 1, `${listenersVar}.clear();`);
|
|
980
1546
|
emit(context, indent, "});");
|
|
@@ -1130,13 +1696,14 @@ function generateFor(context, node, parentVar, cleanupVar, indent, expression, b
|
|
|
1130
1696
|
emit(context, indent, "});");
|
|
1131
1697
|
return startVar;
|
|
1132
1698
|
}
|
|
1133
|
-
function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, beforeVar) {
|
|
1699
|
+
function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, beforeVar, transitionVar) {
|
|
1134
1700
|
const startVar = nextVar(context, "forStart");
|
|
1135
1701
|
const endVar = nextVar(context, "forEnd");
|
|
1136
1702
|
const recordsVar = nextVar(context, "forRecords");
|
|
1137
1703
|
const stopVar = nextVar(context, "stop");
|
|
1138
1704
|
const compiledSource = compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")));
|
|
1139
1705
|
const compiledKey = compileTemplateExpression(keyExpression, "v-for key", toExpressionContext(context, getKeyAttrLocation(node)));
|
|
1706
|
+
const compiledMemo = getMemoExpression(context, node) ?? getOnceMemoExpression(context, node);
|
|
1140
1707
|
emit(context, indent, `const ${recordsVar} = new Map();`);
|
|
1141
1708
|
emit(context, indent, `const ${startVar} = document.createComment("for");`);
|
|
1142
1709
|
emit(context, indent, `const ${endVar} = document.createComment("/for");`);
|
|
@@ -1153,6 +1720,8 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
|
|
|
1153
1720
|
const recordCleanupVar = nextVar(context, "forRecordCleanup");
|
|
1154
1721
|
const itemRefVar = nextVar(context, "forItemRef");
|
|
1155
1722
|
const indexRefVar = nextVar(context, "forIndexRef");
|
|
1723
|
+
const memoVar = compiledMemo ? nextVar(context, "forMemo") : undefined;
|
|
1724
|
+
const memoChangedVar = compiledMemo ? nextVar(context, "forMemoChanged") : undefined;
|
|
1156
1725
|
emit(context, indent + 1, `const ${sourceVar} = unwrap(${compiledSource}) ?? [];`);
|
|
1157
1726
|
emit(context, indent + 1, `const ${nextRecordsVar} = new Map();`);
|
|
1158
1727
|
emit(context, indent + 1, `for (let ${indexVar} = 0; ${indexVar} < ${sourceVar}.length; ${indexVar} += 1) {`);
|
|
@@ -1163,6 +1732,9 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
|
|
|
1163
1732
|
emit(context, indent + 2, `const ${indexName} = ${rawIndexVar};`);
|
|
1164
1733
|
}
|
|
1165
1734
|
emit(context, indent + 2, `const ${keyVar} = unwrap(${compiledKey});`);
|
|
1735
|
+
if (compiledMemo && memoVar) {
|
|
1736
|
+
emit(context, indent + 2, `const ${memoVar} = ${compiledMemo};`);
|
|
1737
|
+
}
|
|
1166
1738
|
emit(context, indent + 2, `let ${recordVar} = ${recordsVar}.get(${keyVar});`);
|
|
1167
1739
|
emit(context, indent + 2, `if (!${recordVar}) {`);
|
|
1168
1740
|
emit(context, indent + 3, `const ${recordCleanupVar} = [];`);
|
|
@@ -1176,14 +1748,32 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
|
|
|
1176
1748
|
emit(context, indent + 4, `const ${indexName} = ${indexRefVar};`);
|
|
1177
1749
|
}
|
|
1178
1750
|
const elementVar = withTemplateRefMode(context, "array", () => generateNode(context, withoutForAttrs(node), parentVar, recordCleanupVar, indent + 4, endVar));
|
|
1179
|
-
|
|
1751
|
+
if (transitionVar) {
|
|
1752
|
+
emitTransitionRegistration(context, elementVar, transitionVar, indent + 4);
|
|
1753
|
+
}
|
|
1754
|
+
emit(context, indent + 4, `${recordVar} = { element: ${elementVar}, cleanups: ${recordCleanupVar}, item: ${itemRefVar}${indexName ? `, index: ${indexRefVar}` : ""}${compiledMemo && memoVar ? `, memo: ${memoVar}` : ""} };`);
|
|
1180
1755
|
emit(context, indent + 3, `}`);
|
|
1181
1756
|
emit(context, indent + 2, `} else {`);
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
emit(context, indent + 3,
|
|
1757
|
+
if (compiledMemo && memoVar && memoChangedVar) {
|
|
1758
|
+
emit(context, indent + 3, `const ${memoChangedVar} = !__mikuru_memoEqual(${recordVar}.memo, ${memoVar});`);
|
|
1759
|
+
emit(context, indent + 3, `if (${memoChangedVar}) {`);
|
|
1760
|
+
emit(context, indent + 4, `${recordVar}.memo = ${memoVar};`);
|
|
1761
|
+
emit(context, indent + 4, `${recordVar}.item.value = ${rawItemVar};`);
|
|
1762
|
+
if (indexName) {
|
|
1763
|
+
emit(context, indent + 4, `${recordVar}.index.value = ${rawIndexVar};`);
|
|
1764
|
+
}
|
|
1765
|
+
emit(context, indent + 3, "}");
|
|
1766
|
+
}
|
|
1767
|
+
else {
|
|
1768
|
+
emit(context, indent + 3, `${recordVar}.item.value = ${rawItemVar};`);
|
|
1769
|
+
if (indexName) {
|
|
1770
|
+
emit(context, indent + 3, `${recordVar}.index.value = ${rawIndexVar};`);
|
|
1771
|
+
}
|
|
1185
1772
|
}
|
|
1186
1773
|
emit(context, indent + 3, `${parentVar}.insertBefore(${recordVar}.element, ${endVar});`);
|
|
1774
|
+
if (transitionVar) {
|
|
1775
|
+
emit(context, indent + 3, `__mikuru_applyTransitionMove(${recordVar}.element, ${transitionVar});`);
|
|
1776
|
+
}
|
|
1187
1777
|
emit(context, indent + 2, `}`);
|
|
1188
1778
|
emit(context, indent + 2, `${nextRecordsVar}.set(${keyVar}, ${recordVar});`);
|
|
1189
1779
|
emit(context, indent + 1, `}`);
|
|
@@ -1234,6 +1824,16 @@ function withTemplateRefMode(context, mode, callback) {
|
|
|
1234
1824
|
context.templateRefMode = previousMode;
|
|
1235
1825
|
}
|
|
1236
1826
|
}
|
|
1827
|
+
function withOnceMode(context, callback) {
|
|
1828
|
+
const previousOnce = context.once;
|
|
1829
|
+
context.once = true;
|
|
1830
|
+
try {
|
|
1831
|
+
return callback();
|
|
1832
|
+
}
|
|
1833
|
+
finally {
|
|
1834
|
+
context.once = previousOnce;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1237
1837
|
function splitTopLevel(source, delimiter) {
|
|
1238
1838
|
const parts = [];
|
|
1239
1839
|
let depth = 0;
|
|
@@ -1665,11 +2265,241 @@ function isIdentifier(value) {
|
|
|
1665
2265
|
return /^[A-Za-z_$][\w$]*$/.test(value);
|
|
1666
2266
|
}
|
|
1667
2267
|
function eventHandlerExpression(expression, context, location) {
|
|
1668
|
-
const validatedExpression =
|
|
2268
|
+
const validatedExpression = validateEventHandlerExpression(expression, context, location);
|
|
1669
2269
|
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(validatedExpression)) {
|
|
1670
2270
|
return validatedExpression;
|
|
1671
2271
|
}
|
|
1672
|
-
return `($event) => (${
|
|
2272
|
+
return `($event) => { return (${compileEventHandlerExpression(validatedExpression, context, location)}); }`;
|
|
2273
|
+
}
|
|
2274
|
+
function validateEventHandlerExpression(expression, context, location) {
|
|
2275
|
+
const source = expression.trim().replace(/;\s*$/, "");
|
|
2276
|
+
if (!source) {
|
|
2277
|
+
throwTemplateError("Invalid template expression for event handler: expression is empty", context, location);
|
|
2278
|
+
}
|
|
2279
|
+
try {
|
|
2280
|
+
const ast = parseExpressionAt(source, 0, { ecmaVersion: "latest" });
|
|
2281
|
+
if (ast.end !== source.length) {
|
|
2282
|
+
throw new Error("Unexpected trailing content");
|
|
2283
|
+
}
|
|
2284
|
+
validateEventHandlerNode(ast, context, source, location);
|
|
2285
|
+
return source;
|
|
2286
|
+
}
|
|
2287
|
+
catch (error) {
|
|
2288
|
+
if (error instanceof Error && error.message.startsWith("Unsupported event handler")) {
|
|
2289
|
+
throwTemplateError(error.message, context, location);
|
|
2290
|
+
}
|
|
2291
|
+
try {
|
|
2292
|
+
return validateTemplateExpression(source, "event handler", toExpressionContext(context, location));
|
|
2293
|
+
}
|
|
2294
|
+
catch {
|
|
2295
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2296
|
+
throwTemplateError(`Invalid template expression for event handler: ${source} (${message})`, context, location);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function compileEventHandlerExpression(expression, context, location) {
|
|
2301
|
+
const ast = parseExpressionAt(expression, 0, { ecmaVersion: "latest" });
|
|
2302
|
+
const edits = [];
|
|
2303
|
+
collectEventHandlerEdits(ast, expression, edits, "read");
|
|
2304
|
+
return applyScriptEdits(expression, edits);
|
|
2305
|
+
}
|
|
2306
|
+
function validateEventHandlerNode(node, context, source, location) {
|
|
2307
|
+
switch (node.type) {
|
|
2308
|
+
case "Identifier":
|
|
2309
|
+
case "Literal":
|
|
2310
|
+
case "TemplateElement":
|
|
2311
|
+
return;
|
|
2312
|
+
case "ThisExpression":
|
|
2313
|
+
case "NewExpression":
|
|
2314
|
+
throw new Error(`Unsupported event handler expression: ${source} (${node.type})`);
|
|
2315
|
+
case "AssignmentExpression":
|
|
2316
|
+
validateEventAssignmentTarget(node.left, context, source, location);
|
|
2317
|
+
validateEventHandlerNode(node.right, context, source, location);
|
|
2318
|
+
return;
|
|
2319
|
+
case "UpdateExpression":
|
|
2320
|
+
validateEventAssignmentTarget(node.argument, context, source, location);
|
|
2321
|
+
return;
|
|
2322
|
+
case "CallExpression":
|
|
2323
|
+
validateEventCallExpression(node, context, source, location);
|
|
2324
|
+
return;
|
|
2325
|
+
case "MemberExpression":
|
|
2326
|
+
validateEventMemberExpression(node, context, source, location);
|
|
2327
|
+
return;
|
|
2328
|
+
case "ChainExpression":
|
|
2329
|
+
validateEventHandlerNode(node.expression, context, source, location);
|
|
2330
|
+
return;
|
|
2331
|
+
case "UnaryExpression":
|
|
2332
|
+
validateEventHandlerNode(node.argument, context, source, location);
|
|
2333
|
+
return;
|
|
2334
|
+
case "BinaryExpression":
|
|
2335
|
+
case "LogicalExpression":
|
|
2336
|
+
validateEventHandlerNode(node.left, context, source, location);
|
|
2337
|
+
validateEventHandlerNode(node.right, context, source, location);
|
|
2338
|
+
return;
|
|
2339
|
+
case "ConditionalExpression":
|
|
2340
|
+
validateEventHandlerNode(node.test, context, source, location);
|
|
2341
|
+
validateEventHandlerNode(node.consequent, context, source, location);
|
|
2342
|
+
validateEventHandlerNode(node.alternate, context, source, location);
|
|
2343
|
+
return;
|
|
2344
|
+
case "ArrayExpression":
|
|
2345
|
+
for (const element of node.elements ?? []) {
|
|
2346
|
+
if (element) {
|
|
2347
|
+
validateEventHandlerNode(element, context, source, location);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return;
|
|
2351
|
+
case "ObjectExpression":
|
|
2352
|
+
for (const property of node.properties ?? []) {
|
|
2353
|
+
validateEventHandlerNode(property, context, source, location);
|
|
2354
|
+
}
|
|
2355
|
+
return;
|
|
2356
|
+
case "Property":
|
|
2357
|
+
if (node.computed) {
|
|
2358
|
+
validateEventHandlerNode(node.key, context, source, location);
|
|
2359
|
+
}
|
|
2360
|
+
validateEventHandlerNode(node.value, context, source, location);
|
|
2361
|
+
return;
|
|
2362
|
+
case "TemplateLiteral":
|
|
2363
|
+
for (const quasi of node.quasis ?? []) {
|
|
2364
|
+
validateEventHandlerNode(quasi, context, source, location);
|
|
2365
|
+
}
|
|
2366
|
+
for (const part of node.expressions ?? []) {
|
|
2367
|
+
validateEventHandlerNode(part, context, source, location);
|
|
2368
|
+
}
|
|
2369
|
+
return;
|
|
2370
|
+
default:
|
|
2371
|
+
throw new Error(`Unsupported event handler expression: ${source} (${node.type})`);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
function validateEventAssignmentTarget(node, context, source, location) {
|
|
2375
|
+
if (node.type === "Identifier") {
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
if (node.type === "MemberExpression") {
|
|
2379
|
+
validateEventMemberExpression(node, context, source, location);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
throw new Error(`Unsupported event handler assignment target: ${source} (${node.type})`);
|
|
2383
|
+
}
|
|
2384
|
+
function validateEventCallExpression(node, context, source, location) {
|
|
2385
|
+
const callee = node.callee;
|
|
2386
|
+
const calleeName = getEventStaticCalleeName(callee, source);
|
|
2387
|
+
if (calleeName === "eval" || calleeName === "Function") {
|
|
2388
|
+
throw new Error(`Unsupported event handler expression: ${source} (${calleeName})`);
|
|
2389
|
+
}
|
|
2390
|
+
validateEventHandlerNode(callee, context, source, location);
|
|
2391
|
+
for (const argument of node.arguments ?? []) {
|
|
2392
|
+
validateEventHandlerNode(argument, context, source, location);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
function validateEventMemberExpression(node, context, source, location) {
|
|
2396
|
+
validateEventHandlerNode(node.object, context, source, location);
|
|
2397
|
+
if (node.computed) {
|
|
2398
|
+
validateEventHandlerNode(node.property, context, source, location);
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
const propertyName = getEventStaticPropertyName(node.property, source);
|
|
2402
|
+
if (propertyName === "constructor" || propertyName === "__proto__" || propertyName === "prototype") {
|
|
2403
|
+
throw new Error(`Unsupported event handler expression: ${source} (${propertyName})`);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
function collectEventHandlerEdits(node, source, edits, mode) {
|
|
2407
|
+
if (!node) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
switch (node.type) {
|
|
2411
|
+
case "Identifier":
|
|
2412
|
+
if (node.name === "$event") {
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
if (mode === "write") {
|
|
2416
|
+
edits.push({ start: node.start, end: node.end, replacement: `${node.name}.value` });
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
if (mode === "callee") {
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
edits.push({ start: node.start, end: node.end, replacement: `unwrap(${node.name})` });
|
|
2423
|
+
return;
|
|
2424
|
+
case "Literal":
|
|
2425
|
+
case "TemplateElement":
|
|
2426
|
+
case "ThisExpression":
|
|
2427
|
+
return;
|
|
2428
|
+
case "AssignmentExpression":
|
|
2429
|
+
collectEventHandlerEdits(node.left, source, edits, "write");
|
|
2430
|
+
collectEventHandlerEdits(node.right, source, edits, "read");
|
|
2431
|
+
return;
|
|
2432
|
+
case "UpdateExpression":
|
|
2433
|
+
collectEventHandlerEdits(node.argument, source, edits, "write");
|
|
2434
|
+
return;
|
|
2435
|
+
case "CallExpression":
|
|
2436
|
+
collectEventHandlerEdits(node.callee, source, edits, "callee");
|
|
2437
|
+
for (const argument of node.arguments ?? []) {
|
|
2438
|
+
collectEventHandlerEdits(argument, source, edits, "read");
|
|
2439
|
+
}
|
|
2440
|
+
return;
|
|
2441
|
+
case "MemberExpression":
|
|
2442
|
+
collectEventHandlerEdits(node.object, source, edits, mode === "callee" ? "read" : mode);
|
|
2443
|
+
if (node.computed) {
|
|
2444
|
+
collectEventHandlerEdits(node.property, source, edits, "read");
|
|
2445
|
+
}
|
|
2446
|
+
return;
|
|
2447
|
+
case "ChainExpression":
|
|
2448
|
+
collectEventHandlerEdits(node.expression, source, edits, mode);
|
|
2449
|
+
return;
|
|
2450
|
+
case "UnaryExpression":
|
|
2451
|
+
collectEventHandlerEdits(node.argument, source, edits, "read");
|
|
2452
|
+
return;
|
|
2453
|
+
case "BinaryExpression":
|
|
2454
|
+
case "LogicalExpression":
|
|
2455
|
+
collectEventHandlerEdits(node.left, source, edits, "read");
|
|
2456
|
+
collectEventHandlerEdits(node.right, source, edits, "read");
|
|
2457
|
+
return;
|
|
2458
|
+
case "ConditionalExpression":
|
|
2459
|
+
collectEventHandlerEdits(node.test, source, edits, "read");
|
|
2460
|
+
collectEventHandlerEdits(node.consequent, source, edits, "read");
|
|
2461
|
+
collectEventHandlerEdits(node.alternate, source, edits, "read");
|
|
2462
|
+
return;
|
|
2463
|
+
case "ArrayExpression":
|
|
2464
|
+
for (const element of node.elements ?? []) {
|
|
2465
|
+
collectEventHandlerEdits(element, source, edits, "read");
|
|
2466
|
+
}
|
|
2467
|
+
return;
|
|
2468
|
+
case "ObjectExpression":
|
|
2469
|
+
for (const property of node.properties ?? []) {
|
|
2470
|
+
collectEventHandlerEdits(property, source, edits, "read");
|
|
2471
|
+
}
|
|
2472
|
+
return;
|
|
2473
|
+
case "Property":
|
|
2474
|
+
if (node.computed) {
|
|
2475
|
+
collectEventHandlerEdits(node.key, source, edits, "read");
|
|
2476
|
+
}
|
|
2477
|
+
collectEventHandlerEdits(node.value, source, edits, "read");
|
|
2478
|
+
return;
|
|
2479
|
+
case "TemplateLiteral":
|
|
2480
|
+
for (const part of node.expressions ?? []) {
|
|
2481
|
+
collectEventHandlerEdits(part, source, edits, "read");
|
|
2482
|
+
}
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
function getEventStaticCalleeName(node, source) {
|
|
2487
|
+
if (node.type === "Identifier") {
|
|
2488
|
+
return source.slice(node.start, node.end);
|
|
2489
|
+
}
|
|
2490
|
+
if (node.type === "MemberExpression") {
|
|
2491
|
+
return getEventStaticPropertyName(node.property, source);
|
|
2492
|
+
}
|
|
2493
|
+
return undefined;
|
|
2494
|
+
}
|
|
2495
|
+
function getEventStaticPropertyName(node, source) {
|
|
2496
|
+
if (node.type === "Identifier") {
|
|
2497
|
+
return source.slice(node.start, node.end);
|
|
2498
|
+
}
|
|
2499
|
+
if (node.type === "Literal") {
|
|
2500
|
+
return String(node.value);
|
|
2501
|
+
}
|
|
2502
|
+
return undefined;
|
|
1673
2503
|
}
|
|
1674
2504
|
function getStringAttr(node, name) {
|
|
1675
2505
|
const attr = node.attrs.find((candidate) => candidate.name === name);
|
|
@@ -1697,6 +2527,24 @@ function getKeyExpression(node) {
|
|
|
1697
2527
|
function getKeyAttrLocation(node) {
|
|
1698
2528
|
return getStringAttrLocation(node, ":key") ?? getStringAttrLocation(node, "v-bind:key");
|
|
1699
2529
|
}
|
|
2530
|
+
function getMemoExpression(context, node) {
|
|
2531
|
+
const attr = node.attrs.find((candidate) => candidate.name === "v-memo");
|
|
2532
|
+
if (!attr) {
|
|
2533
|
+
return undefined;
|
|
2534
|
+
}
|
|
2535
|
+
const expression = validateTemplateExpression(requireAttrValue(attr), "v-memo", toExpressionContext(context, attr.valueLoc));
|
|
2536
|
+
if (!expression.trim().startsWith("[") || !expression.trim().endsWith("]")) {
|
|
2537
|
+
throwTemplateError("v-memo requires an array expression", context, attr.valueLoc ?? attr.loc);
|
|
2538
|
+
}
|
|
2539
|
+
return compileTemplateExpression(expression, "v-memo", toExpressionContext(context, attr.valueLoc));
|
|
2540
|
+
}
|
|
2541
|
+
function getOnceMemoExpression(context, node) {
|
|
2542
|
+
if (!hasAttr(node, "v-once")) {
|
|
2543
|
+
return undefined;
|
|
2544
|
+
}
|
|
2545
|
+
validateOnceAttribute(context, node);
|
|
2546
|
+
return "[]";
|
|
2547
|
+
}
|
|
1700
2548
|
function toExpressionContext(context, location) {
|
|
1701
2549
|
if (!context.source || !location) {
|
|
1702
2550
|
return undefined;
|
|
@@ -1711,7 +2559,7 @@ function withoutAttr(node, name) {
|
|
|
1711
2559
|
return withoutAttrs(node, [name]);
|
|
1712
2560
|
}
|
|
1713
2561
|
function withoutForAttrs(node) {
|
|
1714
|
-
return withoutAttrs(node, ["v-for", "key", ":key", "v-bind:key"]);
|
|
2562
|
+
return withoutAttrs(node, ["v-for", "key", ":key", "v-bind:key", "v-memo", "v-once"]);
|
|
1715
2563
|
}
|
|
1716
2564
|
function withoutAttrs(node, names) {
|
|
1717
2565
|
return {
|
|
@@ -1743,6 +2591,12 @@ function emitComponentProps(context, node, propsVar, attrsVar, indent) {
|
|
|
1743
2591
|
.flatMap((attr) => componentPropEntries(context, attr));
|
|
1744
2592
|
const objectBindAttrs = node.attrs.filter((attr) => isObjectBindAttr(attr));
|
|
1745
2593
|
const objectOnAttrs = node.attrs.filter((attr) => isObjectOnAttr(attr));
|
|
2594
|
+
for (const attr of objectBindAttrs) {
|
|
2595
|
+
validateObjectBindModifiers(parseObjectBindDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
|
|
2596
|
+
}
|
|
2597
|
+
for (const attr of objectOnAttrs) {
|
|
2598
|
+
validateObjectOnModifiers(parseObjectOnDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
|
|
2599
|
+
}
|
|
1746
2600
|
const slots = collectComponentSlots(context, node);
|
|
1747
2601
|
const defaultSlot = slots.find((slot) => !slot.nameExpression && slot.name === "default");
|
|
1748
2602
|
const needsProxy = objectBindAttrs.length > 0 || objectOnAttrs.length > 0;
|
|
@@ -2072,12 +2926,15 @@ function componentPropEntries(context, attr) {
|
|
|
2072
2926
|
validateModelModifiers(modelDirective, attr, context);
|
|
2073
2927
|
const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
2074
2928
|
const valueExpression = compileTemplateExpression(expression, attr.name, toExpressionContext(context, attr.valueLoc));
|
|
2929
|
+
const propName = modelDirective.argument ?? "modelValue";
|
|
2930
|
+
const updatePropName = toComponentEventProp(`update:${propName}`);
|
|
2931
|
+
const modifiersPropName = modelDirective.argument ? `${propName}Modifiers` : "modelModifiers";
|
|
2075
2932
|
const entries = [
|
|
2076
|
-
`get
|
|
2077
|
-
|
|
2933
|
+
`get ${quotePropertyName(propName)}() { return unwrap(${valueExpression}); }`,
|
|
2934
|
+
`${quotePropertyName(updatePropName)}: __mikuru_guardEventHandler(($value) => { ${expression}.value = $value; })`
|
|
2078
2935
|
];
|
|
2079
2936
|
if (modelDirective.modifiers.length > 0) {
|
|
2080
|
-
entries.push(
|
|
2937
|
+
entries.push(`${quotePropertyName(modifiersPropName)}: { ${modelDirective.modifiers.map((modifier) => `${quotePropertyName(modifier)}: true`).join(", ")} }`);
|
|
2081
2938
|
}
|
|
2082
2939
|
return entries;
|
|
2083
2940
|
}
|
|
@@ -2085,13 +2942,36 @@ function componentPropEntries(context, attr) {
|
|
|
2085
2942
|
if (event) {
|
|
2086
2943
|
validateComponentEventModifiers(event, attr, context);
|
|
2087
2944
|
const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
2945
|
+
const handlerExpression = componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context);
|
|
2946
|
+
if (event.nameExpression) {
|
|
2947
|
+
const eventNameExpression = compileTemplateExpression(event.nameExpression, attr.name, toExpressionContext(context, attr.loc));
|
|
2948
|
+
return [
|
|
2949
|
+
`[${componentEventPropRuntimeExpression(`String(unwrap(${eventNameExpression}) ?? "")`)}]: __mikuru_guardEventHandler(${handlerExpression})`
|
|
2950
|
+
];
|
|
2951
|
+
}
|
|
2088
2952
|
return [
|
|
2089
|
-
`${quotePropertyName(toComponentEventProp(event.name))}: __mikuru_guardEventHandler(${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)})`
|
|
2953
|
+
`${quotePropertyName(toComponentEventProp(event.name ?? ""))}: __mikuru_guardEventHandler(${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)})`
|
|
2090
2954
|
];
|
|
2091
2955
|
}
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2956
|
+
const bindDirective = parseBindDirective(attr.name);
|
|
2957
|
+
const dynamicBinding = bindDirective?.nameExpression ? bindDirective : undefined;
|
|
2958
|
+
if (dynamicBinding) {
|
|
2959
|
+
validateBindModifiers(dynamicBinding, attr, context, "component");
|
|
2960
|
+
const nameExpression = compileTemplateExpression(dynamicBinding.nameExpression ?? "", attr.name, toExpressionContext(context, attr.loc));
|
|
2961
|
+
const valueExpression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
2962
|
+
const propertyName = bindNameExpression(`String(unwrap(${nameExpression}) ?? "")`, dynamicBinding);
|
|
2963
|
+
if (context.once) {
|
|
2964
|
+
return [`[${propertyName}]: unwrap(${valueExpression})`];
|
|
2965
|
+
}
|
|
2966
|
+
return [`get [${propertyName}]() { return unwrap(${valueExpression}); }`];
|
|
2967
|
+
}
|
|
2968
|
+
const bindingName = bindDirective?.name;
|
|
2969
|
+
if (bindingName && bindDirective) {
|
|
2970
|
+
validateBindModifiers(bindDirective, attr, context, "component");
|
|
2094
2971
|
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
2972
|
+
if (context.once) {
|
|
2973
|
+
return [`${quotePropertyName(bindingName)}: unwrap(${expression})`];
|
|
2974
|
+
}
|
|
2095
2975
|
return [`get ${quotePropertyName(bindingName)}() { return unwrap(${expression}); }`];
|
|
2096
2976
|
}
|
|
2097
2977
|
if (attr.name === "v-show") {
|
|
@@ -2151,7 +3031,8 @@ function getTransitionOptionsExpression(context, node) {
|
|
|
2151
3031
|
["enter-to-class", "enterToClass"],
|
|
2152
3032
|
["leave-from-class", "leaveFromClass"],
|
|
2153
3033
|
["leave-active-class", "leaveActiveClass"],
|
|
2154
|
-
["leave-to-class", "leaveToClass"]
|
|
3034
|
+
["leave-to-class", "leaveToClass"],
|
|
3035
|
+
["move-class", "moveClass"]
|
|
2155
3036
|
];
|
|
2156
3037
|
for (const [attrName, optionName] of classAttrs) {
|
|
2157
3038
|
const expression = getTransitionAttrExpression(context, node, attrName);
|
|
@@ -2176,7 +3057,7 @@ function getTransitionAttrExpression(context, node, name, fallback) {
|
|
|
2176
3057
|
return fallback === undefined ? undefined : quote(fallback);
|
|
2177
3058
|
}
|
|
2178
3059
|
function validateTransitionAttributes(context, node) {
|
|
2179
|
-
const supported =
|
|
3060
|
+
const supported = [
|
|
2180
3061
|
"name",
|
|
2181
3062
|
"appear",
|
|
2182
3063
|
"mode",
|
|
@@ -2186,13 +3067,33 @@ function validateTransitionAttributes(context, node) {
|
|
|
2186
3067
|
"leave-from-class",
|
|
2187
3068
|
"leave-active-class",
|
|
2188
3069
|
"leave-to-class"
|
|
2189
|
-
]);
|
|
3070
|
+
].map((name) => ({ name, display: name }));
|
|
3071
|
+
for (const attr of node.attrs) {
|
|
3072
|
+
const name = getBindingName(attr.name) ?? attr.name;
|
|
3073
|
+
if (supported.some((candidate) => candidate.name === name)) {
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3076
|
+
throwUnsupportedSpecialAttribute(context, "Transition", attr, supported, "name, appear, mode, and CSS class override attributes");
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
function validateTransitionGroupAttributes(context, node) {
|
|
3080
|
+
const supported = [
|
|
3081
|
+
"name",
|
|
3082
|
+
"tag",
|
|
3083
|
+
"enter-from-class",
|
|
3084
|
+
"enter-active-class",
|
|
3085
|
+
"enter-to-class",
|
|
3086
|
+
"leave-from-class",
|
|
3087
|
+
"leave-active-class",
|
|
3088
|
+
"leave-to-class",
|
|
3089
|
+
"move-class"
|
|
3090
|
+
].map((name) => ({ name, display: name }));
|
|
2190
3091
|
for (const attr of node.attrs) {
|
|
2191
3092
|
const name = getBindingName(attr.name) ?? attr.name;
|
|
2192
|
-
if (supported.
|
|
3093
|
+
if (supported.some((candidate) => candidate.name === name)) {
|
|
2193
3094
|
continue;
|
|
2194
3095
|
}
|
|
2195
|
-
|
|
3096
|
+
throwUnsupportedSpecialAttribute(context, "TransitionGroup", attr, supported, "name, tag, and CSS class override attributes");
|
|
2196
3097
|
}
|
|
2197
3098
|
}
|
|
2198
3099
|
function getSingleElementChild(context, node, label) {
|
|
@@ -2202,6 +3103,13 @@ function getSingleElementChild(context, node, label) {
|
|
|
2202
3103
|
}
|
|
2203
3104
|
return [meaningful[0]];
|
|
2204
3105
|
}
|
|
3106
|
+
function getAsyncBoundaryChildren(context, node) {
|
|
3107
|
+
const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
|
|
3108
|
+
if (meaningful.length === 0) {
|
|
3109
|
+
throwTemplateError("<AsyncBoundary> requires at least one child", context, node.loc);
|
|
3110
|
+
}
|
|
3111
|
+
return node.children;
|
|
3112
|
+
}
|
|
2205
3113
|
function getErrorBoundaryFallbackExpression(context, node) {
|
|
2206
3114
|
const fallbackAttr = node.attrs.find((attr) => getBindingName(attr.name) === "fallback");
|
|
2207
3115
|
if (!fallbackAttr) {
|
|
@@ -2216,13 +3124,82 @@ function getErrorBoundaryResetKeyExpression(context, node) {
|
|
|
2216
3124
|
}
|
|
2217
3125
|
return compileTemplateExpression(requireAttrValue(resetKeyAttr), resetKeyAttr.name, toExpressionContext(context, resetKeyAttr.valueLoc));
|
|
2218
3126
|
}
|
|
3127
|
+
function getAsyncBoundaryLoadingExpression(context, node) {
|
|
3128
|
+
const loadingAttr = node.attrs.find((attr) => getBindingName(attr.name) === "loading");
|
|
3129
|
+
if (!loadingAttr) {
|
|
3130
|
+
throwTemplateError("<AsyncBoundary> requires :loading to resolve to a component object", context, node.loc);
|
|
3131
|
+
}
|
|
3132
|
+
return compileTemplateExpression(requireAttrValue(loadingAttr), loadingAttr.name, toExpressionContext(context, loadingAttr.valueLoc));
|
|
3133
|
+
}
|
|
3134
|
+
function getAsyncBoundaryFallbackExpression(context, node) {
|
|
3135
|
+
const fallbackAttr = node.attrs.find((attr) => getBindingName(attr.name) === "fallback");
|
|
3136
|
+
if (!fallbackAttr) {
|
|
3137
|
+
throwTemplateError("<AsyncBoundary> requires :fallback to resolve to a component object", context, node.loc);
|
|
3138
|
+
}
|
|
3139
|
+
return compileTemplateExpression(requireAttrValue(fallbackAttr), fallbackAttr.name, toExpressionContext(context, fallbackAttr.valueLoc));
|
|
3140
|
+
}
|
|
3141
|
+
function getAsyncBoundaryDelayExpression(context, node) {
|
|
3142
|
+
const delayAttr = node.attrs.find((attr) => getBindingName(attr.name) === "delay");
|
|
3143
|
+
if (!delayAttr) {
|
|
3144
|
+
return "0";
|
|
3145
|
+
}
|
|
3146
|
+
return compileTemplateExpression(requireAttrValue(delayAttr), delayAttr.name, toExpressionContext(context, delayAttr.valueLoc));
|
|
3147
|
+
}
|
|
3148
|
+
function getAsyncBoundaryTimeoutExpression(context, node) {
|
|
3149
|
+
const timeoutAttr = node.attrs.find((attr) => getBindingName(attr.name) === "timeout");
|
|
3150
|
+
if (!timeoutAttr) {
|
|
3151
|
+
return "undefined";
|
|
3152
|
+
}
|
|
3153
|
+
return compileTemplateExpression(requireAttrValue(timeoutAttr), timeoutAttr.name, toExpressionContext(context, timeoutAttr.valueLoc));
|
|
3154
|
+
}
|
|
3155
|
+
function getKeepAliveOptionExpression(context, node, name) {
|
|
3156
|
+
const dynamicAttr = node.attrs.find((attr) => getBindingName(attr.name) === name);
|
|
3157
|
+
if (dynamicAttr) {
|
|
3158
|
+
return compileTemplateExpression(requireAttrValue(dynamicAttr), dynamicAttr.name, toExpressionContext(context, dynamicAttr.valueLoc));
|
|
3159
|
+
}
|
|
3160
|
+
const staticValue = getStaticAttrValue(node, name);
|
|
3161
|
+
return staticValue === undefined ? undefined : quote(staticValue);
|
|
3162
|
+
}
|
|
2219
3163
|
function validateErrorBoundaryAttributes(context, node) {
|
|
3164
|
+
const supported = [
|
|
3165
|
+
{ name: "fallback", display: ":fallback" },
|
|
3166
|
+
{ name: "reset-key", display: ":reset-key" }
|
|
3167
|
+
];
|
|
2220
3168
|
for (const attr of node.attrs) {
|
|
2221
3169
|
const bindingName = getBindingName(attr.name);
|
|
2222
3170
|
if (bindingName === "fallback" || bindingName === "reset-key") {
|
|
2223
3171
|
continue;
|
|
2224
3172
|
}
|
|
2225
|
-
|
|
3173
|
+
throwUnsupportedSpecialAttribute(context, "ErrorBoundary", attr, supported);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
function validateAsyncBoundaryAttributes(context, node) {
|
|
3177
|
+
const supported = [
|
|
3178
|
+
{ name: "loading", display: ":loading" },
|
|
3179
|
+
{ name: "fallback", display: ":fallback" },
|
|
3180
|
+
{ name: "delay", display: ":delay" },
|
|
3181
|
+
{ name: "timeout", display: ":timeout" }
|
|
3182
|
+
];
|
|
3183
|
+
for (const attr of node.attrs) {
|
|
3184
|
+
const bindingName = getBindingName(attr.name);
|
|
3185
|
+
if (bindingName === "loading" || bindingName === "fallback" || bindingName === "delay" || bindingName === "timeout") {
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
throwUnsupportedSpecialAttribute(context, "AsyncBoundary", attr, supported);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
function validateKeepAliveAttributes(context, node) {
|
|
3192
|
+
const supported = [
|
|
3193
|
+
{ name: "include", display: ":include" },
|
|
3194
|
+
{ name: "exclude", display: ":exclude" },
|
|
3195
|
+
{ name: "max", display: ":max" }
|
|
3196
|
+
];
|
|
3197
|
+
for (const attr of node.attrs) {
|
|
3198
|
+
const name = getBindingName(attr.name) ?? attr.name;
|
|
3199
|
+
if (name === "include" || name === "exclude" || name === "max") {
|
|
3200
|
+
continue;
|
|
3201
|
+
}
|
|
3202
|
+
throwUnsupportedSpecialAttribute(context, "KeepAlive", attr, supported, "include, exclude, and max");
|
|
2226
3203
|
}
|
|
2227
3204
|
}
|
|
2228
3205
|
function getTeleportToExpression(context, node) {
|
|
@@ -2244,14 +3221,56 @@ function getTeleportDisabledExpression(context, node) {
|
|
|
2244
3221
|
return hasStaticBooleanAttr(node, "disabled") ? "true" : "false";
|
|
2245
3222
|
}
|
|
2246
3223
|
function validateTeleportAttributes(context, node) {
|
|
3224
|
+
const supported = [
|
|
3225
|
+
{ name: "to", display: "to" },
|
|
3226
|
+
{ name: "disabled", display: "disabled" }
|
|
3227
|
+
];
|
|
2247
3228
|
for (const attr of node.attrs) {
|
|
2248
3229
|
const name = getBindingName(attr.name) ?? attr.name;
|
|
2249
3230
|
if (name === "to" || name === "disabled") {
|
|
2250
3231
|
continue;
|
|
2251
3232
|
}
|
|
2252
|
-
|
|
3233
|
+
throwUnsupportedSpecialAttribute(context, "Teleport", attr, supported);
|
|
2253
3234
|
}
|
|
2254
3235
|
}
|
|
3236
|
+
function throwUnsupportedSpecialAttribute(context, tagName, attr, supported, supportedLabel = supported.map((candidate) => candidate.display).join(", ")) {
|
|
3237
|
+
const normalizedName = getBindingName(attr.name) ?? attr.name;
|
|
3238
|
+
const suggestion = suggestAttributeName(normalizedName, supported);
|
|
3239
|
+
const suggestionMessage = suggestion ? ` Did you mean ${suggestion.display}?` : "";
|
|
3240
|
+
throwTemplateError(`Unsupported attribute ${quote(attr.name)} on <${tagName}>.${suggestionMessage} <${tagName}> only supports ${supportedLabel} in v1.`, context, attr.loc);
|
|
3241
|
+
}
|
|
3242
|
+
function suggestAttributeName(name, supported) {
|
|
3243
|
+
return suggestName(name, supported);
|
|
3244
|
+
}
|
|
3245
|
+
function suggestName(name, supported) {
|
|
3246
|
+
let best;
|
|
3247
|
+
for (const candidate of supported) {
|
|
3248
|
+
const distance = editDistance(name, candidate.name);
|
|
3249
|
+
if (!best || distance < best.distance) {
|
|
3250
|
+
best = { candidate, distance };
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
if (!best) {
|
|
3254
|
+
return undefined;
|
|
3255
|
+
}
|
|
3256
|
+
const maxDistance = Math.max(1, Math.floor(best.candidate.name.length / 3));
|
|
3257
|
+
return best.distance <= maxDistance ? best.candidate : undefined;
|
|
3258
|
+
}
|
|
3259
|
+
function editDistance(left, right) {
|
|
3260
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
3261
|
+
const current = Array.from({ length: right.length + 1 }, () => 0);
|
|
3262
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
3263
|
+
current[0] = leftIndex;
|
|
3264
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
3265
|
+
const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
3266
|
+
current[rightIndex] = Math.min(previous[rightIndex] + 1, current[rightIndex - 1] + 1, previous[rightIndex - 1] + cost);
|
|
3267
|
+
}
|
|
3268
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
3269
|
+
previous[index] = current[index];
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
return previous[right.length] ?? 0;
|
|
3273
|
+
}
|
|
2255
3274
|
function toComponentEventProp(eventName) {
|
|
2256
3275
|
return `on${eventName
|
|
2257
3276
|
.split(/[-:]/)
|
|
@@ -2259,18 +3278,67 @@ function toComponentEventProp(eventName) {
|
|
|
2259
3278
|
.map((part) => part[0]?.toUpperCase() + part.slice(1))
|
|
2260
3279
|
.join("")}`;
|
|
2261
3280
|
}
|
|
2262
|
-
function validateAttributes(node) {
|
|
3281
|
+
function validateAttributes(context, node) {
|
|
3282
|
+
const htmlAttr = getAttr(node, "v-html");
|
|
3283
|
+
const textAttr = getAttr(node, "v-text");
|
|
3284
|
+
if (htmlAttr && textAttr) {
|
|
3285
|
+
throwTemplateError("v-html and v-text cannot be used on the same element", context, textAttr.loc);
|
|
3286
|
+
}
|
|
2263
3287
|
for (const attr of node.attrs) {
|
|
2264
3288
|
if (attr.name.startsWith("v-") && !isSupportedDirectiveAttr(attr)) {
|
|
2265
|
-
|
|
3289
|
+
throwUnsupportedDirective(context, attr);
|
|
3290
|
+
}
|
|
3291
|
+
if ((attr.name === "v-html" || attr.name === "v-text") && attr.value === true) {
|
|
3292
|
+
throwTemplateError(`${attr.name} requires a value`, context, attr.loc);
|
|
3293
|
+
}
|
|
3294
|
+
if ((attr.name === "v-pre" || attr.name === "v-cloak") && attr.value !== true) {
|
|
3295
|
+
throwTemplateError(`${attr.name} does not accept a value`, context, attr.valueLoc ?? attr.loc);
|
|
3296
|
+
}
|
|
3297
|
+
if (attr.name === "v-once") {
|
|
3298
|
+
validateOnceAttribute(context, node);
|
|
2266
3299
|
}
|
|
3300
|
+
if (attr.name === "v-memo") {
|
|
3301
|
+
getMemoExpression(context, node);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
function validateOnceAttribute(context, node) {
|
|
3306
|
+
const attr = node.attrs.find((candidate) => candidate.name === "v-once");
|
|
3307
|
+
if (attr && attr.value !== true) {
|
|
3308
|
+
throwTemplateError("v-once does not accept a value", context, attr.valueLoc ?? attr.loc);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
function validatePreAttribute(context, node) {
|
|
3312
|
+
const attr = node.attrs.find((candidate) => candidate.name === "v-pre");
|
|
3313
|
+
if (attr && attr.value !== true) {
|
|
3314
|
+
throwTemplateError("v-pre does not accept a value", context, attr.valueLoc ?? attr.loc);
|
|
2267
3315
|
}
|
|
2268
3316
|
}
|
|
3317
|
+
function throwUnsupportedDirective(context, attr) {
|
|
3318
|
+
const suggestion = suggestDirectiveName(attr.name);
|
|
3319
|
+
const suggestionMessage = suggestion ? ` Did you mean ${suggestion}?` : "";
|
|
3320
|
+
throwTemplateError(`Unsupported directive ${quote(attr.name)}.${suggestionMessage}`, context, attr.loc);
|
|
3321
|
+
}
|
|
3322
|
+
function suggestDirectiveName(name) {
|
|
3323
|
+
const supported = ["v-if", "v-else-if", "v-else", "v-for", "v-show", "v-html", "v-text", "v-pre", "v-cloak", "v-once", "v-memo", "v-model", "v-bind", "v-on", "v-slot"];
|
|
3324
|
+
const directiveName = name.includes(":") ? name.slice(0, name.indexOf(":")) : name.includes(".") ? name.slice(0, name.indexOf(".")) : name;
|
|
3325
|
+
const suggestion = suggestName(directiveName, supported.map((candidate) => ({ name: candidate, display: candidate })));
|
|
3326
|
+
if (!suggestion) {
|
|
3327
|
+
return undefined;
|
|
3328
|
+
}
|
|
3329
|
+
if (suggestion.name === "v-bind" && name.includes(":")) {
|
|
3330
|
+
return `v-bind:${name.slice(name.indexOf(":") + 1)}`;
|
|
3331
|
+
}
|
|
3332
|
+
if (suggestion.name === "v-on" && name.includes(":")) {
|
|
3333
|
+
return `v-on:${name.slice(name.indexOf(":") + 1)}`;
|
|
3334
|
+
}
|
|
3335
|
+
return suggestion.display;
|
|
3336
|
+
}
|
|
2269
3337
|
function isDirectiveAttr(attr) {
|
|
2270
3338
|
return isSupportedDirectiveAttr(attr) || attr.name.startsWith("@") || attr.name.startsWith(":");
|
|
2271
3339
|
}
|
|
2272
3340
|
function isStructuralAttr(attr) {
|
|
2273
|
-
return attr.name === "v-if" || attr.name === "v-else-if" || attr.name === "v-else" || attr.name === "v-for";
|
|
3341
|
+
return attr.name === "v-if" || attr.name === "v-else-if" || attr.name === "v-else" || attr.name === "v-for" || attr.name === "v-once" || attr.name === "v-memo";
|
|
2274
3342
|
}
|
|
2275
3343
|
function isSlotDirectiveAttr(attr) {
|
|
2276
3344
|
return attr.name === "v-slot" || attr.name.startsWith("v-slot:") || attr.name.startsWith("#");
|
|
@@ -2281,19 +3349,55 @@ function isSupportedDirectiveAttr(attr) {
|
|
|
2281
3349
|
attr.name === "v-else" ||
|
|
2282
3350
|
attr.name === "v-for" ||
|
|
2283
3351
|
attr.name === "v-show" ||
|
|
3352
|
+
attr.name === "v-html" ||
|
|
3353
|
+
attr.name === "v-text" ||
|
|
3354
|
+
attr.name === "v-pre" ||
|
|
3355
|
+
attr.name === "v-cloak" ||
|
|
3356
|
+
attr.name === "v-once" ||
|
|
3357
|
+
attr.name === "v-memo" ||
|
|
2284
3358
|
Boolean(parseModelDirective(attr.name)) ||
|
|
2285
3359
|
isObjectBindAttr(attr) ||
|
|
2286
3360
|
isObjectOnAttr(attr) ||
|
|
3361
|
+
Boolean(getDynamicBindingArgument(attr.name)) ||
|
|
3362
|
+
Boolean(getDynamicEventArgument(attr.name)) ||
|
|
2287
3363
|
Boolean(parseEventDirective(attr.name)) ||
|
|
2288
3364
|
Boolean(getBindingName(attr.name)));
|
|
2289
3365
|
}
|
|
2290
3366
|
function isObjectBindAttr(attr) {
|
|
2291
|
-
return attr.name
|
|
3367
|
+
return Boolean(parseObjectBindDirective(attr.name));
|
|
3368
|
+
}
|
|
3369
|
+
function parseObjectBindDirective(name) {
|
|
3370
|
+
if (name === "v-bind") {
|
|
3371
|
+
return { modifiers: [] };
|
|
3372
|
+
}
|
|
3373
|
+
if (!name.startsWith("v-bind.")) {
|
|
3374
|
+
return undefined;
|
|
3375
|
+
}
|
|
3376
|
+
return { modifiers: name.slice("v-bind.".length).split(".").filter(Boolean) };
|
|
3377
|
+
}
|
|
3378
|
+
function getContentDirectiveAttr(node) {
|
|
3379
|
+
return getAttr(node, "v-html") ?? getAttr(node, "v-text");
|
|
3380
|
+
}
|
|
3381
|
+
function getAttr(node, name) {
|
|
3382
|
+
return node.attrs.find((attr) => attr.name === name);
|
|
2292
3383
|
}
|
|
2293
3384
|
function isObjectOnAttr(attr) {
|
|
2294
|
-
return attr.name
|
|
3385
|
+
return Boolean(parseObjectOnDirective(attr.name));
|
|
3386
|
+
}
|
|
3387
|
+
function parseObjectOnDirective(name) {
|
|
3388
|
+
if (name === "v-on") {
|
|
3389
|
+
return { modifiers: [] };
|
|
3390
|
+
}
|
|
3391
|
+
if (!name.startsWith("v-on.")) {
|
|
3392
|
+
return undefined;
|
|
3393
|
+
}
|
|
3394
|
+
return { modifiers: name.slice("v-on.".length).split(".").filter(Boolean) };
|
|
2295
3395
|
}
|
|
2296
3396
|
function parseEventDirective(name) {
|
|
3397
|
+
const dynamic = getDynamicEventArgument(name);
|
|
3398
|
+
if (dynamic) {
|
|
3399
|
+
return dynamic;
|
|
3400
|
+
}
|
|
2297
3401
|
const rawName = getEventName(name);
|
|
2298
3402
|
if (!rawName) {
|
|
2299
3403
|
return undefined;
|
|
@@ -2308,30 +3412,49 @@ function parseModelDirective(name) {
|
|
|
2308
3412
|
if (name === "v-model") {
|
|
2309
3413
|
return { modifiers: [] };
|
|
2310
3414
|
}
|
|
2311
|
-
if (!name.startsWith("v-model.")) {
|
|
3415
|
+
if (!name.startsWith("v-model.") && !name.startsWith("v-model:")) {
|
|
2312
3416
|
return undefined;
|
|
2313
3417
|
}
|
|
3418
|
+
if (name.startsWith("v-model:")) {
|
|
3419
|
+
const raw = name.slice("v-model:".length);
|
|
3420
|
+
const [argument = "", ...modifiers] = raw.split(".");
|
|
3421
|
+
return { argument, modifiers: modifiers.filter(Boolean) };
|
|
3422
|
+
}
|
|
2314
3423
|
return { modifiers: name.slice("v-model.".length).split(".").filter(Boolean) };
|
|
2315
3424
|
}
|
|
2316
3425
|
function validateModelModifiers(model, attr, context) {
|
|
2317
|
-
const supportedModifiers =
|
|
3426
|
+
const supportedModifiers = ["trim", "number", "lazy"];
|
|
3427
|
+
if (model.argument !== undefined && !model.argument) {
|
|
3428
|
+
throwTemplateError("v-model argument must not be empty", context, attr.loc);
|
|
3429
|
+
}
|
|
2318
3430
|
for (const modifier of model.modifiers) {
|
|
2319
|
-
if (!supportedModifiers.
|
|
2320
|
-
|
|
3431
|
+
if (!supportedModifiers.includes(modifier)) {
|
|
3432
|
+
const suggestion = suggestModifierName(modifier, supportedModifiers);
|
|
3433
|
+
const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
|
|
3434
|
+
throwTemplateError(`Unsupported v-model modifier .${modifier}.${suggestionMessage}`, context, attr.loc);
|
|
2321
3435
|
}
|
|
2322
3436
|
}
|
|
2323
3437
|
}
|
|
2324
|
-
|
|
3438
|
+
const modelValueProperty = "__mikuruModelValue";
|
|
3439
|
+
function modelElementValueExpression(targetExpression, modifiers) {
|
|
3440
|
+
const valueExpression = `(${quote(modelValueProperty)} in ${targetExpression} ? ${targetExpression}[${quote(modelValueProperty)}] : ${targetExpression}.getAttribute("value") ?? (${targetExpression}.tagName === "OPTION" ? (${targetExpression}.textContent ?? "") : "on"))`;
|
|
3441
|
+
return modifiers.includes("number") ? `Number(${valueExpression})` : valueExpression;
|
|
3442
|
+
}
|
|
3443
|
+
function modelAssignedValue(modelMode, modifiers, expression) {
|
|
2325
3444
|
if (modelMode === "checkbox") {
|
|
2326
|
-
|
|
3445
|
+
const valueExpression = modelElementValueExpression("$event.target", modifiers);
|
|
3446
|
+
return `(() => { const checked = $event.target.checked; const current = unwrap(${expression}); const value = ${valueExpression}; if (Array.isArray(current)) { const hasValue = current.some((item) => Object.is(item, value)); return checked ? (hasValue ? current : [...current, value]) : current.filter((item) => !Object.is(item, value)); } return checked; })()`;
|
|
2327
3447
|
}
|
|
2328
3448
|
if (modelMode === "radio") {
|
|
2329
|
-
|
|
2330
|
-
return modifiers.includes("number") ? `Number(${valueExpression})` : valueExpression;
|
|
3449
|
+
return modelElementValueExpression("$event.target", modifiers);
|
|
2331
3450
|
}
|
|
2332
3451
|
if (modelMode === "select-multiple") {
|
|
2333
|
-
const valueExpression = `Array.from($event.target.options).filter((option) => option.selected).map((option) =>
|
|
2334
|
-
return
|
|
3452
|
+
const valueExpression = `Array.from($event.target.options).filter((option) => option.selected).map((option) => ${modelElementValueExpression("option", modifiers)})`;
|
|
3453
|
+
return valueExpression;
|
|
3454
|
+
}
|
|
3455
|
+
if (modelMode === "select") {
|
|
3456
|
+
const valueExpression = `(() => { const option = $event.target.selectedOptions[0]; return option ? ${modelElementValueExpression("option", modifiers)} : ""; })()`;
|
|
3457
|
+
return valueExpression;
|
|
2335
3458
|
}
|
|
2336
3459
|
let valueExpression = "$event.target.value";
|
|
2337
3460
|
if (modifiers.includes("trim")) {
|
|
@@ -2343,10 +3466,12 @@ function modelAssignedValue(modelMode, modifiers) {
|
|
|
2343
3466
|
return valueExpression;
|
|
2344
3467
|
}
|
|
2345
3468
|
function validateEventModifiers(event, attr, context) {
|
|
2346
|
-
const supportedModifiers =
|
|
3469
|
+
const supportedModifiers = [...eventControlModifiers, ...eventOptionModifiers, ...eventSystemModifiers, ...eventMouseModifiers, ...eventKeyModifiers, "exact"];
|
|
2347
3470
|
for (const modifier of event.modifiers) {
|
|
2348
|
-
if (!supportedModifiers.
|
|
2349
|
-
|
|
3471
|
+
if (!supportedModifiers.includes(modifier)) {
|
|
3472
|
+
const suggestion = suggestModifierName(modifier, supportedModifiers);
|
|
3473
|
+
const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
|
|
3474
|
+
throwTemplateError(`Unsupported event modifier .${modifier}.${suggestionMessage}`, context, attr.loc);
|
|
2350
3475
|
}
|
|
2351
3476
|
}
|
|
2352
3477
|
if (event.modifiers.includes("passive") && event.modifiers.includes("prevent")) {
|
|
@@ -2354,12 +3479,30 @@ function validateEventModifiers(event, attr, context) {
|
|
|
2354
3479
|
}
|
|
2355
3480
|
}
|
|
2356
3481
|
function validateComponentEventModifiers(event, attr, context) {
|
|
3482
|
+
const supportedModifiers = ["once"];
|
|
2357
3483
|
for (const modifier of event.modifiers) {
|
|
2358
3484
|
if (modifier !== "once") {
|
|
2359
|
-
|
|
3485
|
+
const suggestion = suggestModifierName(modifier, supportedModifiers);
|
|
3486
|
+
const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
|
|
3487
|
+
throwTemplateError(`Event modifier .${modifier} is only supported on DOM events.${suggestionMessage}`, context, attr.loc);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
function validateObjectOnModifiers(event, attr, context, target) {
|
|
3492
|
+
if (target === "component" && event.modifiers.length > 0) {
|
|
3493
|
+
throwTemplateError("Object v-on modifiers are only supported on native elements", context, attr.loc);
|
|
3494
|
+
}
|
|
3495
|
+
for (const modifier of event.modifiers) {
|
|
3496
|
+
if (!eventOptionModifiers.includes(modifier)) {
|
|
3497
|
+
const suggestion = suggestModifierName(modifier, eventOptionModifiers);
|
|
3498
|
+
const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
|
|
3499
|
+
throwTemplateError(`Object v-on modifier .${modifier} is not supported. Use .once, .capture, or .passive.${suggestionMessage}`, context, attr.loc);
|
|
2360
3500
|
}
|
|
2361
3501
|
}
|
|
2362
3502
|
}
|
|
3503
|
+
function suggestModifierName(name, supported) {
|
|
3504
|
+
return suggestName(name, supported.map((candidate) => ({ name: candidate, display: candidate })))?.name;
|
|
3505
|
+
}
|
|
2363
3506
|
function eventListenerOptions(event) {
|
|
2364
3507
|
const options = [
|
|
2365
3508
|
event.modifiers.includes("capture") ? "capture: true" : undefined,
|
|
@@ -2368,6 +3511,66 @@ function eventListenerOptions(event) {
|
|
|
2368
3511
|
].filter(Boolean);
|
|
2369
3512
|
return options.length ? `{ ${options.join(", ")} }` : undefined;
|
|
2370
3513
|
}
|
|
3514
|
+
const eventControlModifiers = ["prevent", "stop", "self"];
|
|
3515
|
+
const eventOptionModifiers = ["once", "capture", "passive"];
|
|
3516
|
+
const eventSystemModifiers = ["ctrl", "shift", "alt", "meta"];
|
|
3517
|
+
const eventMouseModifiers = ["left", "right", "middle"];
|
|
3518
|
+
const eventKeyModifiers = ["enter", "escape", "esc", "space", "tab", "delete", "backspace", "up", "down", "left", "right"];
|
|
3519
|
+
function eventModifierGuardExpression(event) {
|
|
3520
|
+
const checks = [];
|
|
3521
|
+
const mouseEvent = isMouseEventName(event.name);
|
|
3522
|
+
for (const modifier of event.modifiers) {
|
|
3523
|
+
if (eventSystemModifiers.includes(modifier)) {
|
|
3524
|
+
checks.push(`!$event.${modifier}Key`);
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
const mouseExpression = mouseEvent ? eventMouseButtonExpression(modifier) : undefined;
|
|
3528
|
+
if (mouseExpression) {
|
|
3529
|
+
checks.push(`$event.button !== ${mouseExpression}`);
|
|
3530
|
+
continue;
|
|
3531
|
+
}
|
|
3532
|
+
const keyExpression = eventKeyExpression(modifier);
|
|
3533
|
+
if (keyExpression) {
|
|
3534
|
+
checks.push(`!${keyExpression}.includes($event.key)`);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
if (event.modifiers.includes("exact")) {
|
|
3538
|
+
for (const modifier of eventSystemModifiers) {
|
|
3539
|
+
if (!event.modifiers.includes(modifier)) {
|
|
3540
|
+
checks.push(`$event.${modifier}Key`);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
return checks.length ? checks.join(" || ") : undefined;
|
|
3545
|
+
}
|
|
3546
|
+
function isMouseEventName(name) {
|
|
3547
|
+
return !!name && /^(?:click|dblclick|auxclick|contextmenu|mousedown|mouseup|mousemove|mouseover|mouseout|mouseenter|mouseleave|pointerdown|pointerup|pointermove|pointerover|pointerout|pointerenter|pointerleave)$/.test(name);
|
|
3548
|
+
}
|
|
3549
|
+
function eventMouseButtonExpression(modifier) {
|
|
3550
|
+
const mouseButtons = {
|
|
3551
|
+
left: "0",
|
|
3552
|
+
middle: "1",
|
|
3553
|
+
right: "2"
|
|
3554
|
+
};
|
|
3555
|
+
return mouseButtons[modifier];
|
|
3556
|
+
}
|
|
3557
|
+
function eventKeyExpression(modifier) {
|
|
3558
|
+
const keyAliases = {
|
|
3559
|
+
enter: ["Enter"],
|
|
3560
|
+
escape: ["Escape"],
|
|
3561
|
+
esc: ["Escape"],
|
|
3562
|
+
space: [" ", "Spacebar"],
|
|
3563
|
+
tab: ["Tab"],
|
|
3564
|
+
delete: ["Delete"],
|
|
3565
|
+
backspace: ["Backspace"],
|
|
3566
|
+
up: ["ArrowUp", "Up"],
|
|
3567
|
+
down: ["ArrowDown", "Down"],
|
|
3568
|
+
left: ["ArrowLeft", "Left"],
|
|
3569
|
+
right: ["ArrowRight", "Right"]
|
|
3570
|
+
};
|
|
3571
|
+
const keys = keyAliases[modifier];
|
|
3572
|
+
return keys ? JSON.stringify(keys) : undefined;
|
|
3573
|
+
}
|
|
2371
3574
|
function componentEventHandlerExpression(event, handlerExpression, context) {
|
|
2372
3575
|
if (!event.modifiers.includes("once")) {
|
|
2373
3576
|
return handlerExpression;
|
|
@@ -2376,7 +3579,13 @@ function componentEventHandlerExpression(event, handlerExpression, context) {
|
|
|
2376
3579
|
const handlerVar = nextVar(context, "handler");
|
|
2377
3580
|
return `(() => { let ${calledVar} = false; const ${handlerVar} = ${handlerExpression}; return (...$args) => { if (${calledVar}) { return; } ${calledVar} = true; return ${handlerVar}(...$args); }; })()`;
|
|
2378
3581
|
}
|
|
3582
|
+
function componentEventPropRuntimeExpression(eventNameExpression) {
|
|
3583
|
+
return `"on" + ${eventNameExpression}.split(/[-:]/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("")`;
|
|
3584
|
+
}
|
|
2379
3585
|
function getEventName(name) {
|
|
3586
|
+
if (name.startsWith("@[") || name.startsWith("v-on:[")) {
|
|
3587
|
+
return undefined;
|
|
3588
|
+
}
|
|
2380
3589
|
if (name.startsWith("@")) {
|
|
2381
3590
|
return name.slice(1);
|
|
2382
3591
|
}
|
|
@@ -2386,11 +3595,108 @@ function getEventName(name) {
|
|
|
2386
3595
|
return undefined;
|
|
2387
3596
|
}
|
|
2388
3597
|
function getBindingName(name) {
|
|
2389
|
-
|
|
2390
|
-
|
|
3598
|
+
const binding = parseBindDirective(name);
|
|
3599
|
+
if (!binding || binding.nameExpression) {
|
|
3600
|
+
return undefined;
|
|
3601
|
+
}
|
|
3602
|
+
return binding.name;
|
|
3603
|
+
}
|
|
3604
|
+
function getDynamicBindingArgument(name) {
|
|
3605
|
+
const binding = parseBindDirective(name);
|
|
3606
|
+
if (!binding?.nameExpression) {
|
|
3607
|
+
return undefined;
|
|
3608
|
+
}
|
|
3609
|
+
return { expression: binding.nameExpression };
|
|
3610
|
+
}
|
|
3611
|
+
function parseBindDirective(name) {
|
|
3612
|
+
const dynamic = parseDynamicArgument(name, [":", "v-bind:"]);
|
|
3613
|
+
if (dynamic) {
|
|
3614
|
+
return { nameExpression: dynamic.expression, modifiers: dynamic.modifiers };
|
|
3615
|
+
}
|
|
3616
|
+
const rawName = name.startsWith(":")
|
|
3617
|
+
? name.slice(1)
|
|
3618
|
+
: name.startsWith("v-bind:")
|
|
3619
|
+
? name.slice("v-bind:".length)
|
|
3620
|
+
: undefined;
|
|
3621
|
+
if (!rawName) {
|
|
3622
|
+
return undefined;
|
|
3623
|
+
}
|
|
3624
|
+
const [bindingName, ...modifiers] = rawName.split(".");
|
|
3625
|
+
if (!bindingName) {
|
|
3626
|
+
return undefined;
|
|
3627
|
+
}
|
|
3628
|
+
return { name: modifiers.includes("camel") ? camelize(bindingName) : bindingName, modifiers };
|
|
3629
|
+
}
|
|
3630
|
+
function validateBindModifiers(binding, attr, context, target) {
|
|
3631
|
+
const allowed = new Set(["camel", "prop", "attr"]);
|
|
3632
|
+
for (const modifier of binding.modifiers) {
|
|
3633
|
+
if (!allowed.has(modifier)) {
|
|
3634
|
+
throwTemplateError(`Unsupported v-bind modifier ".${modifier}". Use .camel, .prop, or .attr.`, context, attr.loc);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
if (binding.modifiers.includes("prop") && binding.modifiers.includes("attr")) {
|
|
3638
|
+
throwTemplateError("v-bind modifiers .prop and .attr cannot be used together", context, attr.loc);
|
|
3639
|
+
}
|
|
3640
|
+
if (target === "component" && (binding.modifiers.includes("prop") || binding.modifiers.includes("attr"))) {
|
|
3641
|
+
throwTemplateError("v-bind .prop and .attr modifiers are only supported on native elements", context, attr.loc);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
function validateObjectBindModifiers(binding, attr, context, target) {
|
|
3645
|
+
const allowed = new Set(["camel", "prop", "attr"]);
|
|
3646
|
+
for (const modifier of binding.modifiers) {
|
|
3647
|
+
if (!allowed.has(modifier)) {
|
|
3648
|
+
throwTemplateError(`Unsupported object v-bind modifier ".${modifier}". Use .camel, .prop, or .attr.`, context, attr.loc);
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
if (binding.modifiers.includes("prop") && binding.modifiers.includes("attr")) {
|
|
3652
|
+
throwTemplateError("Object v-bind modifiers .prop and .attr cannot be used together", context, attr.loc);
|
|
3653
|
+
}
|
|
3654
|
+
if (target === "component" && binding.modifiers.length > 0) {
|
|
3655
|
+
throwTemplateError("Object v-bind modifiers are only supported on native elements", context, attr.loc);
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
function bindOptionsExpression(binding) {
|
|
3659
|
+
if (binding.modifiers.includes("prop")) {
|
|
3660
|
+
return ", { property: true }";
|
|
3661
|
+
}
|
|
3662
|
+
if (binding.modifiers.includes("attr")) {
|
|
3663
|
+
return ", { attribute: true }";
|
|
3664
|
+
}
|
|
3665
|
+
return "";
|
|
3666
|
+
}
|
|
3667
|
+
function bindNameExpression(expression, binding) {
|
|
3668
|
+
return binding.modifiers.includes("camel") ? `(${expression}).replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase())` : expression;
|
|
3669
|
+
}
|
|
3670
|
+
function objectBindKeyExpression(keyExpression, binding) {
|
|
3671
|
+
return bindNameExpression(`String(${keyExpression})`, binding);
|
|
3672
|
+
}
|
|
3673
|
+
function camelize(value) {
|
|
3674
|
+
return value.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
3675
|
+
}
|
|
3676
|
+
function getDynamicEventArgument(name) {
|
|
3677
|
+
const dynamic = parseDynamicArgument(name, ["@", "v-on:"]);
|
|
3678
|
+
if (!dynamic) {
|
|
3679
|
+
return undefined;
|
|
2391
3680
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
3681
|
+
return { nameExpression: dynamic.expression, modifiers: dynamic.modifiers };
|
|
3682
|
+
}
|
|
3683
|
+
function parseDynamicArgument(name, prefixes) {
|
|
3684
|
+
for (const prefix of prefixes) {
|
|
3685
|
+
if (!name.startsWith(`${prefix}[`)) {
|
|
3686
|
+
continue;
|
|
3687
|
+
}
|
|
3688
|
+
const argumentStart = prefix.length + 1;
|
|
3689
|
+
const argumentEnd = name.indexOf("]", argumentStart);
|
|
3690
|
+
if (argumentEnd === -1) {
|
|
3691
|
+
return undefined;
|
|
3692
|
+
}
|
|
3693
|
+
const expression = name.slice(argumentStart, argumentEnd).trim();
|
|
3694
|
+
if (!expression) {
|
|
3695
|
+
return undefined;
|
|
3696
|
+
}
|
|
3697
|
+
const rest = name.slice(argumentEnd + 1);
|
|
3698
|
+
const modifiers = rest.startsWith(".") ? rest.slice(1).split(".").filter(Boolean) : [];
|
|
3699
|
+
return { expression, modifiers };
|
|
2394
3700
|
}
|
|
2395
3701
|
return undefined;
|
|
2396
3702
|
}
|