@firsthandjs/compiler 0.9.0 → 0.10.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/dist/attributes.d.ts +13 -0
- package/dist/attributes.d.ts.map +1 -0
- package/dist/chunk-JEIWCE46.js +1998 -0
- package/dist/components.d.ts +25 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/guarded.d.ts +22 -0
- package/dist/guarded.d.ts.map +1 -0
- package/dist/html.d.ts +12 -0
- package/dist/html.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/jsx.d.ts +39 -0
- package/dist/jsx.d.ts.map +1 -0
- package/dist/lists.d.ts +24 -0
- package/dist/lists.d.ts.map +1 -0
- package/dist/marks.d.ts +31 -0
- package/dist/marks.d.ts.map +1 -0
- package/dist/markup.d.ts +27 -0
- package/dist/markup.d.ts.map +1 -0
- package/dist/nodes.d.ts +55 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/options.d.ts +48 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/positions.d.ts +50 -0
- package/dist/positions.d.ts.map +1 -0
- package/dist/props.d.ts +25 -0
- package/dist/props.d.ts.map +1 -0
- package/dist/runs.d.ts +69 -0
- package/dist/runs.d.ts.map +1 -0
- package/dist/site.d.ts +12 -0
- package/dist/site.d.ts.map +1 -0
- package/dist/state.d.ts +84 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/strict.d.ts +72 -0
- package/dist/strict.d.ts.map +1 -0
- package/dist/template.d.ts +11 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/transform.d.ts +14 -86
- package/dist/transform.d.ts.map +1 -1
- package/dist/vite.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ACKKPAGI.js +0 -1828
|
@@ -0,0 +1,1998 @@
|
|
|
1
|
+
// packages/compiler/src/ids.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
function stableId(packageName, filename) {
|
|
4
|
+
const normalised = filename.split(String.fromCharCode(92)).join("/").replace(/^(?:.*\/)?(?:src|lib)\//, "");
|
|
5
|
+
return createHash("sha256").update(`${packageName}:${normalised}`).digest("hex").slice(0, 8);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// packages/compiler/src/plugin.ts
|
|
9
|
+
import * as t16 from "@babel/types";
|
|
10
|
+
|
|
11
|
+
// packages/compiler/src/components.ts
|
|
12
|
+
import * as t7 from "@babel/types";
|
|
13
|
+
|
|
14
|
+
// packages/compiler/src/nodes.ts
|
|
15
|
+
import * as t2 from "@babel/types";
|
|
16
|
+
|
|
17
|
+
// packages/compiler/src/html.ts
|
|
18
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
19
|
+
"area",
|
|
20
|
+
"base",
|
|
21
|
+
"br",
|
|
22
|
+
"col",
|
|
23
|
+
"embed",
|
|
24
|
+
"hr",
|
|
25
|
+
"img",
|
|
26
|
+
"input",
|
|
27
|
+
"link",
|
|
28
|
+
"meta",
|
|
29
|
+
"param",
|
|
30
|
+
"source",
|
|
31
|
+
"track",
|
|
32
|
+
"wbr"
|
|
33
|
+
]);
|
|
34
|
+
var BOOLEAN_PROPERTIES = /* @__PURE__ */ new Set([
|
|
35
|
+
"checked",
|
|
36
|
+
"disabled",
|
|
37
|
+
"multiple",
|
|
38
|
+
"muted",
|
|
39
|
+
"readOnly",
|
|
40
|
+
"selected",
|
|
41
|
+
"autofocus",
|
|
42
|
+
"autoplay",
|
|
43
|
+
"controls",
|
|
44
|
+
"loop",
|
|
45
|
+
"open",
|
|
46
|
+
"required"
|
|
47
|
+
]);
|
|
48
|
+
var DOM_PROPERTIES = /* @__PURE__ */ new Set([
|
|
49
|
+
"value",
|
|
50
|
+
"textContent",
|
|
51
|
+
"innerHTML",
|
|
52
|
+
"innerText",
|
|
53
|
+
"scrollTop",
|
|
54
|
+
"scrollLeft",
|
|
55
|
+
"volume",
|
|
56
|
+
"currentTime",
|
|
57
|
+
"playbackRate",
|
|
58
|
+
"srcObject"
|
|
59
|
+
]);
|
|
60
|
+
var SVG_ELEMENTS = /* @__PURE__ */ new Set([
|
|
61
|
+
"svg",
|
|
62
|
+
"path",
|
|
63
|
+
"circle",
|
|
64
|
+
"rect",
|
|
65
|
+
"line",
|
|
66
|
+
"polyline",
|
|
67
|
+
"polygon",
|
|
68
|
+
"ellipse",
|
|
69
|
+
"g",
|
|
70
|
+
"defs",
|
|
71
|
+
"use",
|
|
72
|
+
"text",
|
|
73
|
+
"tspan",
|
|
74
|
+
"symbol",
|
|
75
|
+
"mask",
|
|
76
|
+
"pattern",
|
|
77
|
+
"clipPath",
|
|
78
|
+
"linearGradient",
|
|
79
|
+
"radialGradient",
|
|
80
|
+
"stop",
|
|
81
|
+
"filter"
|
|
82
|
+
]);
|
|
83
|
+
function escapeText(value) {
|
|
84
|
+
return value.replace(/&/g, "&").replace(/</g, "<");
|
|
85
|
+
}
|
|
86
|
+
function escapeAttribute(value) {
|
|
87
|
+
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
88
|
+
}
|
|
89
|
+
function eventName(attribute) {
|
|
90
|
+
return attribute.slice(2).toLowerCase();
|
|
91
|
+
}
|
|
92
|
+
function isEventName(name) {
|
|
93
|
+
return name.startsWith("on") && name.length > 2 && /[A-Z:]/.test(name[2]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// packages/compiler/src/marks.ts
|
|
97
|
+
import * as t from "@babel/types";
|
|
98
|
+
var CHILD_THUNK = /* @__PURE__ */ Symbol("firsthand.childThunk");
|
|
99
|
+
var GENERATED = /* @__PURE__ */ Symbol("firsthand.generated");
|
|
100
|
+
function generated(arrow) {
|
|
101
|
+
arrow[GENERATED] = true;
|
|
102
|
+
return arrow;
|
|
103
|
+
}
|
|
104
|
+
var LIST_CALL = /* @__PURE__ */ Symbol("firsthand.list");
|
|
105
|
+
function isKeyedList(node) {
|
|
106
|
+
return t.isCallExpression(node) && LIST_CALL in node;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// packages/compiler/src/nodes.ts
|
|
110
|
+
var CORE = "@firsthandjs/core";
|
|
111
|
+
function isComponentTag(node) {
|
|
112
|
+
const name = node.openingElement.name;
|
|
113
|
+
if (t2.isJSXMemberExpression(name)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (!t2.isJSXIdentifier(name)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return /^[A-Z]/.test(name.name);
|
|
120
|
+
}
|
|
121
|
+
function tagExpression(name) {
|
|
122
|
+
if (t2.isJSXIdentifier(name)) {
|
|
123
|
+
return t2.identifier(name.name);
|
|
124
|
+
}
|
|
125
|
+
return t2.memberExpression(tagExpression(name.object), t2.identifier(name.property.name));
|
|
126
|
+
}
|
|
127
|
+
function isPure(node) {
|
|
128
|
+
if (PURE_LEAVES.has(node.type)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
const parts = PURE_PARTS.get(node.type)?.(node);
|
|
132
|
+
if (parts == null) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return parts.every((part) => part == null || t2.isExpression(part) && isPure(part));
|
|
136
|
+
}
|
|
137
|
+
var PURE_LEAVES = /* @__PURE__ */ new Set([
|
|
138
|
+
"Identifier",
|
|
139
|
+
"ThisExpression",
|
|
140
|
+
"StringLiteral",
|
|
141
|
+
"NumericLiteral",
|
|
142
|
+
"BooleanLiteral",
|
|
143
|
+
"NullLiteral",
|
|
144
|
+
"BigIntLiteral",
|
|
145
|
+
"RegExpLiteral"
|
|
146
|
+
]);
|
|
147
|
+
var PURE_PARTS = /* @__PURE__ */ new Map([
|
|
148
|
+
["MemberExpression", memberParts],
|
|
149
|
+
["OptionalMemberExpression", memberParts],
|
|
150
|
+
["UnaryExpression", unaryParts],
|
|
151
|
+
[
|
|
152
|
+
"BinaryExpression",
|
|
153
|
+
(node) => [node.left, node.right]
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
"LogicalExpression",
|
|
157
|
+
(node) => [node.left, node.right]
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
"ConditionalExpression",
|
|
161
|
+
(node) => {
|
|
162
|
+
const conditional = node;
|
|
163
|
+
return [conditional.test, conditional.consequent, conditional.alternate];
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
["TemplateLiteral", (node) => node.expressions],
|
|
167
|
+
["ArrayExpression", (node) => node.elements],
|
|
168
|
+
["ObjectExpression", objectParts]
|
|
169
|
+
]);
|
|
170
|
+
function memberParts(node) {
|
|
171
|
+
const member = node;
|
|
172
|
+
return member.computed ? [member.object, member.property] : [member.object];
|
|
173
|
+
}
|
|
174
|
+
function unaryParts(node) {
|
|
175
|
+
const unary = node;
|
|
176
|
+
return unary.operator === "delete" ? null : [unary.argument];
|
|
177
|
+
}
|
|
178
|
+
function objectParts(node) {
|
|
179
|
+
const values = [];
|
|
180
|
+
for (const property of node.properties) {
|
|
181
|
+
if (!t2.isObjectProperty(property) || property.computed) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
values.push(property.value);
|
|
185
|
+
}
|
|
186
|
+
return values;
|
|
187
|
+
}
|
|
188
|
+
function propertyKey(name) {
|
|
189
|
+
return t2.isValidIdentifier(name) ? t2.identifier(name) : t2.stringLiteral(name);
|
|
190
|
+
}
|
|
191
|
+
function attributeName(attribute) {
|
|
192
|
+
const name = attribute.name;
|
|
193
|
+
return t2.isJSXNamespacedName(name) ? `${name.namespace.name}:${name.name.name}` : name.name;
|
|
194
|
+
}
|
|
195
|
+
function attributeValue(attribute) {
|
|
196
|
+
const value = attribute.value;
|
|
197
|
+
if (value === null || value === void 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (t2.isStringLiteral(value)) {
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
if (t2.isJSXExpressionContainer(value)) {
|
|
204
|
+
return value.expression;
|
|
205
|
+
}
|
|
206
|
+
throw new Error("An element is not a valid attribute value. Wrap it in braces: attr={<El />}.");
|
|
207
|
+
}
|
|
208
|
+
function isStaticValue(value) {
|
|
209
|
+
return t2.isStringLiteral(value) || t2.isNumericLiteral(value) || t2.isBooleanLiteral(value) || t2.isNullLiteral(value);
|
|
210
|
+
}
|
|
211
|
+
function canInlineAttribute(name) {
|
|
212
|
+
return !name.startsWith("prop:") && !name.startsWith("attr:") && !DOM_PROPERTIES.has(name) && !BOOLEAN_PROPERTIES.has(name);
|
|
213
|
+
}
|
|
214
|
+
function isFirsthandImport(binding) {
|
|
215
|
+
const parent = binding.path.parentPath;
|
|
216
|
+
if (!parent.isImportDeclaration()) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const source = parent.node.source.value;
|
|
220
|
+
return source === CORE || source.startsWith("@firsthandjs/") || source === "firsthand";
|
|
221
|
+
}
|
|
222
|
+
function declaredName(path) {
|
|
223
|
+
const parent = path.parentPath;
|
|
224
|
+
if (parent.isVariableDeclarator() && t2.isIdentifier(parent.node.id)) {
|
|
225
|
+
return parent.node.id.name;
|
|
226
|
+
}
|
|
227
|
+
return "Component";
|
|
228
|
+
}
|
|
229
|
+
function planChildren(children) {
|
|
230
|
+
const entries = [];
|
|
231
|
+
let pending = "";
|
|
232
|
+
const flush = () => {
|
|
233
|
+
if (pending !== "") {
|
|
234
|
+
entries.push({ kind: "text", text: pending });
|
|
235
|
+
pending = "";
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
for (const child of children) {
|
|
239
|
+
if (t2.isJSXText(child)) {
|
|
240
|
+
pending += cleanText(child.value);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (t2.isJSXExpressionContainer(child)) {
|
|
244
|
+
const expression = child.expression;
|
|
245
|
+
if (t2.isJSXEmptyExpression(expression)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (t2.isStringLiteral(expression) || t2.isNumericLiteral(expression)) {
|
|
249
|
+
pending += String(expression.value);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
flush();
|
|
253
|
+
const isList = expression[LIST_CALL] === true;
|
|
254
|
+
entries.push({ kind: isList ? "list" : "dynamic", expression });
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
flush();
|
|
258
|
+
if (t2.isJSXElement(child) && !isComponentTag(child)) {
|
|
259
|
+
entries.push({ kind: "element", element: child });
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
entries.push({ kind: "dynamic", expression: child });
|
|
263
|
+
}
|
|
264
|
+
flush();
|
|
265
|
+
return entries;
|
|
266
|
+
}
|
|
267
|
+
function cleanText(value) {
|
|
268
|
+
const lines = value.split(/\r\n|\n|\r/);
|
|
269
|
+
const kept = [];
|
|
270
|
+
for (let i = 0; i < lines.length; i++) {
|
|
271
|
+
let line = lines[i];
|
|
272
|
+
if (i > 0) {
|
|
273
|
+
line = line.replace(/^[\t ]+/, "");
|
|
274
|
+
}
|
|
275
|
+
if (i < lines.length - 1) {
|
|
276
|
+
line = line.replace(/[\t ]+$/, "");
|
|
277
|
+
}
|
|
278
|
+
if (line !== "") {
|
|
279
|
+
kept.push(line);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return kept.join(" ");
|
|
283
|
+
}
|
|
284
|
+
function staticLiteral(value) {
|
|
285
|
+
if (t2.isStringLiteral(value)) {
|
|
286
|
+
return value.value;
|
|
287
|
+
}
|
|
288
|
+
if (t2.isNumericLiteral(value)) {
|
|
289
|
+
return String(value.value);
|
|
290
|
+
}
|
|
291
|
+
if (t2.isBooleanLiteral(value)) {
|
|
292
|
+
return value.value ? "" : null;
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// packages/compiler/src/props.ts
|
|
298
|
+
import * as t5 from "@babel/types";
|
|
299
|
+
|
|
300
|
+
// packages/compiler/src/positions.ts
|
|
301
|
+
import * as t3 from "@babel/types";
|
|
302
|
+
function thunk(expression) {
|
|
303
|
+
const arrow = t3.arrowFunctionExpression([], expression);
|
|
304
|
+
arrow[CHILD_THUNK] = true;
|
|
305
|
+
takePosition(arrow, expression);
|
|
306
|
+
return arrow;
|
|
307
|
+
}
|
|
308
|
+
function takePosition(target, expression) {
|
|
309
|
+
const loc = expression.loc;
|
|
310
|
+
if (loc !== null && loc !== void 0) {
|
|
311
|
+
target.loc = { ...loc, end: loc.start };
|
|
312
|
+
expression.loc = null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function expressionStatement2(expression) {
|
|
316
|
+
return located(t3.expressionStatement(expression), expression);
|
|
317
|
+
}
|
|
318
|
+
function located(node, source) {
|
|
319
|
+
if (source == null || source.loc == null) {
|
|
320
|
+
return node;
|
|
321
|
+
}
|
|
322
|
+
node.loc = source.loc;
|
|
323
|
+
return node;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// packages/compiler/src/state.ts
|
|
327
|
+
import * as t4 from "@babel/types";
|
|
328
|
+
var RUNTIME = "@firsthandjs/dom/internal";
|
|
329
|
+
var SERVER_RUNTIME = "@firsthandjs/server/internal";
|
|
330
|
+
function pushOnce(build, statement) {
|
|
331
|
+
build.once.push(statement);
|
|
332
|
+
}
|
|
333
|
+
function pushEach(build, statement) {
|
|
334
|
+
build.each.push(statement);
|
|
335
|
+
}
|
|
336
|
+
function runtime(state, name, source) {
|
|
337
|
+
return runtimeFrom(state, name, source ?? (state.firsthand.ssr ? SERVER_RUNTIME : RUNTIME));
|
|
338
|
+
}
|
|
339
|
+
function runtimeFrom(state, name, source) {
|
|
340
|
+
const key = `${source}#${name}`;
|
|
341
|
+
let local = state.firsthand.imports.get(key);
|
|
342
|
+
if (local === void 0) {
|
|
343
|
+
local = t4.identifier(`_$${name}`);
|
|
344
|
+
state.firsthand.imports.set(key, local);
|
|
345
|
+
}
|
|
346
|
+
return t4.cloneNode(local);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// packages/compiler/src/props.ts
|
|
350
|
+
function rewritePropsDestructuring(call, name, state) {
|
|
351
|
+
const setup = call.get("arguments.0");
|
|
352
|
+
const params = setup.get("params");
|
|
353
|
+
const param = params[0];
|
|
354
|
+
if (param === void 0 || param.isIdentifier()) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (!param.isObjectPattern()) {
|
|
358
|
+
throw call.buildCodeFrameError(
|
|
359
|
+
`${name}: a component's props parameter must be an object pattern or a plain identifier. Props are an object, so an array pattern cannot destructure them.`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
const propsId = setup.scope.generateUidIdentifier("props");
|
|
363
|
+
const collected = { reads: [], rests: [], call, name };
|
|
364
|
+
collectPattern(param.node, t5.cloneNode(propsId), collected);
|
|
365
|
+
setup.scope.crawl();
|
|
366
|
+
const references = referencesOf(setup, propsId, collected);
|
|
367
|
+
param.replaceWith(propsId);
|
|
368
|
+
for (const entry of references) {
|
|
369
|
+
for (const reference2 of entry.paths) {
|
|
370
|
+
if (entry.access !== propsId) {
|
|
371
|
+
reference2.replaceWith(located(t5.cloneNode(entry.access), reference2.node));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
hoistRests(setup, propsId, collected.rests, state);
|
|
376
|
+
}
|
|
377
|
+
function referencesOf(setup, propsId, collected) {
|
|
378
|
+
const { call, name } = collected;
|
|
379
|
+
const references = [];
|
|
380
|
+
for (const entry of [...collected.reads, ...collected.rests]) {
|
|
381
|
+
const access = "access" in entry ? entry.access : propsId;
|
|
382
|
+
const binding = setup.scope.getBinding(entry.local);
|
|
383
|
+
if (binding.constantViolations.length > 0) {
|
|
384
|
+
throw call.buildCodeFrameError(
|
|
385
|
+
`${name}: \`${entry.local}\` is destructured from props and then assigned to. Props are readonly; assign to a signal instead.`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
references.push({ paths: binding.referencePaths, access });
|
|
389
|
+
}
|
|
390
|
+
return references;
|
|
391
|
+
}
|
|
392
|
+
function hoistRests(setup, propsId, rests, state) {
|
|
393
|
+
if (rests.length === 0) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (!setup.get("body").isBlockStatement()) {
|
|
397
|
+
const body = setup.node.body;
|
|
398
|
+
setup.node.body = t5.blockStatement([t5.returnStatement(body)]);
|
|
399
|
+
}
|
|
400
|
+
const block = setup.get("body");
|
|
401
|
+
for (const rest of rests) {
|
|
402
|
+
block.node.body.unshift(
|
|
403
|
+
t5.variableDeclaration("const", [
|
|
404
|
+
t5.variableDeclarator(
|
|
405
|
+
t5.identifier(rest.local),
|
|
406
|
+
t5.callExpression(runtime(state, "rest"), [
|
|
407
|
+
t5.cloneNode(propsId),
|
|
408
|
+
t5.arrayExpression(rest.omit.map((key) => t5.stringLiteral(key)))
|
|
409
|
+
])
|
|
410
|
+
)
|
|
411
|
+
])
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function collectPattern(pattern, base, into) {
|
|
416
|
+
const { reads, rests, call, name } = into;
|
|
417
|
+
const seen = [];
|
|
418
|
+
for (const property of pattern.properties) {
|
|
419
|
+
if (t5.isRestElement(property)) {
|
|
420
|
+
rests.push({ local: property.argument.name, omit: [...seen] });
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (property.computed || !t5.isIdentifier(property.key)) {
|
|
424
|
+
throw call.buildCodeFrameError(
|
|
425
|
+
`${name}: props can only be destructured by plain key. A computed key would have to be resolved at setup time, which is exactly the snapshot this rewrite avoids.`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
seen.push(property.key.name);
|
|
429
|
+
const access = t5.memberExpression(
|
|
430
|
+
t5.cloneNode(base),
|
|
431
|
+
t5.identifier(property.key.name)
|
|
432
|
+
);
|
|
433
|
+
const value = property.value;
|
|
434
|
+
if (t5.isIdentifier(value)) {
|
|
435
|
+
reads.push({ local: value.name, access });
|
|
436
|
+
} else if (t5.isAssignmentPattern(value) && t5.isIdentifier(value.left)) {
|
|
437
|
+
reads.push({
|
|
438
|
+
local: value.left.name,
|
|
439
|
+
access: t5.logicalExpression("??", access, value.right)
|
|
440
|
+
});
|
|
441
|
+
} else if (t5.isObjectPattern(value)) {
|
|
442
|
+
collectPattern(value, access, into);
|
|
443
|
+
} else {
|
|
444
|
+
throw call.buildCodeFrameError(
|
|
445
|
+
`${name}: this destructuring pattern cannot be rewritten into live reads. Take the props object and read from it where you need the value.`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// packages/compiler/src/strict.ts
|
|
452
|
+
import * as t6 from "@babel/types";
|
|
453
|
+
function checkKeptReads(setup, name) {
|
|
454
|
+
const parameter = setup.node.params[0];
|
|
455
|
+
const propsName = t6.isIdentifier(parameter) ? parameter.name : null;
|
|
456
|
+
const body = setup.get("body");
|
|
457
|
+
if (!body.isBlockStatement()) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
body.traverse({
|
|
461
|
+
Function(nested) {
|
|
462
|
+
nested.skip();
|
|
463
|
+
},
|
|
464
|
+
VariableDeclarator(declarator) {
|
|
465
|
+
const init = declarator.node.init;
|
|
466
|
+
if (init === null || init === void 0 || !readsOnly(init, propsName)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const target = declarator.node.id;
|
|
470
|
+
const label = t6.isIdentifier(target) ? `\`${target.name}\`` : "This value";
|
|
471
|
+
throw declarator.buildCodeFrameError(
|
|
472
|
+
`${name}: ${label} is read once, here, and then kept. A setup runs one time per instance, so this value will not change again.
|
|
473
|
+
|
|
474
|
+
Move the read into the part, handler, effect or computed that should re-read it \u2014 or wrap it in snapshot(() => \u2026) if reading once is what you meant.`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
function checkDecidedOnce(setup, name) {
|
|
480
|
+
const body = setup.get("body");
|
|
481
|
+
if (!body.isBlockStatement()) {
|
|
482
|
+
checkChoice(setup.node.body, body, name);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
body.traverse({
|
|
486
|
+
Function(nested) {
|
|
487
|
+
nested.skip();
|
|
488
|
+
},
|
|
489
|
+
ReturnStatement(statement) {
|
|
490
|
+
const returned = statement.node.argument;
|
|
491
|
+
if (returned !== null && returned !== void 0) {
|
|
492
|
+
checkChoice(returned, statement, name);
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
/**
|
|
496
|
+
* The same mistake spelled with a keyword, and the one that actually
|
|
497
|
+
* shipped: a route guard that returned `<Navigate />` early left the page
|
|
498
|
+
* it was meant to hide on the screen after a sign-out.
|
|
499
|
+
*/
|
|
500
|
+
IfStatement(statement) {
|
|
501
|
+
if (!readsSignal(statement.node.test)) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (returnsView(statement.node.consequent) || returnsView(statement.node.alternate)) {
|
|
505
|
+
reportDecidedOnce(statement.get("test"), name);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function checkChoice(returned, at, name) {
|
|
511
|
+
if (t6.isConditionalExpression(returned)) {
|
|
512
|
+
if (!hasView(returned.consequent) && !hasView(returned.alternate)) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (readsSignal(returned.test)) {
|
|
516
|
+
reportDecidedOnce(at, name);
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (t6.isLogicalExpression(returned) && returned.operator !== "??") {
|
|
521
|
+
if (!hasView(returned.right) && !hasView(returned.left)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (readsSignal(returned.left)) {
|
|
525
|
+
reportDecidedOnce(at, name);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function reportDecidedOnce(at, name) {
|
|
530
|
+
throw at.buildCodeFrameError(
|
|
531
|
+
`${name}: this view is chosen once, here, from a value that changes. A setup runs one time per instance, so the other branch will never appear.
|
|
532
|
+
|
|
533
|
+
Either put the choice inside the markup, where it is a part:
|
|
534
|
+
|
|
535
|
+
return <>{open.value ? <A /> : <B />}</>;
|
|
536
|
+
|
|
537
|
+
or return a render function, which is a reactive scope of its own and may use ordinary control flow:
|
|
538
|
+
|
|
539
|
+
return () => (open.value ? <A /> : <B />);`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
function checkRunBody(setup, name) {
|
|
543
|
+
const runs = [];
|
|
544
|
+
const body = setup.get("body");
|
|
545
|
+
body.traverse({
|
|
546
|
+
Function(nested) {
|
|
547
|
+
nested.skip();
|
|
548
|
+
},
|
|
549
|
+
ReturnStatement(statement) {
|
|
550
|
+
const argument = statement.get("argument");
|
|
551
|
+
if (argument.isArrowFunctionExpression() || argument.isFunctionExpression()) {
|
|
552
|
+
runs.push(argument);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
for (const run of runs) {
|
|
557
|
+
checkNothingPersistent(run, name);
|
|
558
|
+
checkRepeatedMarkup(run, name);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
var PERSISTENT = /* @__PURE__ */ new Map([
|
|
562
|
+
["signal", "a signal"],
|
|
563
|
+
["computed", "a computed"],
|
|
564
|
+
["effect", "an effect"],
|
|
565
|
+
["deepSignal", "a deep signal"],
|
|
566
|
+
["useResource", "a resource"],
|
|
567
|
+
["useAction", "an action"],
|
|
568
|
+
["onCleanup", "a cleanup"],
|
|
569
|
+
["provide", "a context value"]
|
|
570
|
+
]);
|
|
571
|
+
function checkNothingPersistent(run, name) {
|
|
572
|
+
run.traverse({
|
|
573
|
+
Function(nested) {
|
|
574
|
+
nested.skip();
|
|
575
|
+
},
|
|
576
|
+
CallExpression(call) {
|
|
577
|
+
const callee = call.node.callee;
|
|
578
|
+
if (!t6.isIdentifier(callee)) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const what = PERSISTENT.get(callee.name);
|
|
582
|
+
if (what === void 0) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const binding = call.scope.getBinding(callee.name);
|
|
586
|
+
if (binding === void 0 || !isFirsthandImport(binding)) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
throw call.buildCodeFrameError(
|
|
590
|
+
`${name}: this render function makes ${what}, and it runs again whenever something it read changes \u2014 so this would be made again, and the one before it thrown away.
|
|
591
|
+
|
|
592
|
+
Move it into the setup, above the render function. Persistent things are made once, where the setup runs once.`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function checkRepeatedMarkup(run, name) {
|
|
598
|
+
const report = (at) => {
|
|
599
|
+
throw at.buildCodeFrameError(
|
|
600
|
+
`${name}: this markup is written once and appears many times, so where it stands cannot say which of them is which.
|
|
601
|
+
|
|
602
|
+
Give it a key:
|
|
603
|
+
|
|
604
|
+
{rows.map((row) => <Row key={row.id} row={row} />)}`
|
|
605
|
+
);
|
|
606
|
+
};
|
|
607
|
+
run.traverse({
|
|
608
|
+
Function(nested) {
|
|
609
|
+
nested.skip();
|
|
610
|
+
},
|
|
611
|
+
"ForStatement|ForOfStatement|ForInStatement|WhileStatement|DoWhileStatement"(loop) {
|
|
612
|
+
loop.traverse({
|
|
613
|
+
JSXElement(element) {
|
|
614
|
+
element.skip();
|
|
615
|
+
if (!hasKey(element.node)) {
|
|
616
|
+
report(element);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
CallExpression(call) {
|
|
622
|
+
const callee = call.node.callee;
|
|
623
|
+
if (LIST_CALL in call.node || !t6.isMemberExpression(callee) || callee.computed || !t6.isIdentifier(callee.property, { name: "map" })) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
call.traverse({
|
|
627
|
+
JSXElement(element) {
|
|
628
|
+
element.skip();
|
|
629
|
+
if (!hasKey(element.node)) {
|
|
630
|
+
report(element);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function hasKey(element) {
|
|
638
|
+
return element.openingElement.attributes.some(
|
|
639
|
+
(attribute) => t6.isJSXAttribute(attribute) && attributeName(attribute) === "key"
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
function returnsView(node) {
|
|
643
|
+
if (node === null || node === void 0) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
if (t6.isReturnStatement(node)) {
|
|
647
|
+
return node.argument !== null && node.argument !== void 0 && hasView(node.argument);
|
|
648
|
+
}
|
|
649
|
+
if (t6.isBlockStatement(node)) {
|
|
650
|
+
return node.body.some((inner) => returnsView(inner));
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
function hasView(node) {
|
|
655
|
+
if (t6.isConditionalExpression(node)) {
|
|
656
|
+
return hasView(node.consequent) || hasView(node.alternate);
|
|
657
|
+
}
|
|
658
|
+
return t6.isJSXElement(node) || t6.isJSXFragment(node);
|
|
659
|
+
}
|
|
660
|
+
function readsSignal(node) {
|
|
661
|
+
let found = false;
|
|
662
|
+
const walk = (current) => {
|
|
663
|
+
if (current === null || current === void 0 || found) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (t6.isMemberExpression(current)) {
|
|
667
|
+
if (!current.computed && t6.isIdentifier(current.property, { name: "value" })) {
|
|
668
|
+
found = true;
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
walk(current.object);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (t6.isUnaryExpression(current)) {
|
|
675
|
+
walk(current.argument);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (t6.isBinaryExpression(current) || t6.isLogicalExpression(current)) {
|
|
679
|
+
walk(current.left);
|
|
680
|
+
walk(current.right);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (t6.isConditionalExpression(current)) {
|
|
684
|
+
walk(current.test);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
walk(node);
|
|
688
|
+
return found;
|
|
689
|
+
}
|
|
690
|
+
function readsOnly(node, propsName) {
|
|
691
|
+
const found = { read: false };
|
|
692
|
+
return walkRead(node, propsName, found) && found.read;
|
|
693
|
+
}
|
|
694
|
+
function walkRead(current, propsName, found) {
|
|
695
|
+
if (t6.isTemplateLiteral(current)) {
|
|
696
|
+
return current.expressions.every((part) => walkRead(part, propsName, found));
|
|
697
|
+
}
|
|
698
|
+
if (t6.isIdentifier(current) || t6.isLiteral(current)) {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
if (t6.isMemberExpression(current)) {
|
|
702
|
+
return walkMember(current, propsName, found);
|
|
703
|
+
}
|
|
704
|
+
if (t6.isUnaryExpression(current)) {
|
|
705
|
+
return walkRead(current.argument, propsName, found);
|
|
706
|
+
}
|
|
707
|
+
if (t6.isBinaryExpression(current) || t6.isLogicalExpression(current)) {
|
|
708
|
+
return walkRead(current.left, propsName, found) && walkRead(current.right, propsName, found);
|
|
709
|
+
}
|
|
710
|
+
if (t6.isConditionalExpression(current)) {
|
|
711
|
+
return walkRead(current.test, propsName, found) && walkRead(current.consequent, propsName, found) && walkRead(current.alternate, propsName, found);
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
function walkMember(current, propsName, found) {
|
|
716
|
+
const property = current.property;
|
|
717
|
+
if (!current.computed && t6.isIdentifier(property, { name: "value" })) {
|
|
718
|
+
found.read = true;
|
|
719
|
+
}
|
|
720
|
+
if (propsName !== null && t6.isIdentifier(current.object, { name: propsName })) {
|
|
721
|
+
found.read = true;
|
|
722
|
+
}
|
|
723
|
+
return walkRead(current.object, propsName, found) && (!current.computed || walkRead(property, propsName, found));
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// packages/compiler/src/components.ts
|
|
727
|
+
function annotateComponent(path, state, options) {
|
|
728
|
+
const callee = path.node.callee;
|
|
729
|
+
if (!t7.isIdentifier(callee, { name: "component" }) || path.node.arguments.length > 2) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const binding = path.scope.getBinding("component");
|
|
733
|
+
if (binding === void 0 || !isFirsthandImport(binding)) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const setup = path.node.arguments[0];
|
|
737
|
+
if (!t7.isArrowFunctionExpression(setup) && !t7.isFunctionExpression(setup)) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const name = declaredName(path);
|
|
741
|
+
rewritePropsDestructuring(path, name, state);
|
|
742
|
+
if (options.strictReactivity !== false) {
|
|
743
|
+
const setupPath = path.get("arguments.0");
|
|
744
|
+
checkKeptReads(setupPath, name);
|
|
745
|
+
checkDecidedOnce(setupPath, name);
|
|
746
|
+
checkRunBody(setupPath, name);
|
|
747
|
+
}
|
|
748
|
+
path.node.arguments = [
|
|
749
|
+
setup,
|
|
750
|
+
path.node.arguments[1] ?? t7.identifier("undefined"),
|
|
751
|
+
t7.stringLiteral(`${state.firsthand.moduleId}/${name}`),
|
|
752
|
+
t7.stringLiteral(name)
|
|
753
|
+
];
|
|
754
|
+
}
|
|
755
|
+
var NAMED = /* @__PURE__ */ new Map([
|
|
756
|
+
["signal", "signal"],
|
|
757
|
+
["computed", "computed"],
|
|
758
|
+
["deepSignal", "signal"]
|
|
759
|
+
]);
|
|
760
|
+
function nameCell(path, state) {
|
|
761
|
+
const init = path.node.init;
|
|
762
|
+
const target = path.node.id;
|
|
763
|
+
if (!t7.isCallExpression(init) || !t7.isIdentifier(target) || !t7.isIdentifier(init.callee)) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const kind = NAMED.get(init.callee.name);
|
|
767
|
+
if (kind === void 0) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const binding = path.scope.getBinding(init.callee.name);
|
|
771
|
+
if (binding === void 0 || !isFirsthandImport(binding)) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const line = init.loc.start.line;
|
|
775
|
+
const source = state.filename;
|
|
776
|
+
const cut = Math.max(source.lastIndexOf("/"), source.lastIndexOf(String.fromCharCode(92)));
|
|
777
|
+
const where = `${source.slice(cut + 1)}:${String(line)}`;
|
|
778
|
+
const call = t7.callExpression(runtime(state, "label"), [
|
|
779
|
+
init,
|
|
780
|
+
t7.stringLiteral(kind),
|
|
781
|
+
t7.stringLiteral(`${target.name} (${where})`)
|
|
782
|
+
]);
|
|
783
|
+
path.node.init = call;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// packages/compiler/src/jsx.ts
|
|
787
|
+
import * as t14 from "@babel/types";
|
|
788
|
+
|
|
789
|
+
// packages/compiler/src/markup.ts
|
|
790
|
+
import * as t8 from "@babel/types";
|
|
791
|
+
function pushText(markup, text) {
|
|
792
|
+
markup.parts[markup.parts.length - 1] = markup.parts[markup.parts.length - 1] + text;
|
|
793
|
+
}
|
|
794
|
+
function pushHole(markup, value) {
|
|
795
|
+
markup.values.push(value);
|
|
796
|
+
markup.parts.push("");
|
|
797
|
+
}
|
|
798
|
+
function compileMarkup(path, state) {
|
|
799
|
+
const markup = { parts: [""], values: [] };
|
|
800
|
+
emitMarkupElement(path.node, markup, state);
|
|
801
|
+
if (markup.values.length === 0) {
|
|
802
|
+
return t8.callExpression(runtime(state, "ssr"), [
|
|
803
|
+
t8.arrayExpression([t8.stringLiteral(markup.parts[0])])
|
|
804
|
+
]);
|
|
805
|
+
}
|
|
806
|
+
return t8.callExpression(runtime(state, "ssr"), [
|
|
807
|
+
t8.arrayExpression(markup.parts.map((part) => t8.stringLiteral(part))),
|
|
808
|
+
...markup.values
|
|
809
|
+
]);
|
|
810
|
+
}
|
|
811
|
+
function emitMarkupElement(node, markup, state) {
|
|
812
|
+
const name = node.openingElement.name;
|
|
813
|
+
if (!t8.isJSXIdentifier(name)) {
|
|
814
|
+
const namespaced = name;
|
|
815
|
+
throw new Error(
|
|
816
|
+
`Namespaced element names are not supported: <${namespaced.namespace.name}:${namespaced.name.name}>. Write the element without a namespace; SVG children are resolved by the parser.`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
const tag = name.name;
|
|
820
|
+
pushText(markup, `<${tag}`);
|
|
821
|
+
for (const attribute of node.openingElement.attributes) {
|
|
822
|
+
emitMarkupAttribute(attribute, markup, state);
|
|
823
|
+
}
|
|
824
|
+
pushText(markup, ">");
|
|
825
|
+
emitMarkupChildren(node.children, markup, state);
|
|
826
|
+
if (!VOID_ELEMENTS.has(tag)) {
|
|
827
|
+
pushText(markup, `</${tag}>`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function emitMarkupAttribute(attribute, markup, state) {
|
|
831
|
+
if (t8.isJSXSpreadAttribute(attribute)) {
|
|
832
|
+
pushHole(markup, t8.callExpression(runtime(state, "spread"), [attribute.argument]));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const name = attributeName(attribute);
|
|
836
|
+
const value = attributeValue(attribute);
|
|
837
|
+
if (name === "ref" || isEventName(name)) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (name === "key") {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (value === null) {
|
|
844
|
+
pushText(markup, ` ${name}=""`);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (isStaticValue(value)) {
|
|
848
|
+
const literal = staticLiteral(value);
|
|
849
|
+
if (literal !== null) {
|
|
850
|
+
pushText(markup, ` ${name}="${escapeAttribute(literal)}"`);
|
|
851
|
+
}
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
pushHole(markup, markupAttributeCall(name, value, state));
|
|
855
|
+
}
|
|
856
|
+
function markupAttributeCall(name, value, state) {
|
|
857
|
+
if (name.startsWith("prop:")) {
|
|
858
|
+
return t8.callExpression(runtime(state, "setProperty"), [t8.stringLiteral(name.slice(5)), value]);
|
|
859
|
+
}
|
|
860
|
+
if (name.startsWith("attr:")) {
|
|
861
|
+
return t8.callExpression(runtime(state, "setAttribute"), [
|
|
862
|
+
t8.stringLiteral(name.slice(5)),
|
|
863
|
+
value
|
|
864
|
+
]);
|
|
865
|
+
}
|
|
866
|
+
if (name === "class" || name === "className") {
|
|
867
|
+
return t8.callExpression(runtime(state, "setClass"), [value]);
|
|
868
|
+
}
|
|
869
|
+
if (name === "style") {
|
|
870
|
+
return t8.callExpression(runtime(state, "setStyle"), [value]);
|
|
871
|
+
}
|
|
872
|
+
if (BOOLEAN_PROPERTIES.has(name)) {
|
|
873
|
+
return t8.callExpression(runtime(state, "setBoolean"), [t8.stringLiteral(name), value]);
|
|
874
|
+
}
|
|
875
|
+
if (DOM_PROPERTIES.has(name)) {
|
|
876
|
+
return t8.callExpression(runtime(state, "setProperty"), [t8.stringLiteral(name), value]);
|
|
877
|
+
}
|
|
878
|
+
return t8.callExpression(runtime(state, "setAttribute"), [t8.stringLiteral(name), value]);
|
|
879
|
+
}
|
|
880
|
+
function emitMarkupChildren(children, markup, state) {
|
|
881
|
+
const entries = planChildren(children);
|
|
882
|
+
for (let i = 0; i < entries.length; i++) {
|
|
883
|
+
const entry = entries[i];
|
|
884
|
+
if (entry.kind === "text") {
|
|
885
|
+
pushText(markup, escapeText(entry.text));
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (entry.kind === "element") {
|
|
889
|
+
emitMarkupElement(entry.element, markup, state);
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (entries.length > 1) {
|
|
893
|
+
pushText(markup, "<!--[-->");
|
|
894
|
+
}
|
|
895
|
+
pushHole(markup, t8.callExpression(runtime(state, "child"), [entry.expression]));
|
|
896
|
+
if (i !== entries.length - 1) {
|
|
897
|
+
pushText(markup, "<!---->");
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// packages/compiler/src/runs.ts
|
|
903
|
+
import * as t9 from "@babel/types";
|
|
904
|
+
function collectViews(program, state) {
|
|
905
|
+
const { views, viewNodes } = state.firsthand;
|
|
906
|
+
const record = (path) => {
|
|
907
|
+
let fn = path.getFunctionParent();
|
|
908
|
+
while (fn !== null) {
|
|
909
|
+
viewNodes.add(fn.node);
|
|
910
|
+
const named = moduleLevelName(fn);
|
|
911
|
+
if (named !== null) {
|
|
912
|
+
views.set(named, t9.identifier(named));
|
|
913
|
+
}
|
|
914
|
+
fn = fn.getFunctionParent();
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
program.traverse({
|
|
918
|
+
JSXElement: record,
|
|
919
|
+
JSXFragment: record
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function moduleLevelName(fn) {
|
|
923
|
+
if (fn.isFunctionDeclaration()) {
|
|
924
|
+
const id = fn.node.id;
|
|
925
|
+
if (id === null || id === void 0) {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
return t9.isProgram(fn.parentPath.scope.block) ? id.name : null;
|
|
929
|
+
}
|
|
930
|
+
const declarator = fn.parentPath;
|
|
931
|
+
if (!declarator.isVariableDeclarator() || !t9.isIdentifier(declarator.node.id)) {
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
return t9.isProgram(declarator.scope.block) ? declarator.node.id.name : null;
|
|
935
|
+
}
|
|
936
|
+
function isLocalView(path, state) {
|
|
937
|
+
const name = path.node.openingElement.name;
|
|
938
|
+
if (!t9.isJSXIdentifier(name)) {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
const binding = path.scope.getBinding(name.name);
|
|
942
|
+
if (binding === void 0 || binding.kind === "module" || binding.constantViolations.length > 0) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
if (binding.path.isFunctionDeclaration()) {
|
|
946
|
+
return state.firsthand.viewNodes.has(binding.path.node);
|
|
947
|
+
}
|
|
948
|
+
if (!binding.path.isVariableDeclarator()) {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
const init = binding.path.node.init;
|
|
952
|
+
if (!t9.isArrowFunctionExpression(init) && !t9.isFunctionExpression(init)) {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
return state.firsthand.viewNodes.has(init);
|
|
956
|
+
}
|
|
957
|
+
function collectRuns(program, state) {
|
|
958
|
+
program.traverse({
|
|
959
|
+
CallExpression(call) {
|
|
960
|
+
giveStores(call, state);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
function giveStores(call, state) {
|
|
965
|
+
if (!isComponentCall(call)) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const setup = call.get("arguments.0");
|
|
969
|
+
if (!setup.isArrowFunctionExpression() && !setup.isFunctionExpression()) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const found = returnedFunctions(setup);
|
|
973
|
+
if (found.length === 0) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
setup.ensureBlock();
|
|
977
|
+
const block = setup.get("body");
|
|
978
|
+
for (const run of found) {
|
|
979
|
+
const store = setup.scope.generateUidIdentifier("store");
|
|
980
|
+
block.unshiftContainer(
|
|
981
|
+
"body",
|
|
982
|
+
t9.variableDeclaration("const", [
|
|
983
|
+
t9.variableDeclarator(
|
|
984
|
+
store,
|
|
985
|
+
t9.callExpression(runtime(state, "store"), [t9.stringLiteral(declaredName(call))])
|
|
986
|
+
)
|
|
987
|
+
])
|
|
988
|
+
);
|
|
989
|
+
let index = 0;
|
|
990
|
+
state.firsthand.runs.set(run.node, { node: run.node, store, next: () => index++ });
|
|
991
|
+
closeRun(run, store, state);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function returnedFunctions(setup) {
|
|
995
|
+
const found = [];
|
|
996
|
+
const body = setup.get("body");
|
|
997
|
+
if (!body.isBlockStatement()) {
|
|
998
|
+
if (body.isArrowFunctionExpression() || body.isFunctionExpression()) {
|
|
999
|
+
found.push(body);
|
|
1000
|
+
}
|
|
1001
|
+
return found;
|
|
1002
|
+
}
|
|
1003
|
+
body.traverse({
|
|
1004
|
+
// A handler or a callback returns its own things; only what the setup
|
|
1005
|
+
// itself hands back is the view.
|
|
1006
|
+
Function(nested) {
|
|
1007
|
+
nested.skip();
|
|
1008
|
+
},
|
|
1009
|
+
ReturnStatement(statement) {
|
|
1010
|
+
const argument = statement.get("argument");
|
|
1011
|
+
if (argument.isArrowFunctionExpression() || argument.isFunctionExpression()) {
|
|
1012
|
+
found.push(argument);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
return found;
|
|
1017
|
+
}
|
|
1018
|
+
function closeRun(run, store, state) {
|
|
1019
|
+
const body = run.get("body");
|
|
1020
|
+
if (!body.isBlockStatement()) {
|
|
1021
|
+
body.replaceWith(
|
|
1022
|
+
t9.callExpression(runtime(state, "ran"), [t9.cloneNode(store), body.node])
|
|
1023
|
+
);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const returns = [];
|
|
1027
|
+
body.traverse({
|
|
1028
|
+
Function(nested) {
|
|
1029
|
+
nested.skip();
|
|
1030
|
+
},
|
|
1031
|
+
ReturnStatement(statement) {
|
|
1032
|
+
returns.push(statement);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
for (const statement of returns) {
|
|
1036
|
+
statement.node.argument = t9.callExpression(runtime(state, "ran"), [
|
|
1037
|
+
t9.cloneNode(store),
|
|
1038
|
+
statement.node.argument ?? t9.identifier("undefined")
|
|
1039
|
+
]);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function isComponentCall(path) {
|
|
1043
|
+
if (!t9.isIdentifier(path.node.callee, { name: "component" })) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
const binding = path.scope.getBinding("component");
|
|
1047
|
+
return binding !== void 0 && isFirsthandImport(binding);
|
|
1048
|
+
}
|
|
1049
|
+
function enclosingRun(path, state) {
|
|
1050
|
+
let fn = path.getFunctionParent();
|
|
1051
|
+
while (fn !== null && GENERATED in fn.node) {
|
|
1052
|
+
fn = fn.getFunctionParent();
|
|
1053
|
+
}
|
|
1054
|
+
if (fn === null) {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
return state.firsthand.runs.get(fn.node) ?? null;
|
|
1058
|
+
}
|
|
1059
|
+
function dependsOnRun(node, run, at) {
|
|
1060
|
+
if (run === null) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
const names = /* @__PURE__ */ new Set();
|
|
1064
|
+
collectNames(node, names);
|
|
1065
|
+
for (const name of names) {
|
|
1066
|
+
const binding = at.scope.getBinding(name);
|
|
1067
|
+
if (binding === void 0) {
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
if (binding.scope.block === run.node || binding.path.findParent((parent) => parent.node === run.node) !== null) {
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
function collectNames(node, into) {
|
|
1077
|
+
if (t9.isIdentifier(node)) {
|
|
1078
|
+
into.add(node.name);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
for (const key of t9.VISITOR_KEYS[node.type]) {
|
|
1082
|
+
if (isSpelling(node, key)) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const value = node[key];
|
|
1086
|
+
for (const child of Array.isArray(value) ? value : [value]) {
|
|
1087
|
+
if (typeof child === "object" && child !== null) {
|
|
1088
|
+
collectNames(child, into);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function isSpelling(node, key) {
|
|
1094
|
+
if (t9.isMemberExpression(node)) {
|
|
1095
|
+
return !node.computed && key === "property";
|
|
1096
|
+
}
|
|
1097
|
+
if (t9.isObjectProperty(node) || t9.isObjectMethod(node)) {
|
|
1098
|
+
return !node.computed && key === "key";
|
|
1099
|
+
}
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
function canRetain(node, build) {
|
|
1103
|
+
for (const attribute of node.openingElement.attributes) {
|
|
1104
|
+
if (madeOncePerRun(attribute, build)) {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
for (const child of node.children) {
|
|
1109
|
+
if (t9.isJSXElement(child)) {
|
|
1110
|
+
if (!isComponentTag(child) && !canRetain(child, build)) {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (t9.isJSXExpressionContainer(child) && !t9.isJSXEmptyExpression(child.expression) && isKeyedList(child.expression) && listHoldsRunValue(child.expression, build)) {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
function listHoldsRunValue(call, build) {
|
|
1122
|
+
return call.arguments.slice(1).some((argument) => dependsOnRun(argument, build.run, build.at));
|
|
1123
|
+
}
|
|
1124
|
+
function madeOncePerRun(attribute, build) {
|
|
1125
|
+
if (t9.isJSXSpreadAttribute(attribute)) {
|
|
1126
|
+
return dependsOnRun(attribute.argument, build.run, build.at);
|
|
1127
|
+
}
|
|
1128
|
+
const value = attribute.value;
|
|
1129
|
+
return attributeName(attribute) === "ref" && t9.isJSXExpressionContainer(value) && !t9.isJSXEmptyExpression(value.expression) && dependsOnRun(value.expression, build.run, build.at);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// packages/compiler/src/site.ts
|
|
1133
|
+
import * as t13 from "@babel/types";
|
|
1134
|
+
|
|
1135
|
+
// packages/compiler/src/template.ts
|
|
1136
|
+
import * as t12 from "@babel/types";
|
|
1137
|
+
|
|
1138
|
+
// packages/compiler/src/attributes.ts
|
|
1139
|
+
import * as t11 from "@babel/types";
|
|
1140
|
+
|
|
1141
|
+
// packages/compiler/src/guarded.ts
|
|
1142
|
+
import * as t10 from "@babel/types";
|
|
1143
|
+
function guardedWrite(build, state, value, write) {
|
|
1144
|
+
const run = build.run;
|
|
1145
|
+
const slot = build.name("_w$");
|
|
1146
|
+
const held = build.name("_x$");
|
|
1147
|
+
const last = () => t10.memberExpression(t10.cloneNode(slot), t10.identifier("last"));
|
|
1148
|
+
pushEach(
|
|
1149
|
+
build,
|
|
1150
|
+
t10.variableDeclaration("const", [
|
|
1151
|
+
t10.variableDeclarator(
|
|
1152
|
+
slot,
|
|
1153
|
+
t10.callExpression(runtime(state, "site"), [
|
|
1154
|
+
t10.cloneNode(run.store),
|
|
1155
|
+
t10.numericLiteral(run.next())
|
|
1156
|
+
])
|
|
1157
|
+
),
|
|
1158
|
+
t10.variableDeclarator(held, value)
|
|
1159
|
+
])
|
|
1160
|
+
);
|
|
1161
|
+
pushEach(
|
|
1162
|
+
build,
|
|
1163
|
+
t10.ifStatement(
|
|
1164
|
+
t10.binaryExpression("!==", last(), t10.cloneNode(held)),
|
|
1165
|
+
t10.blockStatement([
|
|
1166
|
+
expressionStatement2(t10.assignmentExpression("=", last(), t10.cloneNode(held))),
|
|
1167
|
+
expressionStatement2(t10.callExpression(runtime(state, "wrote"), [t10.cloneNode(run.store)])),
|
|
1168
|
+
expressionStatement2(write(t10.cloneNode(held)))
|
|
1169
|
+
])
|
|
1170
|
+
)
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// packages/compiler/src/attributes.ts
|
|
1175
|
+
function emitAttribute(attribute, host) {
|
|
1176
|
+
if (t11.isJSXSpreadAttribute(attribute)) {
|
|
1177
|
+
host.deferred.push(() => {
|
|
1178
|
+
emitSpread(attribute.argument, host);
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const name = attributeName(attribute);
|
|
1183
|
+
const value = attributeValue(attribute);
|
|
1184
|
+
if (name === "ref") {
|
|
1185
|
+
host.deferred.push(() => {
|
|
1186
|
+
emitRef(value, host);
|
|
1187
|
+
});
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (isEventName(name)) {
|
|
1191
|
+
host.deferred.push(() => {
|
|
1192
|
+
emitListener(name, value, host);
|
|
1193
|
+
});
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (value !== null && isStaticValue(value) && canInlineAttribute(name)) {
|
|
1197
|
+
const literal = staticLiteral(value);
|
|
1198
|
+
if (literal !== null) {
|
|
1199
|
+
host.build.html.push(` ${name}="${escapeAttribute(literal)}"`);
|
|
1200
|
+
}
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (value === null) {
|
|
1204
|
+
host.build.html.push(` ${name}=""`);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
host.deferred.push(() => {
|
|
1208
|
+
emitDynamic(name, value, host);
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
function emitSpread(argument, host) {
|
|
1212
|
+
const { build, self, state } = host;
|
|
1213
|
+
pushOnce(
|
|
1214
|
+
build,
|
|
1215
|
+
expressionStatement2(
|
|
1216
|
+
t11.callExpression(runtime(state, "bind"), [
|
|
1217
|
+
t11.arrowFunctionExpression(
|
|
1218
|
+
[],
|
|
1219
|
+
t11.callExpression(runtime(state, "spread"), [t11.cloneNode(self), argument])
|
|
1220
|
+
)
|
|
1221
|
+
])
|
|
1222
|
+
)
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
function emitRef(value, host) {
|
|
1226
|
+
pushOnce(host.build, expressionStatement2(t11.callExpression(value, [t11.cloneNode(host.self)])));
|
|
1227
|
+
}
|
|
1228
|
+
function emitListener(name, value, host) {
|
|
1229
|
+
const { build, self, state } = host;
|
|
1230
|
+
const parts = name.split(":");
|
|
1231
|
+
const literal = parts[0] === "on";
|
|
1232
|
+
const type = literal ? parts[1] : eventName(parts[0]);
|
|
1233
|
+
const modifier = parts[literal ? 2 : 1];
|
|
1234
|
+
const args = [t11.cloneNode(self), t11.stringLiteral(type), value];
|
|
1235
|
+
if (modifier === "native") {
|
|
1236
|
+
args.push(t11.booleanLiteral(true));
|
|
1237
|
+
} else if (modifier !== void 0) {
|
|
1238
|
+
args.push(
|
|
1239
|
+
t11.objectExpression([t11.objectProperty(propertyKey(modifier), t11.booleanLiteral(true))])
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
const attach = expressionStatement2(t11.callExpression(runtime(state, "on"), args));
|
|
1243
|
+
if (dependsOnRun(value, build.run, build.at)) {
|
|
1244
|
+
pushEach(build, attach);
|
|
1245
|
+
} else {
|
|
1246
|
+
pushOnce(build, attach);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
function emitDynamic(name, value, host) {
|
|
1250
|
+
const { build, state } = host;
|
|
1251
|
+
if (dependsOnRun(value, build.run, build.at)) {
|
|
1252
|
+
guardedWrite(build, state, value, (held) => dynamicAttributeCall(name, held, host));
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
pushOnce(
|
|
1256
|
+
build,
|
|
1257
|
+
expressionStatement2(
|
|
1258
|
+
t11.callExpression(runtime(state, "bind"), [
|
|
1259
|
+
located(t11.arrowFunctionExpression([], dynamicAttributeCall(name, value, host)), value)
|
|
1260
|
+
])
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
function dynamicAttributeCall(name, value, host) {
|
|
1265
|
+
const { self, state, tag } = host;
|
|
1266
|
+
const call = (runtimeName, attribute) => t11.callExpression(runtime(state, runtimeName), [
|
|
1267
|
+
t11.cloneNode(self),
|
|
1268
|
+
t11.stringLiteral(attribute),
|
|
1269
|
+
value
|
|
1270
|
+
]);
|
|
1271
|
+
if (name.startsWith("prop:")) {
|
|
1272
|
+
return call("setProperty", name.slice(5));
|
|
1273
|
+
}
|
|
1274
|
+
if (name.startsWith("attr:")) {
|
|
1275
|
+
return call("setAttribute", name.slice(5));
|
|
1276
|
+
}
|
|
1277
|
+
if (name === "class" || name === "className" || name === "style") {
|
|
1278
|
+
return call("applyProp", name);
|
|
1279
|
+
}
|
|
1280
|
+
if (BOOLEAN_PROPERTIES.has(name)) {
|
|
1281
|
+
return call("setBoolean", name);
|
|
1282
|
+
}
|
|
1283
|
+
if (DOM_PROPERTIES.has(name) && !SVG_ELEMENTS.has(tag)) {
|
|
1284
|
+
return call("setProperty", name);
|
|
1285
|
+
}
|
|
1286
|
+
return call("setAttribute", name);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// packages/compiler/src/template.ts
|
|
1290
|
+
function emitElement(node, build, self, state) {
|
|
1291
|
+
const name = node.openingElement.name;
|
|
1292
|
+
if (!t12.isJSXIdentifier(name)) {
|
|
1293
|
+
const namespaced = name;
|
|
1294
|
+
throw new Error(
|
|
1295
|
+
`Namespaced element names are not supported: <${namespaced.namespace.name}:${namespaced.name.name}>. Write the element without a namespace; SVG children are resolved by the parser.`
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const tag = name.name;
|
|
1299
|
+
build.html.push(`<${tag}`);
|
|
1300
|
+
const host = { build, self, state, deferred: [], tag };
|
|
1301
|
+
for (const attribute of node.openingElement.attributes) {
|
|
1302
|
+
emitAttribute(attribute, host);
|
|
1303
|
+
}
|
|
1304
|
+
build.html.push(">");
|
|
1305
|
+
for (const emit of host.deferred) {
|
|
1306
|
+
emit();
|
|
1307
|
+
}
|
|
1308
|
+
emitChildren(node.children, host);
|
|
1309
|
+
if (!VOID_ELEMENTS.has(tag)) {
|
|
1310
|
+
build.html.push(`</${tag}>`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function emitChildren(children, host) {
|
|
1314
|
+
const entries = planChildren(children);
|
|
1315
|
+
const cursor = { previous: null, previousIndex: 0, index: 0 };
|
|
1316
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1317
|
+
const entry = entries[i];
|
|
1318
|
+
if (entry.kind === "text") {
|
|
1319
|
+
host.build.html.push(escapeText(entry.text));
|
|
1320
|
+
} else if (entry.kind === "element") {
|
|
1321
|
+
const element = entry.element;
|
|
1322
|
+
const self = needsReference(element) ? reference(cursor, host) : t12.identifier("_unused$");
|
|
1323
|
+
emitElement(element, host.build, self, host.state);
|
|
1324
|
+
} else {
|
|
1325
|
+
emitChildPart(entry, i === entries.length - 1, cursor, host);
|
|
1326
|
+
}
|
|
1327
|
+
cursor.index++;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function reference(cursor, host) {
|
|
1331
|
+
const id = host.build.next();
|
|
1332
|
+
host.build.statements.push(
|
|
1333
|
+
t12.variableDeclaration("const", [t12.variableDeclarator(id, navigate(cursor, host))])
|
|
1334
|
+
);
|
|
1335
|
+
cursor.previous = id;
|
|
1336
|
+
cursor.previousIndex = cursor.index;
|
|
1337
|
+
return id;
|
|
1338
|
+
}
|
|
1339
|
+
function emitChildPart(entry, isLast, cursor, host) {
|
|
1340
|
+
const { build, self, state } = host;
|
|
1341
|
+
const expression = entry.expression;
|
|
1342
|
+
let marker = null;
|
|
1343
|
+
if (!isLast) {
|
|
1344
|
+
build.html.push("<!>");
|
|
1345
|
+
marker = t12.cloneNode(reference(cursor, host));
|
|
1346
|
+
}
|
|
1347
|
+
if (entry.kind !== "list" && dependsOnRun(expression, build.run, build.at)) {
|
|
1348
|
+
const run = build.run;
|
|
1349
|
+
pushEach(
|
|
1350
|
+
build,
|
|
1351
|
+
expressionStatement2(
|
|
1352
|
+
t12.callExpression(runtime(state, "writeChild"), [
|
|
1353
|
+
t12.cloneNode(run.store),
|
|
1354
|
+
t12.numericLiteral(run.next()),
|
|
1355
|
+
t12.cloneNode(self),
|
|
1356
|
+
marker ?? t12.nullLiteral(),
|
|
1357
|
+
expression
|
|
1358
|
+
])
|
|
1359
|
+
)
|
|
1360
|
+
);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const args = [
|
|
1364
|
+
t12.cloneNode(self),
|
|
1365
|
+
// A list part is already a thunk that owns its rows; wrapping it would
|
|
1366
|
+
// rebuild the whole list on every evaluation.
|
|
1367
|
+
entry.kind === "list" ? listFedByRun(expression, host) : thunk(expression)
|
|
1368
|
+
];
|
|
1369
|
+
if (marker !== null) {
|
|
1370
|
+
args.push(marker);
|
|
1371
|
+
}
|
|
1372
|
+
pushOnce(build, expressionStatement2(t12.callExpression(runtime(state, "insert"), args)));
|
|
1373
|
+
}
|
|
1374
|
+
function listFedByRun(call, host) {
|
|
1375
|
+
const { build, state } = host;
|
|
1376
|
+
const source = call.arguments[0];
|
|
1377
|
+
if (build.run === null || !t12.isExpression(source) || !dependsOnRun(source, build.run, build.at)) {
|
|
1378
|
+
return call;
|
|
1379
|
+
}
|
|
1380
|
+
const run = build.run;
|
|
1381
|
+
const holder = build.name("_data$");
|
|
1382
|
+
build.statements.push(
|
|
1383
|
+
t12.variableDeclaration("const", [
|
|
1384
|
+
t12.variableDeclarator(
|
|
1385
|
+
holder,
|
|
1386
|
+
t12.callExpression(runtime(state, "cell"), [
|
|
1387
|
+
t12.cloneNode(run.store),
|
|
1388
|
+
t12.numericLiteral(run.next()),
|
|
1389
|
+
// What the cell holds is the data itself, not a thunk over it: a
|
|
1390
|
+
// list's source is a value, unlike a child, which is a reading that
|
|
1391
|
+
// belongs to the part displaying it (ADR-0026).
|
|
1392
|
+
//
|
|
1393
|
+
// `rewriteKeyedMaps` always writes the source as `() => <data>`, so
|
|
1394
|
+
// the body is the data and there is no other shape to answer for.
|
|
1395
|
+
source.body
|
|
1396
|
+
])
|
|
1397
|
+
)
|
|
1398
|
+
])
|
|
1399
|
+
);
|
|
1400
|
+
call.arguments[0] = t12.arrowFunctionExpression(
|
|
1401
|
+
[],
|
|
1402
|
+
t12.memberExpression(t12.cloneNode(holder), t12.identifier("value"))
|
|
1403
|
+
);
|
|
1404
|
+
return call;
|
|
1405
|
+
}
|
|
1406
|
+
function needsReference(element) {
|
|
1407
|
+
if (element.openingElement.attributes.some(attributeNeedsReference)) {
|
|
1408
|
+
return true;
|
|
1409
|
+
}
|
|
1410
|
+
for (const entry of planChildren(element.children)) {
|
|
1411
|
+
if (entry.kind === "dynamic" || entry.kind === "list") {
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
if (entry.kind === "element" && needsReference(entry.element)) {
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
function attributeNeedsReference(attribute) {
|
|
1421
|
+
if (t12.isJSXSpreadAttribute(attribute)) {
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
const name = attributeName(attribute);
|
|
1425
|
+
if (name === "ref" || name.startsWith("on")) {
|
|
1426
|
+
return true;
|
|
1427
|
+
}
|
|
1428
|
+
const value = attributeValue(attribute);
|
|
1429
|
+
return value !== null && (!isStaticValue(value) || !canInlineAttribute(name));
|
|
1430
|
+
}
|
|
1431
|
+
function navigate(cursor, host) {
|
|
1432
|
+
const { self, state } = host;
|
|
1433
|
+
const hydratable = state.firsthand.hydratable;
|
|
1434
|
+
const first = hydratable ? runtime(state, "first") : null;
|
|
1435
|
+
const next = hydratable ? runtime(state, "next") : null;
|
|
1436
|
+
let expression;
|
|
1437
|
+
let steps;
|
|
1438
|
+
if (cursor.previous === null) {
|
|
1439
|
+
expression = hydratable ? t12.callExpression(first, [t12.cloneNode(self)]) : t12.memberExpression(t12.cloneNode(self), t12.identifier("firstChild"));
|
|
1440
|
+
steps = cursor.index;
|
|
1441
|
+
} else {
|
|
1442
|
+
expression = t12.cloneNode(cursor.previous);
|
|
1443
|
+
steps = cursor.index - cursor.previousIndex;
|
|
1444
|
+
}
|
|
1445
|
+
for (let i = 0; i < steps; i++) {
|
|
1446
|
+
expression = hydratable ? t12.callExpression(next, [expression]) : t12.memberExpression(expression, t12.identifier("nextSibling"));
|
|
1447
|
+
}
|
|
1448
|
+
return expression;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// packages/compiler/src/site.ts
|
|
1452
|
+
function compileTemplate(path, state) {
|
|
1453
|
+
const build = newBuild(path, state);
|
|
1454
|
+
const root = t13.identifier("_el$");
|
|
1455
|
+
emitElement(path.node, build, root, state);
|
|
1456
|
+
const templateId = registerTemplate(build, state);
|
|
1457
|
+
const body = build.run === null ? freshForm(root, templateId, build) : keptForm(root, templateId, build, state);
|
|
1458
|
+
return t13.callExpression(generated(t13.arrowFunctionExpression([], t13.blockStatement(body))), []);
|
|
1459
|
+
}
|
|
1460
|
+
function newBuild(path, state) {
|
|
1461
|
+
let names = 0;
|
|
1462
|
+
const statements = [];
|
|
1463
|
+
const build = {
|
|
1464
|
+
html: [],
|
|
1465
|
+
statements,
|
|
1466
|
+
once: statements,
|
|
1467
|
+
each: statements,
|
|
1468
|
+
run: null,
|
|
1469
|
+
at: path,
|
|
1470
|
+
next: /* @__PURE__ */ (() => {
|
|
1471
|
+
let n = 0;
|
|
1472
|
+
return () => t13.identifier(`_el$${String(++n)}`);
|
|
1473
|
+
})(),
|
|
1474
|
+
name: (prefix) => t13.identifier(`${prefix}${String(++names)}`)
|
|
1475
|
+
};
|
|
1476
|
+
const run = enclosingRun(path, state);
|
|
1477
|
+
if (run !== null) {
|
|
1478
|
+
build.run = run;
|
|
1479
|
+
if (canRetain(path.node, build)) {
|
|
1480
|
+
build.once = [];
|
|
1481
|
+
build.each = [];
|
|
1482
|
+
} else {
|
|
1483
|
+
build.run = null;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return build;
|
|
1487
|
+
}
|
|
1488
|
+
function registerTemplate(build, state) {
|
|
1489
|
+
const templateId = t13.identifier(`_tmpl$${String(++state.firsthand.counter)}`);
|
|
1490
|
+
state.firsthand.templates.push(
|
|
1491
|
+
t13.variableDeclarator(
|
|
1492
|
+
templateId,
|
|
1493
|
+
t13.addComment(
|
|
1494
|
+
t13.callExpression(runtime(state, "template"), [t13.stringLiteral(build.html.join(""))]),
|
|
1495
|
+
"leading",
|
|
1496
|
+
"#__PURE__"
|
|
1497
|
+
)
|
|
1498
|
+
)
|
|
1499
|
+
);
|
|
1500
|
+
return templateId;
|
|
1501
|
+
}
|
|
1502
|
+
function freshForm(root, templateId, build) {
|
|
1503
|
+
return [
|
|
1504
|
+
t13.variableDeclaration("const", [
|
|
1505
|
+
t13.variableDeclarator(root, t13.callExpression(t13.cloneNode(templateId), []))
|
|
1506
|
+
]),
|
|
1507
|
+
...build.statements,
|
|
1508
|
+
t13.returnStatement(t13.cloneNode(root))
|
|
1509
|
+
];
|
|
1510
|
+
}
|
|
1511
|
+
function keptForm(root, templateId, build, state) {
|
|
1512
|
+
const kept = {
|
|
1513
|
+
root,
|
|
1514
|
+
templateId,
|
|
1515
|
+
slot: build.name("_site$"),
|
|
1516
|
+
fresh: build.name("_new$"),
|
|
1517
|
+
held: build.name("_own$"),
|
|
1518
|
+
owner: build.run
|
|
1519
|
+
};
|
|
1520
|
+
return [
|
|
1521
|
+
...keptPrologue(kept, state),
|
|
1522
|
+
...build.statements,
|
|
1523
|
+
t13.ifStatement(
|
|
1524
|
+
t13.cloneNode(kept.fresh),
|
|
1525
|
+
t13.blockStatement([
|
|
1526
|
+
...build.once,
|
|
1527
|
+
expressionStatement2(t13.callExpression(runtime(state, "close"), [t13.cloneNode(kept.held)]))
|
|
1528
|
+
])
|
|
1529
|
+
),
|
|
1530
|
+
...build.each,
|
|
1531
|
+
t13.returnStatement(t13.cloneNode(kept.root))
|
|
1532
|
+
];
|
|
1533
|
+
}
|
|
1534
|
+
function keptPrologue(kept, state) {
|
|
1535
|
+
const { root, slot, fresh, held, owner, templateId } = kept;
|
|
1536
|
+
return [
|
|
1537
|
+
t13.variableDeclaration("const", [
|
|
1538
|
+
t13.variableDeclarator(
|
|
1539
|
+
slot,
|
|
1540
|
+
t13.callExpression(runtime(state, "site"), [
|
|
1541
|
+
t13.cloneNode(owner.store),
|
|
1542
|
+
t13.numericLiteral(owner.next())
|
|
1543
|
+
])
|
|
1544
|
+
)
|
|
1545
|
+
]),
|
|
1546
|
+
t13.variableDeclaration("let", [
|
|
1547
|
+
t13.variableDeclarator(root, t13.memberExpression(t13.cloneNode(slot), t13.identifier("node")))
|
|
1548
|
+
]),
|
|
1549
|
+
t13.variableDeclaration("const", [
|
|
1550
|
+
t13.variableDeclarator(
|
|
1551
|
+
fresh,
|
|
1552
|
+
t13.binaryExpression("===", t13.cloneNode(root), t13.identifier("undefined"))
|
|
1553
|
+
)
|
|
1554
|
+
]),
|
|
1555
|
+
t13.variableDeclaration("let", [t13.variableDeclarator(held)]),
|
|
1556
|
+
t13.ifStatement(
|
|
1557
|
+
t13.cloneNode(fresh),
|
|
1558
|
+
t13.blockStatement([
|
|
1559
|
+
// What this site makes belongs to the instance. A run's own scope is
|
|
1560
|
+
// cleared before it runs again, and a part left there would be disposed
|
|
1561
|
+
// by the very next run.
|
|
1562
|
+
expressionStatement2(
|
|
1563
|
+
t13.assignmentExpression(
|
|
1564
|
+
"=",
|
|
1565
|
+
t13.cloneNode(held),
|
|
1566
|
+
t13.callExpression(runtime(state, "open"), [t13.cloneNode(owner.store), t13.cloneNode(slot)])
|
|
1567
|
+
)
|
|
1568
|
+
),
|
|
1569
|
+
expressionStatement2(
|
|
1570
|
+
t13.assignmentExpression(
|
|
1571
|
+
"=",
|
|
1572
|
+
t13.cloneNode(root),
|
|
1573
|
+
t13.assignmentExpression(
|
|
1574
|
+
"=",
|
|
1575
|
+
t13.memberExpression(t13.cloneNode(slot), t13.identifier("node")),
|
|
1576
|
+
t13.callExpression(t13.cloneNode(templateId), [])
|
|
1577
|
+
)
|
|
1578
|
+
)
|
|
1579
|
+
)
|
|
1580
|
+
])
|
|
1581
|
+
)
|
|
1582
|
+
];
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// packages/compiler/src/jsx.ts
|
|
1586
|
+
function compileNode(path, state) {
|
|
1587
|
+
if (path.isJSXFragment()) {
|
|
1588
|
+
return t14.arrayExpression(compileChildren(path.node.children, state));
|
|
1589
|
+
}
|
|
1590
|
+
const node = path.node;
|
|
1591
|
+
if (isComponentTag(node)) {
|
|
1592
|
+
return compileComponent(path, state);
|
|
1593
|
+
}
|
|
1594
|
+
return state.firsthand.ssr ? compileMarkup(path, state) : compileTemplate(path, state);
|
|
1595
|
+
}
|
|
1596
|
+
function compileComponent(path, state) {
|
|
1597
|
+
const node = path.node;
|
|
1598
|
+
const run = enclosingRun(path, state);
|
|
1599
|
+
const props = componentProps(path, run, state);
|
|
1600
|
+
const feed = (value) => throughCell(value, run, props, state);
|
|
1601
|
+
const children = compileChildren(node.children, state, {
|
|
1602
|
+
depends: (value) => dependsOnRun(value, run, path),
|
|
1603
|
+
cell: (value) => t14.callExpression(feed(t14.arrowFunctionExpression([], value)), []),
|
|
1604
|
+
value: feed
|
|
1605
|
+
});
|
|
1606
|
+
if (children.length > 0) {
|
|
1607
|
+
const returned = children.length === 1 ? children[0] : t14.arrayExpression(children);
|
|
1608
|
+
props.properties.push(
|
|
1609
|
+
t14.objectMethod(
|
|
1610
|
+
"get",
|
|
1611
|
+
t14.identifier("children"),
|
|
1612
|
+
[],
|
|
1613
|
+
t14.blockStatement([t14.returnStatement(returned)])
|
|
1614
|
+
)
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
const tag = tagExpression(node.openingElement.name);
|
|
1618
|
+
const local = isLocalView(path, state);
|
|
1619
|
+
const make = local ? t14.callExpression(tag, [t14.objectExpression(props.properties)]) : t14.callExpression(runtime(state, "createComponent"), [
|
|
1620
|
+
tag,
|
|
1621
|
+
t14.objectExpression(props.properties)
|
|
1622
|
+
]);
|
|
1623
|
+
if (run !== null) {
|
|
1624
|
+
return keptChild(state, run, make, props.cells);
|
|
1625
|
+
}
|
|
1626
|
+
return local ? asScope(make, path, state) : make;
|
|
1627
|
+
}
|
|
1628
|
+
function asScope(make, path, state) {
|
|
1629
|
+
if (state.firsthand.ssr) {
|
|
1630
|
+
return make;
|
|
1631
|
+
}
|
|
1632
|
+
const parent = path.parentPath;
|
|
1633
|
+
if (parent.isArrowFunctionExpression() && parent.node.body === path.node && CHILD_THUNK in parent.node) {
|
|
1634
|
+
return make;
|
|
1635
|
+
}
|
|
1636
|
+
return t14.callExpression(runtime(state, "part"), [t14.arrowFunctionExpression([], make)]);
|
|
1637
|
+
}
|
|
1638
|
+
function componentProps(path, run, state) {
|
|
1639
|
+
const props = { properties: [], cells: [] };
|
|
1640
|
+
for (const attribute of path.node.openingElement.attributes) {
|
|
1641
|
+
if (t14.isJSXSpreadAttribute(attribute)) {
|
|
1642
|
+
props.properties.push(t14.spreadElement(attribute.argument));
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
const name = attributeName(attribute);
|
|
1646
|
+
const value = attributeValue(attribute);
|
|
1647
|
+
if (state.firsthand.ssr && name === "key") {
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
if (value === null || isStaticValue(value)) {
|
|
1651
|
+
props.properties.push(t14.objectProperty(propertyKey(name), value ?? t14.booleanLiteral(true)));
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
const held = dependsOnRun(value, run, path) ? throughCell(value, run, props, state) : value;
|
|
1655
|
+
props.properties.push(dynamicProp(name, value, held, state));
|
|
1656
|
+
}
|
|
1657
|
+
return props;
|
|
1658
|
+
}
|
|
1659
|
+
function dynamicProp(name, value, held, state) {
|
|
1660
|
+
if (state.firsthand.ssr && isPure(value)) {
|
|
1661
|
+
return t14.objectProperty(propertyKey(name), held);
|
|
1662
|
+
}
|
|
1663
|
+
const read = t14.returnStatement(held);
|
|
1664
|
+
takePosition(read, value);
|
|
1665
|
+
return t14.objectMethod("get", propertyKey(name), [], t14.blockStatement([read]));
|
|
1666
|
+
}
|
|
1667
|
+
function throughCell(value, run, props, state) {
|
|
1668
|
+
const holder = t14.identifier(`_cell$${String(run.next())}`);
|
|
1669
|
+
props.cells.push(
|
|
1670
|
+
t14.variableDeclaration("const", [
|
|
1671
|
+
t14.variableDeclarator(
|
|
1672
|
+
holder,
|
|
1673
|
+
t14.callExpression(runtime(state, "cell"), [
|
|
1674
|
+
t14.cloneNode(run.store),
|
|
1675
|
+
t14.numericLiteral(run.next()),
|
|
1676
|
+
value
|
|
1677
|
+
])
|
|
1678
|
+
)
|
|
1679
|
+
])
|
|
1680
|
+
);
|
|
1681
|
+
return t14.memberExpression(t14.cloneNode(holder), t14.identifier("value"));
|
|
1682
|
+
}
|
|
1683
|
+
function keptChild(state, run, make, cells) {
|
|
1684
|
+
const slot = t14.identifier(`_kept$${String(run.next())}`);
|
|
1685
|
+
const held = t14.identifier(`_own$${String(run.next())}`);
|
|
1686
|
+
const made = () => t14.memberExpression(t14.cloneNode(slot), t14.identifier("last"));
|
|
1687
|
+
const body = [
|
|
1688
|
+
...cells,
|
|
1689
|
+
t14.variableDeclaration("const", [
|
|
1690
|
+
t14.variableDeclarator(
|
|
1691
|
+
slot,
|
|
1692
|
+
t14.callExpression(runtime(state, "site"), [
|
|
1693
|
+
t14.cloneNode(run.store),
|
|
1694
|
+
t14.numericLiteral(run.next())
|
|
1695
|
+
])
|
|
1696
|
+
)
|
|
1697
|
+
]),
|
|
1698
|
+
t14.ifStatement(
|
|
1699
|
+
t14.binaryExpression("===", made(), t14.identifier("undefined")),
|
|
1700
|
+
t14.blockStatement([
|
|
1701
|
+
t14.variableDeclaration("const", [
|
|
1702
|
+
t14.variableDeclarator(
|
|
1703
|
+
held,
|
|
1704
|
+
t14.callExpression(runtime(state, "open"), [t14.cloneNode(run.store), t14.cloneNode(slot)])
|
|
1705
|
+
)
|
|
1706
|
+
]),
|
|
1707
|
+
expressionStatement2(
|
|
1708
|
+
t14.assignmentExpression(
|
|
1709
|
+
"=",
|
|
1710
|
+
made(),
|
|
1711
|
+
t14.callExpression(runtime(state, "part"), [t14.arrowFunctionExpression([], make)])
|
|
1712
|
+
)
|
|
1713
|
+
),
|
|
1714
|
+
expressionStatement2(t14.callExpression(runtime(state, "close"), [t14.cloneNode(held)]))
|
|
1715
|
+
])
|
|
1716
|
+
),
|
|
1717
|
+
t14.returnStatement(made())
|
|
1718
|
+
];
|
|
1719
|
+
return t14.callExpression(generated(t14.arrowFunctionExpression([], t14.blockStatement(body))), []);
|
|
1720
|
+
}
|
|
1721
|
+
function compileChildren(children, state, kept) {
|
|
1722
|
+
const result = [];
|
|
1723
|
+
for (const entry of planChildren(children)) {
|
|
1724
|
+
if (entry.kind === "text") {
|
|
1725
|
+
result.push(t14.stringLiteral(entry.text));
|
|
1726
|
+
} else if (entry.kind === "element") {
|
|
1727
|
+
result.push(entry.element);
|
|
1728
|
+
} else if (state.firsthand.ssr) {
|
|
1729
|
+
result.push(entry.expression);
|
|
1730
|
+
} else if (entry.kind === "list") {
|
|
1731
|
+
const list = entry.expression;
|
|
1732
|
+
keepReading(list, kept);
|
|
1733
|
+
result.push(t14.callExpression(runtime(state, "part"), [list]));
|
|
1734
|
+
} else {
|
|
1735
|
+
const value = entry.expression;
|
|
1736
|
+
const read = kept !== void 0 && kept.depends(value) ? kept.cell(value) : value;
|
|
1737
|
+
result.push(t14.callExpression(runtime(state, "part"), [thunk(read)]));
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return result;
|
|
1741
|
+
}
|
|
1742
|
+
function keepReading(list, kept) {
|
|
1743
|
+
const each = list.arguments[0];
|
|
1744
|
+
if (kept === void 0 || !t14.isArrowFunctionExpression(each) || !t14.isExpression(each.body)) {
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
if (!kept.depends(each.body)) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
each.body = kept.value(each.body);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// packages/compiler/src/lists.ts
|
|
1754
|
+
import * as t15 from "@babel/types";
|
|
1755
|
+
function rewriteKeyedMaps(path, state) {
|
|
1756
|
+
path.traverse({
|
|
1757
|
+
CallExpression(call) {
|
|
1758
|
+
const keyed = keyedMap(call);
|
|
1759
|
+
if (keyed !== null) {
|
|
1760
|
+
rewriteOne(call, keyed, state);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
function keyedMap(call) {
|
|
1766
|
+
if (!isChildPosition(call)) {
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
const callee = call.node.callee;
|
|
1770
|
+
if (!t15.isMemberExpression(callee) || callee.computed || !t15.isIdentifier(callee.property, { name: "map" }) || call.node.arguments.length !== 1) {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
const callback = call.get("arguments.0");
|
|
1774
|
+
if (!callback.isArrowFunctionExpression() && !callback.isFunctionExpression()) {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
const [item, index] = callback.node.params;
|
|
1778
|
+
if (!t15.isIdentifier(item) || index !== void 0 && !t15.isIdentifier(index)) {
|
|
1779
|
+
return null;
|
|
1780
|
+
}
|
|
1781
|
+
const root = keyedRoot(callback);
|
|
1782
|
+
if (root === null) {
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1785
|
+
return { source: callee.object, callback, item, index, root };
|
|
1786
|
+
}
|
|
1787
|
+
function rewriteOne(call, keyed, state) {
|
|
1788
|
+
const { callback, item, index, root } = keyed;
|
|
1789
|
+
const keyExpression = t15.cloneNode(root.key);
|
|
1790
|
+
root.element.openingElement.attributes = root.element.openingElement.attributes.filter(
|
|
1791
|
+
(attribute) => attribute !== root.attribute
|
|
1792
|
+
);
|
|
1793
|
+
const itemCell = callback.scope.generateUidIdentifier("item");
|
|
1794
|
+
const indexCell = callback.scope.generateUidIdentifier("index");
|
|
1795
|
+
callback.scope.crawl();
|
|
1796
|
+
liveRead(callback, item.name, itemCell);
|
|
1797
|
+
if (index !== void 0) {
|
|
1798
|
+
liveRead(callback, index.name, indexCell);
|
|
1799
|
+
}
|
|
1800
|
+
callback.node.params = [itemCell, indexCell];
|
|
1801
|
+
const listCall = t15.callExpression(runtime(state, "list"), [
|
|
1802
|
+
t15.arrowFunctionExpression([], keyed.source),
|
|
1803
|
+
t15.arrowFunctionExpression(
|
|
1804
|
+
[t15.cloneNode(item), index ?? callback.scope.generateUidIdentifier("i")],
|
|
1805
|
+
keyExpression
|
|
1806
|
+
),
|
|
1807
|
+
callback.node
|
|
1808
|
+
]);
|
|
1809
|
+
listCall[LIST_CALL] = true;
|
|
1810
|
+
call.replaceWith(listCall);
|
|
1811
|
+
call.skip();
|
|
1812
|
+
}
|
|
1813
|
+
function isChildPosition(call) {
|
|
1814
|
+
let child = call;
|
|
1815
|
+
let parent = child.parentPath;
|
|
1816
|
+
while (!parent.isJSXExpressionContainer()) {
|
|
1817
|
+
const key = child.key;
|
|
1818
|
+
const throughConditional = parent.isConditionalExpression() && (key === "consequent" || key === "alternate");
|
|
1819
|
+
const throughLogical = parent.isLogicalExpression() && key === "right";
|
|
1820
|
+
if (!throughConditional && !throughLogical) {
|
|
1821
|
+
return false;
|
|
1822
|
+
}
|
|
1823
|
+
child = parent;
|
|
1824
|
+
parent = parent.parentPath;
|
|
1825
|
+
}
|
|
1826
|
+
const holder = parent.parentPath;
|
|
1827
|
+
return holder.isJSXElement() || holder.isJSXFragment();
|
|
1828
|
+
}
|
|
1829
|
+
function keyedRoot(callback) {
|
|
1830
|
+
const body = callback.node.body;
|
|
1831
|
+
let element = null;
|
|
1832
|
+
if (t15.isJSXElement(body)) {
|
|
1833
|
+
element = body;
|
|
1834
|
+
} else if (t15.isBlockStatement(body)) {
|
|
1835
|
+
const last = body.body[body.body.length - 1];
|
|
1836
|
+
if (t15.isReturnStatement(last) && t15.isJSXElement(last.argument)) {
|
|
1837
|
+
element = last.argument;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (element === null || !t15.isJSXElement(element)) {
|
|
1841
|
+
return null;
|
|
1842
|
+
}
|
|
1843
|
+
for (const attribute of element.openingElement.attributes) {
|
|
1844
|
+
if (t15.isJSXAttribute(attribute) && t15.isJSXIdentifier(attribute.name, { name: "key" }) && t15.isJSXExpressionContainer(attribute.value)) {
|
|
1845
|
+
return { element, attribute, key: attribute.value.expression };
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
function liveRead(scopePath, name, cell) {
|
|
1851
|
+
const binding = scopePath.scope.getBinding(name);
|
|
1852
|
+
for (const reference2 of binding.referencePaths) {
|
|
1853
|
+
reference2.replaceWith(t15.memberExpression(t15.cloneNode(cell), t15.identifier("value")));
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// packages/compiler/src/plugin.ts
|
|
1858
|
+
function firsthandPlugin(_api, options = {}) {
|
|
1859
|
+
return {
|
|
1860
|
+
name: "firsthand",
|
|
1861
|
+
visitor: {
|
|
1862
|
+
Program: {
|
|
1863
|
+
enter(path, state) {
|
|
1864
|
+
enterProgram(path, state, options);
|
|
1865
|
+
},
|
|
1866
|
+
exit(path, state) {
|
|
1867
|
+
exitProgram(path, state);
|
|
1868
|
+
}
|
|
1869
|
+
},
|
|
1870
|
+
// Babel enters the outermost JSX node first. Its nested elements are
|
|
1871
|
+
// folded into the template and discarded; the nodes that survive (a
|
|
1872
|
+
// component's children, a dynamic expression) are re-visited later, by
|
|
1873
|
+
// which time they are no longer inside JSX.
|
|
1874
|
+
JSXElement(path, state) {
|
|
1875
|
+
if (!state.firsthand.ssr) {
|
|
1876
|
+
rewriteKeyedMaps(path, state);
|
|
1877
|
+
}
|
|
1878
|
+
path.replaceWith(compileNode(path, state));
|
|
1879
|
+
},
|
|
1880
|
+
JSXFragment(path, state) {
|
|
1881
|
+
if (!state.firsthand.ssr) {
|
|
1882
|
+
rewriteKeyedMaps(path, state);
|
|
1883
|
+
}
|
|
1884
|
+
path.replaceWith(compileNode(path, state));
|
|
1885
|
+
},
|
|
1886
|
+
CallExpression(path, state) {
|
|
1887
|
+
annotateComponent(path, state, options);
|
|
1888
|
+
},
|
|
1889
|
+
VariableDeclarator(path, state) {
|
|
1890
|
+
if (options.devtools === true && options.ssr !== true) {
|
|
1891
|
+
nameCell(path, state);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
function enterProgram(path, state, options) {
|
|
1898
|
+
state.firsthand = {
|
|
1899
|
+
imports: /* @__PURE__ */ new Map(),
|
|
1900
|
+
templates: [],
|
|
1901
|
+
counter: 0,
|
|
1902
|
+
moduleId: stableId(options.packageName ?? "app", state.filename ?? "module"),
|
|
1903
|
+
views: /* @__PURE__ */ new Map(),
|
|
1904
|
+
viewNodes: /* @__PURE__ */ new Set(),
|
|
1905
|
+
runs: /* @__PURE__ */ new Map(),
|
|
1906
|
+
ssr: options.ssr === true,
|
|
1907
|
+
hydratable: options.hydratable === true && options.ssr !== true
|
|
1908
|
+
};
|
|
1909
|
+
collectViews(path, state);
|
|
1910
|
+
if (options.ssr !== true) {
|
|
1911
|
+
collectRuns(path, state);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function exitProgram(path, state) {
|
|
1915
|
+
const { imports, templates, views } = state.firsthand;
|
|
1916
|
+
for (const [, local] of views) {
|
|
1917
|
+
path.node.body.push(
|
|
1918
|
+
expressionStatement2(t16.callExpression(runtime(state, "view"), [t16.cloneNode(local)]))
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
if (templates.length > 0) {
|
|
1922
|
+
path.node.body.unshift(t16.variableDeclaration("const", templates));
|
|
1923
|
+
}
|
|
1924
|
+
for (const [source, names] of groupBySource(imports)) {
|
|
1925
|
+
path.node.body.unshift(
|
|
1926
|
+
t16.importDeclaration(
|
|
1927
|
+
names.map(([exported, local]) => t16.importSpecifier(local, t16.identifier(exported))),
|
|
1928
|
+
t16.stringLiteral(source)
|
|
1929
|
+
)
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
function groupBySource(imports) {
|
|
1934
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1935
|
+
for (const [key, local] of imports) {
|
|
1936
|
+
const separator = key.indexOf("#");
|
|
1937
|
+
const source = key.slice(0, separator);
|
|
1938
|
+
const exported = key.slice(separator + 1);
|
|
1939
|
+
const bucket = grouped.get(source);
|
|
1940
|
+
if (bucket === void 0) {
|
|
1941
|
+
grouped.set(source, [[exported, local]]);
|
|
1942
|
+
} else {
|
|
1943
|
+
bucket.push([exported, local]);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
return grouped;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// packages/compiler/src/api.ts
|
|
1950
|
+
import { transformSync } from "@babel/core";
|
|
1951
|
+
import jsxSyntax from "@babel/plugin-syntax-jsx";
|
|
1952
|
+
import typescriptSyntax from "@babel/plugin-syntax-typescript";
|
|
1953
|
+
function compileModule(code, options = {}) {
|
|
1954
|
+
const result = transformSync(code, {
|
|
1955
|
+
...options.filename === void 0 ? {} : {
|
|
1956
|
+
filename: options.filename,
|
|
1957
|
+
// The full path, not the basename Babel would default to. A map
|
|
1958
|
+
// whose `sources` is `comp.tsx` resolves, relative to a module
|
|
1959
|
+
// served at `/src/comp.tsx`, to that very URL — so a debugger ends
|
|
1960
|
+
// up with two files under one address, shows both, and puts the
|
|
1961
|
+
// breakpoint in the wrong one.
|
|
1962
|
+
sourceFileName: options.filename
|
|
1963
|
+
},
|
|
1964
|
+
babelrc: false,
|
|
1965
|
+
configFile: false,
|
|
1966
|
+
sourceMaps: options.sourceMaps === true,
|
|
1967
|
+
plugins: buildPlugins(options)
|
|
1968
|
+
});
|
|
1969
|
+
return { code: result.code, map: result.map };
|
|
1970
|
+
}
|
|
1971
|
+
function transform(code, options = {}) {
|
|
1972
|
+
return compileModule(code, options).code;
|
|
1973
|
+
}
|
|
1974
|
+
function buildPlugins(options) {
|
|
1975
|
+
const plugins = [];
|
|
1976
|
+
if (options.typescript === true) {
|
|
1977
|
+
plugins.push([typescriptSyntax, { isTSX: true }]);
|
|
1978
|
+
}
|
|
1979
|
+
plugins.push(jsxSyntax);
|
|
1980
|
+
plugins.push([
|
|
1981
|
+
firsthandPlugin,
|
|
1982
|
+
{
|
|
1983
|
+
packageName: options.packageName,
|
|
1984
|
+
strictReactivity: options.strictReactivity,
|
|
1985
|
+
devtools: options.devtools,
|
|
1986
|
+
ssr: options.ssr,
|
|
1987
|
+
hydratable: options.hydratable
|
|
1988
|
+
}
|
|
1989
|
+
]);
|
|
1990
|
+
return plugins;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
export {
|
|
1994
|
+
stableId,
|
|
1995
|
+
firsthandPlugin,
|
|
1996
|
+
compileModule,
|
|
1997
|
+
transform
|
|
1998
|
+
};
|