mikuru 1.0.0
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 +14 -0
- package/README.md +142 -0
- package/dist/compiler/analyzeTemplate.d.ts +7 -0
- package/dist/compiler/analyzeTemplate.js +161 -0
- package/dist/compiler/analyzeTemplate.js.map +1 -0
- package/dist/compiler/compile.d.ts +2 -0
- package/dist/compiler/compile.js +24 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/errors.d.ts +17 -0
- package/dist/compiler/errors.js +51 -0
- package/dist/compiler/errors.js.map +1 -0
- package/dist/compiler/generate.d.ts +2 -0
- package/dist/compiler/generate.js +982 -0
- package/dist/compiler/generate.js.map +1 -0
- package/dist/compiler/index.d.ts +9 -0
- package/dist/compiler/index.js +8 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/parseExpression.d.ts +14 -0
- package/dist/compiler/parseExpression.js +273 -0
- package/dist/compiler/parseExpression.js.map +1 -0
- package/dist/compiler/parseSfc.d.ts +2 -0
- package/dist/compiler/parseSfc.js +38 -0
- package/dist/compiler/parseSfc.js.map +1 -0
- package/dist/compiler/parseTemplate.d.ts +8 -0
- package/dist/compiler/parseTemplate.js +266 -0
- package/dist/compiler/parseTemplate.js.map +1 -0
- package/dist/compiler/sourceMap.d.ts +2 -0
- package/dist/compiler/sourceMap.js +66 -0
- package/dist/compiler/sourceMap.js.map +1 -0
- package/dist/compiler/types.d.ts +89 -0
- package/dist/compiler/types.js +2 -0
- package/dist/compiler/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/dom.d.ts +3 -0
- package/dist/runtime/dom.js +46 -0
- package/dist/runtime/dom.js.map +1 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/lifecycle.d.ts +10 -0
- package/dist/runtime/lifecycle.js +79 -0
- package/dist/runtime/lifecycle.js.map +1 -0
- package/dist/runtime/reactivity.d.ts +11 -0
- package/dist/runtime/reactivity.js +84 -0
- package/dist/runtime/reactivity.js.map +1 -0
- package/dist/vite.d.ts +7 -0
- package/dist/vite.js +39 -0
- package/dist/vite.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
import { parse } from "acorn";
|
|
2
|
+
import { createCompileError } from "./errors.js";
|
|
3
|
+
import { compileTemplateExpression, parseForExpression, validateAssignableExpression, validateTemplateExpression } from "./parseExpression.js";
|
|
4
|
+
export function generate(descriptor, root) {
|
|
5
|
+
const context = {
|
|
6
|
+
lines: [],
|
|
7
|
+
index: 0,
|
|
8
|
+
source: descriptor.source,
|
|
9
|
+
filename: descriptor.filename,
|
|
10
|
+
scopeAttr: descriptor.styleScoped ? createScopeAttr(descriptor) : undefined
|
|
11
|
+
};
|
|
12
|
+
const script = normalizeScript(descriptor);
|
|
13
|
+
for (const importLine of script.imports) {
|
|
14
|
+
emit(context, 0, importLine);
|
|
15
|
+
}
|
|
16
|
+
emit(context, 0, `import { computed, effect, ref, setAttribute, unwrap } from "mikuru/runtime";`);
|
|
17
|
+
emit(context, 0, "");
|
|
18
|
+
emit(context, 0, "export function mount(target, props = {}) {");
|
|
19
|
+
emit(context, 1, "const __mikuru_cleanup = [];");
|
|
20
|
+
emit(context, 1, "const __mikuru_afterUnmount = [];");
|
|
21
|
+
emit(context, 1, "const __mikuru_mounted = [];");
|
|
22
|
+
emit(context, 1, "const __mikuru_runCleanup = (cleanups) => {");
|
|
23
|
+
emit(context, 2, "for (const cleanup of cleanups.splice(0).reverse()) {");
|
|
24
|
+
emit(context, 3, "cleanup();");
|
|
25
|
+
emit(context, 2, "}");
|
|
26
|
+
emit(context, 1, "};");
|
|
27
|
+
emit(context, 1, "// expose a lightweight registrar for runtime lifecycle helpers (onMounted, onBeforeUnmount, onUnmounted, watch)");
|
|
28
|
+
emit(context, 1, "globalThis.__mikuru_currentRegistrar = {");
|
|
29
|
+
emit(context, 2, "registerMounted: (fn) => __mikuru_mounted.push(fn),");
|
|
30
|
+
emit(context, 2, "registerBeforeUnmount: (fn) => __mikuru_cleanup.push(fn),");
|
|
31
|
+
emit(context, 2, "registerUnmounted: (fn) => __mikuru_afterUnmount.push(fn),");
|
|
32
|
+
emit(context, 2, "registerEffect: (fn) => Promise.resolve().then(fn)");
|
|
33
|
+
emit(context, 1, "};");
|
|
34
|
+
emit(context, 1, "");
|
|
35
|
+
if (descriptor.style?.trim()) {
|
|
36
|
+
emitStyleInjection(context, descriptor, 1);
|
|
37
|
+
emit(context, 1, "");
|
|
38
|
+
}
|
|
39
|
+
if (script.body.trim()) {
|
|
40
|
+
if (script.usesPropsAlias) {
|
|
41
|
+
emit(context, 1, "const __mikuru_props = props;");
|
|
42
|
+
}
|
|
43
|
+
if (script.usesEmitAlias) {
|
|
44
|
+
emit(context, 1, "const __mikuru_emit = (name, ...args) => {");
|
|
45
|
+
emit(context, 2, "const handlerName = \"on\" + String(name).split(/[-:]/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(\"\");");
|
|
46
|
+
emit(context, 2, "const handler = props[handlerName];");
|
|
47
|
+
emit(context, 2, "if (handler) {");
|
|
48
|
+
emit(context, 3, "handler(...args);");
|
|
49
|
+
emit(context, 2, "}");
|
|
50
|
+
emit(context, 1, "};");
|
|
51
|
+
}
|
|
52
|
+
emitBlock(context, 1, script.body);
|
|
53
|
+
emit(context, 1, "");
|
|
54
|
+
}
|
|
55
|
+
const rootVar = generateNode(context, root, "target", "__mikuru_cleanup", 1);
|
|
56
|
+
emit(context, 1, "// call mounted callbacks registered during setup and remove registrar");
|
|
57
|
+
emit(context, 1, "for (const cb of __mikuru_mounted.splice(0)) { try { cb(); } catch (e) { setTimeout(() => { throw e; }); } }");
|
|
58
|
+
emit(context, 1, "delete globalThis.__mikuru_currentRegistrar;");
|
|
59
|
+
emit(context, 1, "return {");
|
|
60
|
+
emit(context, 2, `element: ${rootVar},`);
|
|
61
|
+
emit(context, 2, "unmount() {");
|
|
62
|
+
emit(context, 3, "__mikuru_runCleanup(__mikuru_cleanup);");
|
|
63
|
+
emit(context, 3, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) { try { cb(); } catch (e) { setTimeout(() => { throw e; }); } }");
|
|
64
|
+
emit(context, 3, `${rootVar}.remove();`);
|
|
65
|
+
emit(context, 2, "}");
|
|
66
|
+
emit(context, 1, "};");
|
|
67
|
+
emit(context, 0, "}");
|
|
68
|
+
emit(context, 0, "");
|
|
69
|
+
emit(context, 0, "const __mikuru_component = { mount };");
|
|
70
|
+
emit(context, 0, "export default __mikuru_component;");
|
|
71
|
+
return `${context.lines.join("\n")}\n`;
|
|
72
|
+
}
|
|
73
|
+
function emitStyleInjection(context, descriptor, indent) {
|
|
74
|
+
const styleId = `mikuru-${hash(`${descriptor.filename ?? ""}\n${descriptor.style ?? ""}`)}`;
|
|
75
|
+
const styleContent = descriptor.styleScoped && context.scopeAttr
|
|
76
|
+
? scopeCssSelectors(descriptor.style?.trim() ?? "", context.scopeAttr)
|
|
77
|
+
: descriptor.style?.trim() ?? "";
|
|
78
|
+
emit(context, indent, `if (!document.querySelector(${quote(`style[data-mikuru-style="${styleId}"]`)})) {`);
|
|
79
|
+
emit(context, indent + 1, "const style = document.createElement(\"style\");");
|
|
80
|
+
emit(context, indent + 1, `style.setAttribute("data-mikuru-style", ${quote(styleId)});`);
|
|
81
|
+
emit(context, indent + 1, `style.textContent = ${quote(styleContent)};`);
|
|
82
|
+
emit(context, indent + 1, "document.head.appendChild(style);");
|
|
83
|
+
emit(context, indent, "}");
|
|
84
|
+
}
|
|
85
|
+
function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
86
|
+
if (node.type === "text") {
|
|
87
|
+
return generateText(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
88
|
+
}
|
|
89
|
+
const forExpression = getStringAttr(node, "v-for");
|
|
90
|
+
if (forExpression) {
|
|
91
|
+
return generateFor(context, node, parentVar, cleanupVar, indent, forExpression, beforeVar);
|
|
92
|
+
}
|
|
93
|
+
const ifExpression = getStringAttr(node, "v-if");
|
|
94
|
+
if (ifExpression) {
|
|
95
|
+
return generateIfChain(context, [{ node, condition: ifExpression, directive: "v-if" }], parentVar, cleanupVar, indent, beforeVar);
|
|
96
|
+
}
|
|
97
|
+
const orphanElseAttr = node.attrs.find((attr) => attr.name === "v-else-if" || attr.name === "v-else");
|
|
98
|
+
if (orphanElseAttr) {
|
|
99
|
+
throwTemplateError(`${orphanElseAttr.name} must follow v-if or v-else-if`, context, orphanElseAttr.loc);
|
|
100
|
+
}
|
|
101
|
+
if (isComponentTag(node.tag)) {
|
|
102
|
+
return generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
103
|
+
}
|
|
104
|
+
if (node.tag === "slot") {
|
|
105
|
+
return generateSlot(context, parentVar, cleanupVar, indent, beforeVar);
|
|
106
|
+
}
|
|
107
|
+
return generateElement(context, node, parentVar, cleanupVar, indent, beforeVar);
|
|
108
|
+
}
|
|
109
|
+
function generateSlot(context, parentVar, cleanupVar, indent, beforeVar) {
|
|
110
|
+
const slotVar = nextVar(context, "slot");
|
|
111
|
+
const slotCleanupVar = nextVar(context, "slotCleanup");
|
|
112
|
+
emit(context, indent, `const ${slotVar} = document.createDocumentFragment();`);
|
|
113
|
+
emit(context, indent, `const ${slotCleanupVar} = props.children ? props.children(${slotVar}) : undefined;`);
|
|
114
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
115
|
+
emit(context, indent + 1, `if (${slotCleanupVar}) {`);
|
|
116
|
+
emit(context, indent + 2, `${slotCleanupVar}();`);
|
|
117
|
+
emit(context, indent + 1, "}");
|
|
118
|
+
emit(context, indent, "});");
|
|
119
|
+
appendNode(context, parentVar, slotVar, indent, beforeVar);
|
|
120
|
+
return slotVar;
|
|
121
|
+
}
|
|
122
|
+
function generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
123
|
+
const fragmentVar = nextVar(context, "fragment");
|
|
124
|
+
const propsVar = nextVar(context, "props");
|
|
125
|
+
const componentVar = nextVar(context, "component");
|
|
126
|
+
emit(context, indent, `const ${fragmentVar} = document.createDocumentFragment();`);
|
|
127
|
+
emitComponentProps(context, node, propsVar, indent);
|
|
128
|
+
emit(context, indent, `const ${componentVar} = ${node.tag}.mount(${fragmentVar}, ${propsVar});`);
|
|
129
|
+
if (context.scopeAttr) {
|
|
130
|
+
emit(context, indent, `if (${componentVar}.element instanceof Element) {`);
|
|
131
|
+
emit(context, indent + 1, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
|
|
132
|
+
emit(context, indent, "}");
|
|
133
|
+
}
|
|
134
|
+
emit(context, indent, `${cleanupVar}.push(() => ${componentVar}.unmount());`);
|
|
135
|
+
appendNode(context, parentVar, fragmentVar, indent, beforeVar);
|
|
136
|
+
return `${componentVar}.element`;
|
|
137
|
+
}
|
|
138
|
+
function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
139
|
+
validateAttributes(node);
|
|
140
|
+
const elementVar = nextVar(context, "el");
|
|
141
|
+
emit(context, indent, `const ${elementVar} = document.createElement(${quote(node.tag)});`);
|
|
142
|
+
if (context.scopeAttr) {
|
|
143
|
+
emit(context, indent, `${elementVar}.setAttribute(${quote(context.scopeAttr)}, "");`);
|
|
144
|
+
}
|
|
145
|
+
for (const attr of node.attrs) {
|
|
146
|
+
if (isDirectiveAttr(attr)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
|
|
150
|
+
}
|
|
151
|
+
for (const attr of node.attrs) {
|
|
152
|
+
if (attr.name === "v-model") {
|
|
153
|
+
const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
154
|
+
const stopVar = nextVar(context, "stop");
|
|
155
|
+
const handlerVar = nextVar(context, "handler");
|
|
156
|
+
const inputType = getStaticAttrValue(node, "type")?.toLowerCase();
|
|
157
|
+
const modelMode = node.tag === "input" && inputType === "checkbox" ? "checkbox" : node.tag === "select" ? "select" : "text";
|
|
158
|
+
const eventName = modelMode === "text" ? "input" : "change";
|
|
159
|
+
const propertyName = modelMode === "checkbox" ? "checked" : "value";
|
|
160
|
+
const renderedValue = modelMode === "checkbox" ? `Boolean(unwrap(${expression}))` : `String(unwrap(${expression}) ?? "")`;
|
|
161
|
+
const assignedValue = modelMode === "checkbox" ? `$event.target.checked` : `$event.target.value`;
|
|
162
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
163
|
+
emit(context, indent + 1, `if (${elementVar}.${propertyName} !== ${renderedValue}) {`);
|
|
164
|
+
emit(context, indent + 2, `${elementVar}.${propertyName} = ${renderedValue};`);
|
|
165
|
+
emit(context, indent + 1, "}");
|
|
166
|
+
emit(context, indent, "});");
|
|
167
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
168
|
+
emit(context, indent, `const ${handlerVar} = ($event) => {`);
|
|
169
|
+
emit(context, indent + 1, `${expression}.value = ${assignedValue};`);
|
|
170
|
+
emit(context, indent, "};");
|
|
171
|
+
emit(context, indent, `${elementVar}.addEventListener(${quote(eventName)}, ${handlerVar});`);
|
|
172
|
+
emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(eventName)}, ${handlerVar}));`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (attr.name === "v-show") {
|
|
176
|
+
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
177
|
+
const stopVar = nextVar(context, "stop");
|
|
178
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
179
|
+
emit(context, indent + 1, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
|
|
180
|
+
emit(context, indent, "});");
|
|
181
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const event = parseEventDirective(attr.name);
|
|
185
|
+
if (event) {
|
|
186
|
+
validateEventModifiers(event, attr, context);
|
|
187
|
+
const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
188
|
+
const handlerVar = nextVar(context, "handler");
|
|
189
|
+
const handlerExpression = eventHandlerExpression(handler, context, attr.valueLoc);
|
|
190
|
+
if (event.modifiers.length) {
|
|
191
|
+
const baseHandlerVar = nextVar(context, "handler");
|
|
192
|
+
emit(context, indent, `const ${baseHandlerVar} = ${handlerExpression};`);
|
|
193
|
+
emit(context, indent, `const ${handlerVar} = ($event) => {`);
|
|
194
|
+
if (event.modifiers.includes("prevent")) {
|
|
195
|
+
emit(context, indent + 1, "$event.preventDefault();");
|
|
196
|
+
}
|
|
197
|
+
if (event.modifiers.includes("stop")) {
|
|
198
|
+
emit(context, indent + 1, "$event.stopPropagation();");
|
|
199
|
+
}
|
|
200
|
+
emit(context, indent + 1, `return ${baseHandlerVar}($event);`);
|
|
201
|
+
emit(context, indent, "};");
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
emit(context, indent, `const ${handlerVar} = ${handlerExpression};`);
|
|
205
|
+
}
|
|
206
|
+
emit(context, indent, `${elementVar}.addEventListener(${quote(event.name)}, ${handlerVar});`);
|
|
207
|
+
emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name)}, ${handlerVar}));`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const bindingName = getBindingName(attr.name);
|
|
211
|
+
if (bindingName) {
|
|
212
|
+
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
213
|
+
const stopVar = nextVar(context, "stop");
|
|
214
|
+
const valueExpression = bindingName === "class" && getStaticAttrValue(node, "class")
|
|
215
|
+
? `[${quote(getStaticAttrValue(node, "class"))}, ${expression}]`
|
|
216
|
+
: expression;
|
|
217
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
218
|
+
emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression}));`);
|
|
219
|
+
emit(context, indent, "});");
|
|
220
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
generateChildren(context, node.children, elementVar, cleanupVar, indent);
|
|
224
|
+
appendNode(context, parentVar, elementVar, indent, beforeVar);
|
|
225
|
+
return elementVar;
|
|
226
|
+
}
|
|
227
|
+
function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
|
|
228
|
+
const textVar = nextVar(context, "text");
|
|
229
|
+
emit(context, indent, `const ${textVar} = document.createTextNode("");`);
|
|
230
|
+
if (node.parts.some((part) => part.type === "expression")) {
|
|
231
|
+
const stopVar = nextVar(context, "stop");
|
|
232
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
233
|
+
emit(context, indent + 1, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
|
|
234
|
+
emit(context, indent, "});");
|
|
235
|
+
emit(context, indent, `${cleanupVar}.push(${stopVar});`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
emit(context, indent, `${textVar}.textContent = ${quote(node.parts.map((part) => part.value).join(""))};`);
|
|
239
|
+
}
|
|
240
|
+
appendNode(context, parentVar, textVar, indent, beforeVar);
|
|
241
|
+
return textVar;
|
|
242
|
+
}
|
|
243
|
+
function generateChildren(context, children, parentVar, cleanupVar, indent, beforeVar) {
|
|
244
|
+
let index = 0;
|
|
245
|
+
while (index < children.length) {
|
|
246
|
+
const child = children[index];
|
|
247
|
+
if (child.type === "element" && getStringAttr(child, "v-if")) {
|
|
248
|
+
const branches = [
|
|
249
|
+
{
|
|
250
|
+
node: child,
|
|
251
|
+
condition: getStringAttr(child, "v-if"),
|
|
252
|
+
directive: "v-if"
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
let cursor = index + 1;
|
|
256
|
+
while (cursor < children.length) {
|
|
257
|
+
let candidateIndex = cursor;
|
|
258
|
+
while (candidateIndex < children.length && isWhitespaceText(children[candidateIndex])) {
|
|
259
|
+
candidateIndex += 1;
|
|
260
|
+
}
|
|
261
|
+
const candidate = children[candidateIndex];
|
|
262
|
+
if (candidate?.type !== "element") {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
const elseIfExpression = getStringAttr(candidate, "v-else-if");
|
|
266
|
+
if (elseIfExpression) {
|
|
267
|
+
branches.push({
|
|
268
|
+
node: candidate,
|
|
269
|
+
condition: elseIfExpression,
|
|
270
|
+
directive: "v-else-if"
|
|
271
|
+
});
|
|
272
|
+
cursor = candidateIndex + 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (hasAttr(candidate, "v-else")) {
|
|
276
|
+
validateElseAttribute(candidate, context);
|
|
277
|
+
branches.push({
|
|
278
|
+
node: candidate,
|
|
279
|
+
directive: "v-else"
|
|
280
|
+
});
|
|
281
|
+
cursor = candidateIndex + 1;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
generateIfChain(context, branches, parentVar, cleanupVar, indent, beforeVar);
|
|
286
|
+
index = cursor;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (child.type === "element" && (hasAttr(child, "v-else-if") || hasAttr(child, "v-else"))) {
|
|
290
|
+
const attr = child.attrs.find((candidate) => candidate.name === "v-else-if" || candidate.name === "v-else");
|
|
291
|
+
throwTemplateError(`${attr?.name ?? "v-else"} must follow v-if or v-else-if`, context, attr?.loc);
|
|
292
|
+
}
|
|
293
|
+
generateNode(context, child, parentVar, cleanupVar, indent, beforeVar);
|
|
294
|
+
index += 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function generateIfChain(context, branches, parentVar, cleanupVar, indent, beforeVar) {
|
|
298
|
+
const startVar = nextVar(context, "ifStart");
|
|
299
|
+
const endVar = nextVar(context, "ifEnd");
|
|
300
|
+
const branchCleanupVar = nextVar(context, "ifCleanup");
|
|
301
|
+
const stopVar = nextVar(context, "stop");
|
|
302
|
+
emit(context, indent, `const ${branchCleanupVar} = [];`);
|
|
303
|
+
emit(context, indent, `const ${startVar} = document.createComment("if");`);
|
|
304
|
+
emit(context, indent, `const ${endVar} = document.createComment("/if");`);
|
|
305
|
+
appendNode(context, parentVar, startVar, indent, beforeVar);
|
|
306
|
+
appendNode(context, parentVar, endVar, indent, beforeVar);
|
|
307
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
308
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${branchCleanupVar});`);
|
|
309
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
310
|
+
branches.forEach((branch, branchIndex) => {
|
|
311
|
+
if (branch.directive === "v-else") {
|
|
312
|
+
emit(context, indent + 1, `${branchIndex === 0 ? "if (true)" : "else"} {`);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const condition = compileTemplateExpression(branch.condition ?? "", branch.directive, toExpressionContext(context, getStringAttrLocation(branch.node, branch.directive)));
|
|
316
|
+
emit(context, indent + 1, `${branchIndex === 0 ? "if" : "else if"} (unwrap(${condition})) {`);
|
|
317
|
+
}
|
|
318
|
+
generateNode(context, withoutAttrs(branch.node, ["v-if", "v-else-if", "v-else"]), parentVar, branchCleanupVar, indent + 2, endVar);
|
|
319
|
+
emit(context, indent + 1, "}");
|
|
320
|
+
});
|
|
321
|
+
emit(context, indent, "});");
|
|
322
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
323
|
+
emit(context, indent + 1, `${stopVar}();`);
|
|
324
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${branchCleanupVar});`);
|
|
325
|
+
emit(context, indent, "});");
|
|
326
|
+
return startVar;
|
|
327
|
+
}
|
|
328
|
+
function generateFor(context, node, parentVar, cleanupVar, indent, expression, beforeVar) {
|
|
329
|
+
const { item: itemName, index: indexName, source: sourceExpression } = parseForExpression(expression, toExpressionContext(context, getStringAttrLocation(node, "v-for")));
|
|
330
|
+
const keyExpression = getKeyExpression(node);
|
|
331
|
+
if (keyExpression) {
|
|
332
|
+
return generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, beforeVar);
|
|
333
|
+
}
|
|
334
|
+
const startVar = nextVar(context, "forStart");
|
|
335
|
+
const endVar = nextVar(context, "forEnd");
|
|
336
|
+
const branchCleanupVar = nextVar(context, "forCleanup");
|
|
337
|
+
const stopVar = nextVar(context, "stop");
|
|
338
|
+
emit(context, indent, `const ${branchCleanupVar} = [];`);
|
|
339
|
+
emit(context, indent, `const ${startVar} = document.createComment("for");`);
|
|
340
|
+
emit(context, indent, `const ${endVar} = document.createComment("/for");`);
|
|
341
|
+
appendNode(context, parentVar, startVar, indent, beforeVar);
|
|
342
|
+
appendNode(context, parentVar, endVar, indent, beforeVar);
|
|
343
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
344
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${branchCleanupVar});`);
|
|
345
|
+
emitRemoveBetween(context, indent + 1, startVar, endVar);
|
|
346
|
+
if (indexName) {
|
|
347
|
+
const sourceVar = nextVar(context, "forSource");
|
|
348
|
+
const indexVar = nextVar(context, "forIndex");
|
|
349
|
+
emit(context, indent + 1, `const ${sourceVar} = unwrap(${compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")))}) ?? [];`);
|
|
350
|
+
emit(context, indent + 1, `for (let ${indexVar} = 0; ${indexVar} < ${sourceVar}.length; ${indexVar} += 1) {`);
|
|
351
|
+
emit(context, indent + 2, `const ${itemName} = ${sourceVar}[${indexVar}];`);
|
|
352
|
+
emit(context, indent + 2, `const ${indexName} = ${indexVar};`);
|
|
353
|
+
generateNode(context, withoutForAttrs(node), parentVar, branchCleanupVar, indent + 2, endVar);
|
|
354
|
+
emit(context, indent + 1, "}");
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
emit(context, indent + 1, `for (const ${itemName} of unwrap(${compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")))}) ?? []) {`);
|
|
358
|
+
generateNode(context, withoutForAttrs(node), parentVar, branchCleanupVar, indent + 2, endVar);
|
|
359
|
+
emit(context, indent + 1, "}");
|
|
360
|
+
}
|
|
361
|
+
emit(context, indent, "});");
|
|
362
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
363
|
+
emit(context, indent + 1, `${stopVar}();`);
|
|
364
|
+
emit(context, indent + 1, `__mikuru_runCleanup(${branchCleanupVar});`);
|
|
365
|
+
emit(context, indent, "});");
|
|
366
|
+
return startVar;
|
|
367
|
+
}
|
|
368
|
+
function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, beforeVar) {
|
|
369
|
+
const startVar = nextVar(context, "forStart");
|
|
370
|
+
const endVar = nextVar(context, "forEnd");
|
|
371
|
+
const recordsVar = nextVar(context, "forRecords");
|
|
372
|
+
const stopVar = nextVar(context, "stop");
|
|
373
|
+
const compiledSource = compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")));
|
|
374
|
+
const compiledKey = compileTemplateExpression(keyExpression, "v-for key", toExpressionContext(context, getKeyAttrLocation(node)));
|
|
375
|
+
emit(context, indent, `const ${recordsVar} = new Map();`);
|
|
376
|
+
emit(context, indent, `const ${startVar} = document.createComment("for");`);
|
|
377
|
+
emit(context, indent, `const ${endVar} = document.createComment("/for");`);
|
|
378
|
+
appendNode(context, parentVar, startVar, indent, beforeVar);
|
|
379
|
+
appendNode(context, parentVar, endVar, indent, beforeVar);
|
|
380
|
+
emit(context, indent, `const ${stopVar} = effect(() => {`);
|
|
381
|
+
const sourceVar = nextVar(context, "forSource");
|
|
382
|
+
const nextRecordsVar = nextVar(context, "nextRecords");
|
|
383
|
+
const indexVar = nextVar(context, "forIndex");
|
|
384
|
+
const rawItemVar = nextVar(context, "forItem");
|
|
385
|
+
const rawIndexVar = nextVar(context, "forIndexValue");
|
|
386
|
+
const keyVar = nextVar(context, "forKey");
|
|
387
|
+
const recordVar = nextVar(context, "forRecord");
|
|
388
|
+
const recordCleanupVar = nextVar(context, "forRecordCleanup");
|
|
389
|
+
const itemRefVar = nextVar(context, "forItemRef");
|
|
390
|
+
const indexRefVar = nextVar(context, "forIndexRef");
|
|
391
|
+
emit(context, indent + 1, `const ${sourceVar} = unwrap(${compiledSource}) ?? [];`);
|
|
392
|
+
emit(context, indent + 1, `const ${nextRecordsVar} = new Map();`);
|
|
393
|
+
emit(context, indent + 1, `for (let ${indexVar} = 0; ${indexVar} < ${sourceVar}.length; ${indexVar} += 1) {`);
|
|
394
|
+
emit(context, indent + 2, `const ${rawItemVar} = ${sourceVar}[${indexVar}];`);
|
|
395
|
+
emit(context, indent + 2, `const ${rawIndexVar} = ${indexVar};`);
|
|
396
|
+
emit(context, indent + 2, `const ${itemName} = ${rawItemVar};`);
|
|
397
|
+
if (indexName) {
|
|
398
|
+
emit(context, indent + 2, `const ${indexName} = ${rawIndexVar};`);
|
|
399
|
+
}
|
|
400
|
+
emit(context, indent + 2, `const ${keyVar} = unwrap(${compiledKey});`);
|
|
401
|
+
emit(context, indent + 2, `let ${recordVar} = ${recordsVar}.get(${keyVar});`);
|
|
402
|
+
emit(context, indent + 2, `if (!${recordVar}) {`);
|
|
403
|
+
emit(context, indent + 3, `const ${recordCleanupVar} = [];`);
|
|
404
|
+
emit(context, indent + 3, `const ${itemRefVar} = ref(${rawItemVar});`);
|
|
405
|
+
if (indexName) {
|
|
406
|
+
emit(context, indent + 3, `const ${indexRefVar} = ref(${rawIndexVar});`);
|
|
407
|
+
}
|
|
408
|
+
emit(context, indent + 3, `{`);
|
|
409
|
+
emit(context, indent + 4, `const ${itemName} = ${itemRefVar};`);
|
|
410
|
+
if (indexName) {
|
|
411
|
+
emit(context, indent + 4, `const ${indexName} = ${indexRefVar};`);
|
|
412
|
+
}
|
|
413
|
+
const elementVar = generateNode(context, withoutForAttrs(node), parentVar, recordCleanupVar, indent + 4, endVar);
|
|
414
|
+
emit(context, indent + 4, `${recordVar} = { element: ${elementVar}, cleanups: ${recordCleanupVar}, item: ${itemRefVar}${indexName ? `, index: ${indexRefVar}` : ""} };`);
|
|
415
|
+
emit(context, indent + 3, `}`);
|
|
416
|
+
emit(context, indent + 2, `} else {`);
|
|
417
|
+
emit(context, indent + 3, `${recordVar}.item.value = ${rawItemVar};`);
|
|
418
|
+
if (indexName) {
|
|
419
|
+
emit(context, indent + 3, `${recordVar}.index.value = ${rawIndexVar};`);
|
|
420
|
+
}
|
|
421
|
+
emit(context, indent + 3, `${parentVar}.insertBefore(${recordVar}.element, ${endVar});`);
|
|
422
|
+
emit(context, indent + 2, `}`);
|
|
423
|
+
emit(context, indent + 2, `${nextRecordsVar}.set(${keyVar}, ${recordVar});`);
|
|
424
|
+
emit(context, indent + 1, `}`);
|
|
425
|
+
emit(context, indent + 1, `for (const [${keyVar}, ${recordVar}] of ${recordsVar}) {`);
|
|
426
|
+
emit(context, indent + 2, `if (!${nextRecordsVar}.has(${keyVar})) {`);
|
|
427
|
+
emit(context, indent + 3, `__mikuru_runCleanup(${recordVar}.cleanups);`);
|
|
428
|
+
emit(context, indent + 3, `${recordVar}.element.remove();`);
|
|
429
|
+
emit(context, indent + 2, `}`);
|
|
430
|
+
emit(context, indent + 1, `}`);
|
|
431
|
+
emit(context, indent + 1, `${recordsVar}.clear();`);
|
|
432
|
+
emit(context, indent + 1, `for (const [${keyVar}, ${recordVar}] of ${nextRecordsVar}) {`);
|
|
433
|
+
emit(context, indent + 2, `${recordsVar}.set(${keyVar}, ${recordVar});`);
|
|
434
|
+
emit(context, indent + 1, `}`);
|
|
435
|
+
emit(context, indent, "});");
|
|
436
|
+
emit(context, indent, `${cleanupVar}.push(() => {`);
|
|
437
|
+
emit(context, indent + 1, `${stopVar}();`);
|
|
438
|
+
emit(context, indent + 1, `for (const ${recordVar} of ${recordsVar}.values()) {`);
|
|
439
|
+
emit(context, indent + 2, `__mikuru_runCleanup(${recordVar}.cleanups);`);
|
|
440
|
+
emit(context, indent + 1, `}`);
|
|
441
|
+
emit(context, indent + 1, `${recordsVar}.clear();`);
|
|
442
|
+
emit(context, indent, "});");
|
|
443
|
+
return startVar;
|
|
444
|
+
}
|
|
445
|
+
function emitRemoveBetween(context, indent, startVar, endVar) {
|
|
446
|
+
const currentVar = nextVar(context, "current");
|
|
447
|
+
const nextVarName = nextVar(context, "next");
|
|
448
|
+
emit(context, indent, `let ${currentVar} = ${startVar}.nextSibling;`);
|
|
449
|
+
emit(context, indent, `while (${currentVar} && ${currentVar} !== ${endVar}) {`);
|
|
450
|
+
emit(context, indent + 1, `const ${nextVarName} = ${currentVar}.nextSibling;`);
|
|
451
|
+
emit(context, indent + 1, `${currentVar}.remove();`);
|
|
452
|
+
emit(context, indent + 1, `${currentVar} = ${nextVarName};`);
|
|
453
|
+
emit(context, indent, "}");
|
|
454
|
+
}
|
|
455
|
+
function appendNode(context, parentVar, nodeVar, indent, beforeVar) {
|
|
456
|
+
if (beforeVar) {
|
|
457
|
+
emit(context, indent, `${parentVar}.insertBefore(${nodeVar}, ${beforeVar});`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
emit(context, indent, `${parentVar}.appendChild(${nodeVar});`);
|
|
461
|
+
}
|
|
462
|
+
function textExpression(parts, context) {
|
|
463
|
+
return parts
|
|
464
|
+
.map((part) => {
|
|
465
|
+
if (part.type === "static") {
|
|
466
|
+
return quote(part.value);
|
|
467
|
+
}
|
|
468
|
+
return `String(unwrap(${compileTemplateExpression(part.value, "interpolation", toExpressionContext(context, part.loc))}) ?? "")`;
|
|
469
|
+
})
|
|
470
|
+
.join(" + ");
|
|
471
|
+
}
|
|
472
|
+
function normalizeScript(descriptor) {
|
|
473
|
+
const script = descriptor.script ?? "";
|
|
474
|
+
const source = descriptor.source ?? script;
|
|
475
|
+
const scriptOffset = descriptor.scriptOffset ?? 0;
|
|
476
|
+
const imports = [];
|
|
477
|
+
const edits = [];
|
|
478
|
+
const transformedMacroStarts = new Set();
|
|
479
|
+
const emitsDeclarations = [];
|
|
480
|
+
let usesPropsAlias = false;
|
|
481
|
+
let usesEmitAlias = false;
|
|
482
|
+
let ast;
|
|
483
|
+
try {
|
|
484
|
+
ast = parse(script, { ecmaVersion: "latest", sourceType: "module" });
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
const offset = typeof error.pos === "number" ? error.pos : 0;
|
|
488
|
+
throw createCompileError("Invalid <script> syntax", source, scriptOffset + offset, descriptor.filename);
|
|
489
|
+
}
|
|
490
|
+
for (const statement of ast.body ?? []) {
|
|
491
|
+
if (statement.type === "ImportDeclaration") {
|
|
492
|
+
const importSource = statement.source?.value;
|
|
493
|
+
edits.push({ start: statement.start, end: statement.end, replacement: "" });
|
|
494
|
+
if (importSource === "mikuru" || importSource === "mikuru/runtime") {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
imports.push(script.slice(statement.start, statement.end).trim());
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (statement.type !== "VariableDeclaration") {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const macroDeclarations = (statement.declarations ?? []).filter((declaration) => isMacroCall(declaration.init, "defineProps") || isMacroCall(declaration.init, "defineEmits"));
|
|
504
|
+
if (macroDeclarations.length > 0 && (statement.declarations ?? []).length !== 1) {
|
|
505
|
+
throwUnsupportedMacro("defineProps() and defineEmits() cannot share a variable declaration with other bindings", macroDeclarations[0], descriptor);
|
|
506
|
+
}
|
|
507
|
+
if (macroDeclarations.length > 0 && statement.kind !== "const") {
|
|
508
|
+
throwUnsupportedMacro("defineProps() and defineEmits() must use const declarations", macroDeclarations[0], descriptor);
|
|
509
|
+
}
|
|
510
|
+
for (const declaration of statement.declarations ?? []) {
|
|
511
|
+
if (!isMacroCall(declaration.init, "defineProps") && !isMacroCall(declaration.init, "defineEmits")) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (isMacroCall(declaration.init, "defineProps")) {
|
|
515
|
+
const replacement = transformDefinePropsDeclaration(declaration, script, descriptor);
|
|
516
|
+
edits.push({ start: statement.start, end: statement.end, replacement });
|
|
517
|
+
transformedMacroStarts.add(declaration.init?.start ?? declaration.start);
|
|
518
|
+
usesPropsAlias = usesPropsAlias || replacement.includes("__mikuru_props");
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const emitDeclaration = transformDefineEmitsDeclaration(declaration, descriptor);
|
|
522
|
+
edits.push({ start: statement.start, end: statement.end, replacement: emitDeclaration.replacement });
|
|
523
|
+
transformedMacroStarts.add(declaration.init?.start ?? declaration.start);
|
|
524
|
+
emitsDeclarations.push({
|
|
525
|
+
localName: emitDeclaration.localName,
|
|
526
|
+
events: emitDeclaration.events
|
|
527
|
+
});
|
|
528
|
+
usesEmitAlias = true;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const body = applyScriptEdits(script, edits).replace(/\bexport\s+(?=(const|let|var|function|class)\b)/g, "").trim();
|
|
532
|
+
assertNoUnsupportedMacroCalls(ast, transformedMacroStarts, descriptor);
|
|
533
|
+
validateDeclaredEmitCalls(ast, emitsDeclarations, descriptor);
|
|
534
|
+
return {
|
|
535
|
+
imports,
|
|
536
|
+
body,
|
|
537
|
+
usesPropsAlias,
|
|
538
|
+
usesEmitAlias
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function transformDefinePropsDeclaration(declaration, script, descriptor) {
|
|
542
|
+
const macroCall = declaration.init;
|
|
543
|
+
validateDefinePropsDeclaration(macroCall, descriptor);
|
|
544
|
+
if (declaration.id?.type === "Identifier") {
|
|
545
|
+
const localName = declaration.id.name ?? "";
|
|
546
|
+
if (!isIdentifier(localName)) {
|
|
547
|
+
throwUnsupportedMacro("Unsupported defineProps binding", declaration.id, descriptor);
|
|
548
|
+
}
|
|
549
|
+
return `const ${localName} = ${localName === "props" ? "__mikuru_props" : "props"};`;
|
|
550
|
+
}
|
|
551
|
+
if (declaration.id?.type !== "ObjectPattern") {
|
|
552
|
+
throwUnsupportedMacro("defineProps() only supports identifier and object destructuring bindings", declaration.id ?? declaration, descriptor);
|
|
553
|
+
}
|
|
554
|
+
return (declaration.id.properties ?? [])
|
|
555
|
+
.map((property) => transformDefinePropsProperty(property, script, descriptor))
|
|
556
|
+
.join("\n");
|
|
557
|
+
}
|
|
558
|
+
function transformDefinePropsProperty(property, script, descriptor) {
|
|
559
|
+
if (property.type === "RestElement") {
|
|
560
|
+
throwUnsupportedMacro("defineProps() does not support rest props yet", property, descriptor);
|
|
561
|
+
}
|
|
562
|
+
if (property.type !== "Property" || property.computed || property.key?.type !== "Identifier") {
|
|
563
|
+
throwUnsupportedMacro("Unsupported defineProps destructuring entry", property, descriptor);
|
|
564
|
+
}
|
|
565
|
+
const propName = property.key.name ?? "";
|
|
566
|
+
const propValueNode = property.value;
|
|
567
|
+
let localName = "";
|
|
568
|
+
let defaultValue;
|
|
569
|
+
if (propValueNode?.type === "Identifier") {
|
|
570
|
+
localName = propValueNode.name ?? "";
|
|
571
|
+
}
|
|
572
|
+
else if (propValueNode?.type === "AssignmentPattern" && propValueNode.left?.type === "Identifier") {
|
|
573
|
+
localName = propValueNode.left.name ?? "";
|
|
574
|
+
defaultValue = script.slice(propValueNode.right?.start ?? propValueNode.end, propValueNode.right?.end ?? propValueNode.end);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
throwUnsupportedMacro("defineProps() does not support nested destructuring yet", propValueNode ?? property, descriptor);
|
|
578
|
+
}
|
|
579
|
+
if (!isIdentifier(propName) || !isIdentifier(localName)) {
|
|
580
|
+
throwUnsupportedMacro("Unsupported defineProps destructuring entry", property, descriptor);
|
|
581
|
+
}
|
|
582
|
+
const propValue = `props.${propName}`;
|
|
583
|
+
if (defaultValue) {
|
|
584
|
+
return `const ${localName} = { get value() { const value = ${propValue}; return value === undefined ? (${defaultValue}) : value; } };`;
|
|
585
|
+
}
|
|
586
|
+
return `const ${localName} = { get value() { return ${propValue}; } };`;
|
|
587
|
+
}
|
|
588
|
+
function transformDefineEmitsDeclaration(declaration, descriptor) {
|
|
589
|
+
if (declaration.id?.type !== "Identifier" || !isIdentifier(declaration.id.name ?? "")) {
|
|
590
|
+
throwUnsupportedMacro("defineEmits() only supports identifier bindings", declaration.id ?? declaration, descriptor);
|
|
591
|
+
}
|
|
592
|
+
const localName = declaration.id.name ?? "";
|
|
593
|
+
return {
|
|
594
|
+
replacement: `const ${localName} = __mikuru_emit;`,
|
|
595
|
+
localName,
|
|
596
|
+
events: parseDefineEmitsDeclaration(declaration.init, descriptor)
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function applyScriptEdits(script, edits) {
|
|
600
|
+
const ordered = [...edits].sort((left, right) => left.start - right.start);
|
|
601
|
+
let output = "";
|
|
602
|
+
let cursor = 0;
|
|
603
|
+
for (const edit of ordered) {
|
|
604
|
+
if (edit.start < cursor) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
output += script.slice(cursor, edit.start);
|
|
608
|
+
output += edit.replacement;
|
|
609
|
+
cursor = edit.end;
|
|
610
|
+
}
|
|
611
|
+
output += script.slice(cursor);
|
|
612
|
+
return output;
|
|
613
|
+
}
|
|
614
|
+
function isMacroCall(node, name) {
|
|
615
|
+
return node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === name;
|
|
616
|
+
}
|
|
617
|
+
function validateDefinePropsDeclaration(node, descriptor) {
|
|
618
|
+
const args = node?.arguments ?? [];
|
|
619
|
+
if (args.length === 0) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (args.length !== 1 || args[0]?.type !== "ObjectExpression") {
|
|
623
|
+
throwUnsupportedMacro("defineProps() only supports an object declaration argument", args[0] ?? node ?? undefined, descriptor);
|
|
624
|
+
}
|
|
625
|
+
for (const property of args[0].properties ?? []) {
|
|
626
|
+
if (property.type !== "Property" || property.computed) {
|
|
627
|
+
throwUnsupportedMacro("Unsupported defineProps() declaration entry", property, descriptor);
|
|
628
|
+
}
|
|
629
|
+
const propName = getPropertyKeyName(property.key);
|
|
630
|
+
if (!propName) {
|
|
631
|
+
throwUnsupportedMacro("Unsupported defineProps() declaration key", property.key ?? property, descriptor);
|
|
632
|
+
}
|
|
633
|
+
const valueNode = property.value;
|
|
634
|
+
if (!isSupportedPropConstructor(valueNode)) {
|
|
635
|
+
throwUnsupportedMacro("defineProps() declaration values must use String, Number, Boolean, Array, or Object", valueNode, descriptor);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function parseDefineEmitsDeclaration(node, descriptor) {
|
|
640
|
+
const args = node?.arguments ?? [];
|
|
641
|
+
if (args.length === 0) {
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
if (args.length !== 1 || args[0]?.type !== "ArrayExpression") {
|
|
645
|
+
throwUnsupportedMacro("defineEmits() only supports an array declaration argument", args[0] ?? node ?? undefined, descriptor);
|
|
646
|
+
}
|
|
647
|
+
const events = new Set();
|
|
648
|
+
for (const element of args[0].elements ?? []) {
|
|
649
|
+
if (element?.type !== "Literal" || typeof element.value !== "string") {
|
|
650
|
+
throwUnsupportedMacro("defineEmits() declarations must be string literals", element ?? args[0], descriptor);
|
|
651
|
+
}
|
|
652
|
+
events.add(element.value);
|
|
653
|
+
}
|
|
654
|
+
return events;
|
|
655
|
+
}
|
|
656
|
+
function validateDeclaredEmitCalls(node, emitsDeclarations, descriptor) {
|
|
657
|
+
if (!emitsDeclarations.length) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
walkScriptNode(node, (candidate) => {
|
|
661
|
+
if (candidate.type !== "CallExpression" || candidate.callee?.type !== "Identifier") {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const declaration = emitsDeclarations.find((emitDeclaration) => emitDeclaration.localName === candidate.callee?.name);
|
|
665
|
+
if (!declaration?.events) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const eventArg = candidate.arguments?.[0];
|
|
669
|
+
if (eventArg?.type !== "Literal" || typeof eventArg.value !== "string") {
|
|
670
|
+
throwUnsupportedMacro("Declared emit calls must use a string literal event name", eventArg ?? candidate, descriptor);
|
|
671
|
+
}
|
|
672
|
+
if (!declaration.events.has(eventArg.value)) {
|
|
673
|
+
throwUnsupportedMacro(`Emit event "${eventArg.value}" is not declared`, eventArg, descriptor);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
function getPropertyKeyName(node) {
|
|
678
|
+
if (node?.type === "Identifier") {
|
|
679
|
+
return node.name;
|
|
680
|
+
}
|
|
681
|
+
if (node?.type === "Literal" && typeof node.value === "string") {
|
|
682
|
+
return node.value;
|
|
683
|
+
}
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
function isSupportedPropConstructor(node) {
|
|
687
|
+
if (node?.type !== "Identifier") {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
return ["String", "Number", "Boolean", "Array", "Object"].includes(node.name ?? "");
|
|
691
|
+
}
|
|
692
|
+
function assertNoUnsupportedMacroCalls(node, transformedMacroStarts, descriptor) {
|
|
693
|
+
walkScriptNode(node, (candidate) => {
|
|
694
|
+
if (candidate.type === "CallExpression" &&
|
|
695
|
+
candidate.callee?.type === "Identifier" &&
|
|
696
|
+
(candidate.callee.name === "defineProps" || candidate.callee.name === "defineEmits") &&
|
|
697
|
+
!transformedMacroStarts.has(candidate.start)) {
|
|
698
|
+
throwUnsupportedMacro("defineProps() and defineEmits() must be used in top-level const declarations", candidate, descriptor);
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
function walkScriptNode(node, visit) {
|
|
703
|
+
if (!node) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (Array.isArray(node)) {
|
|
707
|
+
for (const child of node) {
|
|
708
|
+
walkScriptNode(child, visit);
|
|
709
|
+
}
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
visit(node);
|
|
713
|
+
for (const value of Object.values(node)) {
|
|
714
|
+
if (!value || typeof value !== "object") {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (Array.isArray(value)) {
|
|
718
|
+
for (const item of value) {
|
|
719
|
+
if (item && typeof item === "object" && "type" in item) {
|
|
720
|
+
walkScriptNode(item, visit);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if ("type" in value) {
|
|
726
|
+
walkScriptNode(value, visit);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function throwUnsupportedMacro(message, node, descriptor) {
|
|
731
|
+
throw createCompileError(message, descriptor.source ?? descriptor.script ?? "", (descriptor.scriptOffset ?? 0) + (node?.start ?? 0), descriptor.filename);
|
|
732
|
+
}
|
|
733
|
+
function isIdentifier(value) {
|
|
734
|
+
return /^[A-Za-z_$][\w$]*$/.test(value);
|
|
735
|
+
}
|
|
736
|
+
function eventHandlerExpression(expression, context, location) {
|
|
737
|
+
const validatedExpression = validateTemplateExpression(expression, "event handler", toExpressionContext(context, location));
|
|
738
|
+
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(validatedExpression)) {
|
|
739
|
+
return validatedExpression;
|
|
740
|
+
}
|
|
741
|
+
return `($event) => (${compileTemplateExpression(validatedExpression, "event handler", toExpressionContext(context, location))})`;
|
|
742
|
+
}
|
|
743
|
+
function getStringAttr(node, name) {
|
|
744
|
+
const attr = node.attrs.find((candidate) => candidate.name === name);
|
|
745
|
+
if (!attr) {
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
return requireAttrValue(attr);
|
|
749
|
+
}
|
|
750
|
+
function getStringAttrLocation(node, name) {
|
|
751
|
+
return node.attrs.find((candidate) => candidate.name === name)?.valueLoc;
|
|
752
|
+
}
|
|
753
|
+
function getStaticAttrValue(node, name) {
|
|
754
|
+
const attr = node.attrs.find((candidate) => candidate.name === name);
|
|
755
|
+
if (!attr || attr.value === true) {
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
return attr.value;
|
|
759
|
+
}
|
|
760
|
+
function getKeyExpression(node) {
|
|
761
|
+
return getStringAttr(node, ":key") ?? getStringAttr(node, "v-bind:key");
|
|
762
|
+
}
|
|
763
|
+
function getKeyAttrLocation(node) {
|
|
764
|
+
return getStringAttrLocation(node, ":key") ?? getStringAttrLocation(node, "v-bind:key");
|
|
765
|
+
}
|
|
766
|
+
function toExpressionContext(context, location) {
|
|
767
|
+
if (!context.source || !location) {
|
|
768
|
+
return undefined;
|
|
769
|
+
}
|
|
770
|
+
return {
|
|
771
|
+
source: context.source,
|
|
772
|
+
offset: location.offset,
|
|
773
|
+
filename: context.filename
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function withoutAttr(node, name) {
|
|
777
|
+
return withoutAttrs(node, [name]);
|
|
778
|
+
}
|
|
779
|
+
function withoutForAttrs(node) {
|
|
780
|
+
return withoutAttrs(node, ["v-for", "key", ":key", "v-bind:key"]);
|
|
781
|
+
}
|
|
782
|
+
function withoutAttrs(node, names) {
|
|
783
|
+
return {
|
|
784
|
+
...node,
|
|
785
|
+
attrs: node.attrs.filter((attr) => !names.includes(attr.name))
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function hasAttr(node, name) {
|
|
789
|
+
return node.attrs.some((attr) => attr.name === name);
|
|
790
|
+
}
|
|
791
|
+
function isWhitespaceText(node) {
|
|
792
|
+
return node?.type === "text" && node.parts.every((part) => part.type === "static" && !part.value.trim());
|
|
793
|
+
}
|
|
794
|
+
function validateElseAttribute(node, context) {
|
|
795
|
+
const attr = node.attrs.find((candidate) => candidate.name === "v-else");
|
|
796
|
+
if (attr && attr.value !== true) {
|
|
797
|
+
throwTemplateError("v-else does not accept a value", context, attr.valueLoc ?? attr.loc);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function throwTemplateError(message, context, location) {
|
|
801
|
+
if (context.source && location) {
|
|
802
|
+
throw createCompileError(message, context.source, location.offset, context.filename);
|
|
803
|
+
}
|
|
804
|
+
throw new Error(message);
|
|
805
|
+
}
|
|
806
|
+
function emitComponentProps(context, node, propsVar, indent) {
|
|
807
|
+
const props = node.attrs
|
|
808
|
+
.filter((attr) => !isStructuralAttr(attr))
|
|
809
|
+
.flatMap((attr) => componentPropEntries(context, attr));
|
|
810
|
+
const hasChildren = hasMeaningfulChildren(node);
|
|
811
|
+
emit(context, indent, `const ${propsVar} = {`);
|
|
812
|
+
for (const prop of props) {
|
|
813
|
+
emit(context, indent + 1, `${prop},`);
|
|
814
|
+
}
|
|
815
|
+
if (hasChildren) {
|
|
816
|
+
const slotTargetVar = nextVar(context, "slotTarget");
|
|
817
|
+
const slotCleanupVar = nextVar(context, "slotCleanup");
|
|
818
|
+
emit(context, indent + 1, `children(${slotTargetVar}) {`);
|
|
819
|
+
emit(context, indent + 2, `const ${slotCleanupVar} = [];`);
|
|
820
|
+
generateChildren(context, node.children, slotTargetVar, slotCleanupVar, indent + 2);
|
|
821
|
+
emit(context, indent + 2, `return () => __mikuru_runCleanup(${slotCleanupVar});`);
|
|
822
|
+
emit(context, indent + 1, "},");
|
|
823
|
+
}
|
|
824
|
+
emit(context, indent, "};");
|
|
825
|
+
}
|
|
826
|
+
function componentPropEntries(context, attr) {
|
|
827
|
+
if (attr.name === "v-model") {
|
|
828
|
+
const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
829
|
+
const valueExpression = compileTemplateExpression(expression, attr.name, toExpressionContext(context, attr.valueLoc));
|
|
830
|
+
return [
|
|
831
|
+
`get modelValue() { return unwrap(${valueExpression}); }`,
|
|
832
|
+
`onUpdateModelValue: ($value) => { ${expression}.value = $value; }`
|
|
833
|
+
];
|
|
834
|
+
}
|
|
835
|
+
const event = parseEventDirective(attr.name);
|
|
836
|
+
if (event) {
|
|
837
|
+
if (event.modifiers.length) {
|
|
838
|
+
throwTemplateError("Event modifiers are not supported on component events yet", context, attr.loc);
|
|
839
|
+
}
|
|
840
|
+
const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
841
|
+
return [`${quotePropertyName(toComponentEventProp(event.name))}: ${eventHandlerExpression(handler, context, attr.valueLoc)}`];
|
|
842
|
+
}
|
|
843
|
+
const bindingName = getBindingName(attr.name);
|
|
844
|
+
if (bindingName) {
|
|
845
|
+
const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
|
|
846
|
+
return [`get ${quotePropertyName(bindingName)}() { return unwrap(${expression}); }`];
|
|
847
|
+
}
|
|
848
|
+
if (attr.name === "v-show") {
|
|
849
|
+
throwTemplateError(`Unsupported component directive ${attr.name}`, context, attr.loc);
|
|
850
|
+
}
|
|
851
|
+
return [`${quotePropertyName(attr.name)}: ${quote(attr.value === true ? true : attr.value)}`];
|
|
852
|
+
}
|
|
853
|
+
function hasMeaningfulChildren(node) {
|
|
854
|
+
return node.children.some((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
|
|
855
|
+
}
|
|
856
|
+
function toComponentEventProp(eventName) {
|
|
857
|
+
return `on${eventName
|
|
858
|
+
.split(/[-:]/)
|
|
859
|
+
.filter(Boolean)
|
|
860
|
+
.map((part) => part[0]?.toUpperCase() + part.slice(1))
|
|
861
|
+
.join("")}`;
|
|
862
|
+
}
|
|
863
|
+
function validateAttributes(node) {
|
|
864
|
+
for (const attr of node.attrs) {
|
|
865
|
+
if (attr.name.startsWith("v-") && !isSupportedDirectiveAttr(attr)) {
|
|
866
|
+
throw new Error(`Unsupported directive ${attr.name}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
function isDirectiveAttr(attr) {
|
|
871
|
+
return isSupportedDirectiveAttr(attr) || attr.name.startsWith("@") || attr.name.startsWith(":");
|
|
872
|
+
}
|
|
873
|
+
function isStructuralAttr(attr) {
|
|
874
|
+
return attr.name === "v-if" || attr.name === "v-else-if" || attr.name === "v-else" || attr.name === "v-for";
|
|
875
|
+
}
|
|
876
|
+
function isSupportedDirectiveAttr(attr) {
|
|
877
|
+
return (attr.name === "v-if" ||
|
|
878
|
+
attr.name === "v-else-if" ||
|
|
879
|
+
attr.name === "v-else" ||
|
|
880
|
+
attr.name === "v-for" ||
|
|
881
|
+
attr.name === "v-show" ||
|
|
882
|
+
attr.name === "v-model" ||
|
|
883
|
+
Boolean(parseEventDirective(attr.name)) ||
|
|
884
|
+
Boolean(getBindingName(attr.name)));
|
|
885
|
+
}
|
|
886
|
+
function parseEventDirective(name) {
|
|
887
|
+
const rawName = getEventName(name);
|
|
888
|
+
if (!rawName) {
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
const [eventName, ...modifiers] = rawName.split(".");
|
|
892
|
+
return {
|
|
893
|
+
name: eventName,
|
|
894
|
+
modifiers
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function validateEventModifiers(event, attr, context) {
|
|
898
|
+
for (const modifier of event.modifiers) {
|
|
899
|
+
if (modifier !== "prevent" && modifier !== "stop") {
|
|
900
|
+
throwTemplateError(`Unsupported event modifier .${modifier}`, context, attr.loc);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function getEventName(name) {
|
|
905
|
+
if (name.startsWith("@")) {
|
|
906
|
+
return name.slice(1);
|
|
907
|
+
}
|
|
908
|
+
if (name.startsWith("v-on:")) {
|
|
909
|
+
return name.slice("v-on:".length);
|
|
910
|
+
}
|
|
911
|
+
return undefined;
|
|
912
|
+
}
|
|
913
|
+
function getBindingName(name) {
|
|
914
|
+
if (name.startsWith(":")) {
|
|
915
|
+
return name.slice(1);
|
|
916
|
+
}
|
|
917
|
+
if (name.startsWith("v-bind:")) {
|
|
918
|
+
return name.slice("v-bind:".length);
|
|
919
|
+
}
|
|
920
|
+
return undefined;
|
|
921
|
+
}
|
|
922
|
+
function requireAttrValue(attr) {
|
|
923
|
+
if (attr.value === true) {
|
|
924
|
+
throw new Error(`Attribute ${attr.name} requires a value`);
|
|
925
|
+
}
|
|
926
|
+
return attr.value;
|
|
927
|
+
}
|
|
928
|
+
function isComponentTag(tag) {
|
|
929
|
+
return /^[A-Z]/.test(tag);
|
|
930
|
+
}
|
|
931
|
+
function emitBlock(context, indent, block) {
|
|
932
|
+
for (const line of block.split("\n")) {
|
|
933
|
+
emit(context, indent, line);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function emit(context, indent, line) {
|
|
937
|
+
context.lines.push(`${" ".repeat(indent)}${line}`);
|
|
938
|
+
}
|
|
939
|
+
function nextVar(context, prefix) {
|
|
940
|
+
const value = `${prefix}${context.index}`;
|
|
941
|
+
context.index += 1;
|
|
942
|
+
return value;
|
|
943
|
+
}
|
|
944
|
+
function quote(value) {
|
|
945
|
+
return JSON.stringify(value);
|
|
946
|
+
}
|
|
947
|
+
function quotePropertyName(value) {
|
|
948
|
+
return /^[A-Za-z_$][\w$]*$/.test(value) ? value : quote(value);
|
|
949
|
+
}
|
|
950
|
+
function createScopeAttr(descriptor) {
|
|
951
|
+
return `data-mikuru-scope-${hash(`${descriptor.filename ?? ""}\n${descriptor.style ?? ""}`)}`;
|
|
952
|
+
}
|
|
953
|
+
function scopeCssSelectors(css, scopeAttr) {
|
|
954
|
+
return css.replace(/([^{}]+)\{/g, (match, selectorSource) => {
|
|
955
|
+
const selector = selectorSource.trim();
|
|
956
|
+
if (!selector || selector.startsWith("@")) {
|
|
957
|
+
return match;
|
|
958
|
+
}
|
|
959
|
+
return `${selector
|
|
960
|
+
.split(",")
|
|
961
|
+
.map((part) => scopeSingleSelector(part.trim(), scopeAttr))
|
|
962
|
+
.join(", ")} {`;
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
function scopeSingleSelector(selector, scopeAttr) {
|
|
966
|
+
if (!selector || selector.includes(`[${scopeAttr}]`)) {
|
|
967
|
+
return selector;
|
|
968
|
+
}
|
|
969
|
+
const pseudoIndex = selector.search(/:{1,2}[A-Za-z-]/);
|
|
970
|
+
if (pseudoIndex === -1) {
|
|
971
|
+
return `${selector}[${scopeAttr}]`;
|
|
972
|
+
}
|
|
973
|
+
return `${selector.slice(0, pseudoIndex)}[${scopeAttr}]${selector.slice(pseudoIndex)}`;
|
|
974
|
+
}
|
|
975
|
+
function hash(value) {
|
|
976
|
+
let result = 5381;
|
|
977
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
978
|
+
result = (result * 33) ^ value.charCodeAt(index);
|
|
979
|
+
}
|
|
980
|
+
return (result >>> 0).toString(36);
|
|
981
|
+
}
|
|
982
|
+
//# sourceMappingURL=generate.js.map
|