@firsthandjs/compiler 0.7.1 → 0.9.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/{chunk-7DU5USN7.js → chunk-ACKKPAGI.js} +787 -43
- package/dist/index.js +1 -1
- package/dist/transform.d.ts +46 -0
- package/dist/transform.d.ts.map +1 -1
- package/dist/vite.d.ts +27 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +13 -3
- package/package.json +1 -1
|
@@ -86,22 +86,49 @@ function eventName(attribute) {
|
|
|
86
86
|
|
|
87
87
|
// packages/compiler/src/transform.ts
|
|
88
88
|
var RUNTIME = "@firsthandjs/dom/internal";
|
|
89
|
+
var SERVER_RUNTIME = "@firsthandjs/server/internal";
|
|
89
90
|
var CORE = "@firsthandjs/core";
|
|
91
|
+
var CHILD_THUNK = /* @__PURE__ */ Symbol("firsthand.childThunk");
|
|
92
|
+
var GENERATED = /* @__PURE__ */ Symbol("firsthand.generated");
|
|
93
|
+
function generated(arrow) {
|
|
94
|
+
arrow[GENERATED] = true;
|
|
95
|
+
return arrow;
|
|
96
|
+
}
|
|
97
|
+
function pushOnce(build, statement) {
|
|
98
|
+
build.once.push(statement);
|
|
99
|
+
}
|
|
100
|
+
function pushEach(build, statement) {
|
|
101
|
+
build.each.push(statement);
|
|
102
|
+
}
|
|
90
103
|
function firsthandPlugin(_api, options = {}) {
|
|
91
104
|
return {
|
|
92
105
|
name: "firsthand",
|
|
93
106
|
visitor: {
|
|
94
107
|
Program: {
|
|
95
|
-
enter(
|
|
108
|
+
enter(path, state) {
|
|
96
109
|
state.firsthand = {
|
|
97
110
|
imports: /* @__PURE__ */ new Map(),
|
|
98
111
|
templates: [],
|
|
99
112
|
counter: 0,
|
|
100
|
-
moduleId: stableId(options.packageName ?? "app", state.filename ?? "module")
|
|
113
|
+
moduleId: stableId(options.packageName ?? "app", state.filename ?? "module"),
|
|
114
|
+
views: /* @__PURE__ */ new Map(),
|
|
115
|
+
viewNodes: /* @__PURE__ */ new Set(),
|
|
116
|
+
runs: /* @__PURE__ */ new Map(),
|
|
117
|
+
ssr: options.ssr === true,
|
|
118
|
+
hydratable: options.hydratable === true && options.ssr !== true
|
|
101
119
|
};
|
|
120
|
+
collectViews(path, state);
|
|
121
|
+
if (options.ssr !== true) {
|
|
122
|
+
collectRuns(path, state);
|
|
123
|
+
}
|
|
102
124
|
},
|
|
103
125
|
exit(path, state) {
|
|
104
|
-
const { imports, templates } = state.firsthand;
|
|
126
|
+
const { imports, templates, views } = state.firsthand;
|
|
127
|
+
for (const [, local] of views) {
|
|
128
|
+
path.node.body.push(
|
|
129
|
+
expressionStatement2(t.callExpression(runtime(state, "view"), [t.cloneNode(local)]))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
105
132
|
if (templates.length > 0) {
|
|
106
133
|
path.node.body.unshift(t.variableDeclaration("const", templates));
|
|
107
134
|
}
|
|
@@ -120,18 +147,22 @@ function firsthandPlugin(_api, options = {}) {
|
|
|
120
147
|
// component's children, a dynamic expression) are re-visited later, by
|
|
121
148
|
// which time they are no longer inside JSX.
|
|
122
149
|
JSXElement(path, state) {
|
|
123
|
-
|
|
150
|
+
if (!state.firsthand.ssr) {
|
|
151
|
+
rewriteKeyedMaps(path, state);
|
|
152
|
+
}
|
|
124
153
|
path.replaceWith(compileNode(path, state));
|
|
125
154
|
},
|
|
126
155
|
JSXFragment(path, state) {
|
|
127
|
-
|
|
156
|
+
if (!state.firsthand.ssr) {
|
|
157
|
+
rewriteKeyedMaps(path, state);
|
|
158
|
+
}
|
|
128
159
|
path.replaceWith(compileNode(path, state));
|
|
129
160
|
},
|
|
130
161
|
CallExpression(path, state) {
|
|
131
162
|
annotateComponent(path, state, options);
|
|
132
163
|
},
|
|
133
164
|
VariableDeclarator(path, state) {
|
|
134
|
-
if (options.devtools === true) {
|
|
165
|
+
if (options.devtools === true && options.ssr !== true) {
|
|
135
166
|
nameCell(path, state);
|
|
136
167
|
}
|
|
137
168
|
}
|
|
@@ -153,7 +184,10 @@ function groupBySource(imports) {
|
|
|
153
184
|
}
|
|
154
185
|
return grouped;
|
|
155
186
|
}
|
|
156
|
-
function runtime(state, name, source
|
|
187
|
+
function runtime(state, name, source) {
|
|
188
|
+
return runtimeFrom(state, name, source ?? (state.firsthand.ssr ? SERVER_RUNTIME : RUNTIME));
|
|
189
|
+
}
|
|
190
|
+
function runtimeFrom(state, name, source) {
|
|
157
191
|
const key = `${source}#${name}`;
|
|
158
192
|
let local = state.firsthand.imports.get(key);
|
|
159
193
|
if (local === void 0) {
|
|
@@ -181,6 +215,7 @@ function annotateComponent(path, state, options) {
|
|
|
181
215
|
const setupPath = path.get("arguments.0");
|
|
182
216
|
checkKeptReads(setupPath, name);
|
|
183
217
|
checkDecidedOnce(setupPath, name);
|
|
218
|
+
checkRunBody(setupPath, name);
|
|
184
219
|
}
|
|
185
220
|
path.node.arguments = [
|
|
186
221
|
setup,
|
|
@@ -220,9 +255,13 @@ function checkDecidedOnce(setup, name) {
|
|
|
220
255
|
throw at.buildCodeFrameError(
|
|
221
256
|
`${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.
|
|
222
257
|
|
|
223
|
-
|
|
258
|
+
Either put the choice inside the markup, where it is a part:
|
|
224
259
|
|
|
225
|
-
return <>{open.value ? <A /> : <B />}
|
|
260
|
+
return <>{open.value ? <A /> : <B />}</>;
|
|
261
|
+
|
|
262
|
+
or return a render function, which is a reactive scope of its own and may use ordinary control flow:
|
|
263
|
+
|
|
264
|
+
return () => (open.value ? <A /> : <B />);`
|
|
226
265
|
);
|
|
227
266
|
};
|
|
228
267
|
const check = (returned, at) => {
|
|
@@ -274,6 +313,106 @@ Put the choice inside the markup, where it is a part that can run again:
|
|
|
274
313
|
}
|
|
275
314
|
});
|
|
276
315
|
}
|
|
316
|
+
function checkRunBody(setup, name) {
|
|
317
|
+
const runs = [];
|
|
318
|
+
const body = setup.get("body");
|
|
319
|
+
body.traverse({
|
|
320
|
+
Function(nested) {
|
|
321
|
+
nested.skip();
|
|
322
|
+
},
|
|
323
|
+
ReturnStatement(statement) {
|
|
324
|
+
const argument = statement.get("argument");
|
|
325
|
+
if (argument.isArrowFunctionExpression() || argument.isFunctionExpression()) {
|
|
326
|
+
runs.push(argument);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
for (const run of runs) {
|
|
331
|
+
checkNothingPersistent(run, name);
|
|
332
|
+
checkRepeatedMarkup(run, name);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
var PERSISTENT = /* @__PURE__ */ new Map([
|
|
336
|
+
["signal", "a signal"],
|
|
337
|
+
["computed", "a computed"],
|
|
338
|
+
["effect", "an effect"],
|
|
339
|
+
["deepSignal", "a deep signal"],
|
|
340
|
+
["useResource", "a resource"],
|
|
341
|
+
["useAction", "an action"],
|
|
342
|
+
["onCleanup", "a cleanup"],
|
|
343
|
+
["provide", "a context value"]
|
|
344
|
+
]);
|
|
345
|
+
function checkNothingPersistent(run, name) {
|
|
346
|
+
run.traverse({
|
|
347
|
+
Function(nested) {
|
|
348
|
+
nested.skip();
|
|
349
|
+
},
|
|
350
|
+
CallExpression(call) {
|
|
351
|
+
const callee = call.node.callee;
|
|
352
|
+
if (!t.isIdentifier(callee)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const what = PERSISTENT.get(callee.name);
|
|
356
|
+
if (what === void 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const binding = call.scope.getBinding(callee.name);
|
|
360
|
+
if (binding === void 0 || !isFirsthandImport(binding)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
throw call.buildCodeFrameError(
|
|
364
|
+
`${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.
|
|
365
|
+
|
|
366
|
+
Move it into the setup, above the render function. Persistent things are made once, where the setup runs once.`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function checkRepeatedMarkup(run, name) {
|
|
372
|
+
const report = (at) => {
|
|
373
|
+
throw at.buildCodeFrameError(
|
|
374
|
+
`${name}: this markup is written once and appears many times, so where it stands cannot say which of them is which.
|
|
375
|
+
|
|
376
|
+
Give it a key:
|
|
377
|
+
|
|
378
|
+
{rows.map((row) => <Row key={row.id} row={row} />)}`
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
run.traverse({
|
|
382
|
+
Function(nested) {
|
|
383
|
+
nested.skip();
|
|
384
|
+
},
|
|
385
|
+
"ForStatement|ForOfStatement|ForInStatement|WhileStatement|DoWhileStatement"(loop) {
|
|
386
|
+
loop.traverse({
|
|
387
|
+
JSXElement(element) {
|
|
388
|
+
element.skip();
|
|
389
|
+
if (!hasKey(element.node)) {
|
|
390
|
+
report(element);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
CallExpression(call) {
|
|
396
|
+
const callee = call.node.callee;
|
|
397
|
+
if (LIST_CALL in call.node || !t.isMemberExpression(callee) || callee.computed || !t.isIdentifier(callee.property, { name: "map" })) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
call.traverse({
|
|
401
|
+
JSXElement(element) {
|
|
402
|
+
element.skip();
|
|
403
|
+
if (!hasKey(element.node)) {
|
|
404
|
+
report(element);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
function hasKey(element) {
|
|
412
|
+
return element.openingElement.attributes.some(
|
|
413
|
+
(attribute) => t.isJSXAttribute(attribute) && attributeName(attribute) === "key"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
277
416
|
function returnsView(node) {
|
|
278
417
|
if (node === null || node === void 0) {
|
|
279
418
|
return false;
|
|
@@ -497,7 +636,229 @@ function compileNode(path, state) {
|
|
|
497
636
|
if (isComponentTag(node)) {
|
|
498
637
|
return compileComponent(path, state);
|
|
499
638
|
}
|
|
500
|
-
return compileTemplate(path, state);
|
|
639
|
+
return state.firsthand.ssr ? compileMarkup(path, state) : compileTemplate(path, state);
|
|
640
|
+
}
|
|
641
|
+
function collectViews(program, state) {
|
|
642
|
+
const { views, viewNodes } = state.firsthand;
|
|
643
|
+
const record = (path) => {
|
|
644
|
+
let fn = path.getFunctionParent();
|
|
645
|
+
while (fn !== null) {
|
|
646
|
+
viewNodes.add(fn.node);
|
|
647
|
+
const named = moduleLevelName(fn);
|
|
648
|
+
if (named !== null) {
|
|
649
|
+
views.set(named, t.identifier(named));
|
|
650
|
+
}
|
|
651
|
+
fn = fn.getFunctionParent();
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
program.traverse({
|
|
655
|
+
JSXElement: record,
|
|
656
|
+
JSXFragment: record
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
function moduleLevelName(fn) {
|
|
660
|
+
if (fn.isFunctionDeclaration()) {
|
|
661
|
+
const id = fn.node.id;
|
|
662
|
+
if (id === null || id === void 0) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
return t.isProgram(fn.parentPath.scope.block) ? id.name : null;
|
|
666
|
+
}
|
|
667
|
+
const declarator = fn.parentPath;
|
|
668
|
+
if (!declarator.isVariableDeclarator() || !t.isIdentifier(declarator.node.id)) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
return t.isProgram(declarator.scope.block) ? declarator.node.id.name : null;
|
|
672
|
+
}
|
|
673
|
+
function isLocalView(path, state) {
|
|
674
|
+
const name = path.node.openingElement.name;
|
|
675
|
+
if (!t.isJSXIdentifier(name)) {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
const binding = path.scope.getBinding(name.name);
|
|
679
|
+
if (binding === void 0 || binding.kind === "module" || binding.constantViolations.length > 0) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
if (binding.path.isFunctionDeclaration()) {
|
|
683
|
+
return state.firsthand.viewNodes.has(binding.path.node);
|
|
684
|
+
}
|
|
685
|
+
if (!binding.path.isVariableDeclarator()) {
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
const init = binding.path.node.init;
|
|
689
|
+
if (!t.isArrowFunctionExpression(init) && !t.isFunctionExpression(init)) {
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
return state.firsthand.viewNodes.has(init);
|
|
693
|
+
}
|
|
694
|
+
function collectRuns(program, state) {
|
|
695
|
+
program.traverse({
|
|
696
|
+
CallExpression(call) {
|
|
697
|
+
if (!isComponentCall(call)) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const setup = call.get("arguments.0");
|
|
701
|
+
if (!setup.isArrowFunctionExpression() && !setup.isFunctionExpression()) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const found = [];
|
|
705
|
+
const body = setup.get("body");
|
|
706
|
+
if (!body.isBlockStatement()) {
|
|
707
|
+
if (body.isArrowFunctionExpression() || body.isFunctionExpression()) {
|
|
708
|
+
found.push(body);
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
body.traverse({
|
|
712
|
+
// A handler or a callback returns its own things; only what the
|
|
713
|
+
// setup itself hands back is the view.
|
|
714
|
+
Function(nested) {
|
|
715
|
+
nested.skip();
|
|
716
|
+
},
|
|
717
|
+
ReturnStatement(statement) {
|
|
718
|
+
const argument = statement.get("argument");
|
|
719
|
+
if (argument.isArrowFunctionExpression() || argument.isFunctionExpression()) {
|
|
720
|
+
found.push(argument);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (found.length === 0) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
setup.ensureBlock();
|
|
729
|
+
const block = setup.get("body");
|
|
730
|
+
for (const run of found) {
|
|
731
|
+
const store = setup.scope.generateUidIdentifier("store");
|
|
732
|
+
block.unshiftContainer(
|
|
733
|
+
"body",
|
|
734
|
+
t.variableDeclaration("const", [
|
|
735
|
+
t.variableDeclarator(
|
|
736
|
+
store,
|
|
737
|
+
t.callExpression(runtime(state, "store"), [t.stringLiteral(declaredName(call))])
|
|
738
|
+
)
|
|
739
|
+
])
|
|
740
|
+
);
|
|
741
|
+
let index = 0;
|
|
742
|
+
state.firsthand.runs.set(run.node, {
|
|
743
|
+
node: run.node,
|
|
744
|
+
store,
|
|
745
|
+
next: () => index++
|
|
746
|
+
});
|
|
747
|
+
closeRun(run, store, state);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
function closeRun(run, store, state) {
|
|
753
|
+
const body = run.get("body");
|
|
754
|
+
if (!body.isBlockStatement()) {
|
|
755
|
+
body.replaceWith(
|
|
756
|
+
t.callExpression(runtime(state, "ran"), [t.cloneNode(store), body.node])
|
|
757
|
+
);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const returns = [];
|
|
761
|
+
body.traverse({
|
|
762
|
+
Function(nested) {
|
|
763
|
+
nested.skip();
|
|
764
|
+
},
|
|
765
|
+
ReturnStatement(statement) {
|
|
766
|
+
returns.push(statement);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
for (const statement of returns) {
|
|
770
|
+
statement.node.argument = t.callExpression(runtime(state, "ran"), [
|
|
771
|
+
t.cloneNode(store),
|
|
772
|
+
statement.node.argument ?? t.identifier("undefined")
|
|
773
|
+
]);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function isComponentCall(path) {
|
|
777
|
+
if (!t.isIdentifier(path.node.callee, { name: "component" })) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
const binding = path.scope.getBinding("component");
|
|
781
|
+
return binding !== void 0 && isFirsthandImport(binding);
|
|
782
|
+
}
|
|
783
|
+
function enclosingRun(path, state) {
|
|
784
|
+
let fn = path.getFunctionParent();
|
|
785
|
+
while (fn !== null && GENERATED in fn.node) {
|
|
786
|
+
fn = fn.getFunctionParent();
|
|
787
|
+
}
|
|
788
|
+
if (fn === null) {
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
return state.firsthand.runs.get(fn.node) ?? null;
|
|
792
|
+
}
|
|
793
|
+
function dependsOnRun(node, run, at) {
|
|
794
|
+
if (run === null) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
const names = /* @__PURE__ */ new Set();
|
|
798
|
+
collectNames(node, names);
|
|
799
|
+
for (const name of names) {
|
|
800
|
+
const binding = at.scope.getBinding(name);
|
|
801
|
+
if (binding === void 0) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (binding.scope.block === run.node || binding.path.findParent((parent) => parent.node === run.node) !== null) {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
function collectNames(node, into) {
|
|
811
|
+
if (t.isIdentifier(node)) {
|
|
812
|
+
into.add(node.name);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
for (const key of t.VISITOR_KEYS[node.type]) {
|
|
816
|
+
if (t.isMemberExpression(node) && !node.computed && key === "property") {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if ((t.isObjectProperty(node) || t.isObjectMethod(node)) && !node.computed && key === "key") {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const value = node[key];
|
|
823
|
+
for (const child of Array.isArray(value) ? value : [value]) {
|
|
824
|
+
if (typeof child === "object" && child !== null) {
|
|
825
|
+
collectNames(child, into);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function canRetain(node, build) {
|
|
831
|
+
let possible = true;
|
|
832
|
+
const visit = (element) => {
|
|
833
|
+
for (const attribute of element.openingElement.attributes) {
|
|
834
|
+
if (t.isJSXSpreadAttribute(attribute)) {
|
|
835
|
+
if (dependsOnRun(attribute.argument, build.run, build.at)) {
|
|
836
|
+
possible = false;
|
|
837
|
+
}
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
const value = attribute.value;
|
|
841
|
+
if (attributeName(attribute) === "ref" && t.isJSXExpressionContainer(value) && !t.isJSXEmptyExpression(value.expression) && dependsOnRun(value.expression, build.run, build.at)) {
|
|
842
|
+
possible = false;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
for (const child of element.children) {
|
|
846
|
+
if (t.isJSXElement(child)) {
|
|
847
|
+
if (!isComponentTag(child)) {
|
|
848
|
+
visit(child);
|
|
849
|
+
}
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression) && isKeyedList(child.expression) && dependsOnRun(child.expression, build.run, build.at)) {
|
|
853
|
+
possible = false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
visit(node);
|
|
858
|
+
return possible;
|
|
859
|
+
}
|
|
860
|
+
function isKeyedList(node) {
|
|
861
|
+
return t.isCallExpression(node) && LIST_CALL in node;
|
|
501
862
|
}
|
|
502
863
|
function isComponentTag(node) {
|
|
503
864
|
const name = node.openingElement.name;
|
|
@@ -517,7 +878,25 @@ function tagExpression(name) {
|
|
|
517
878
|
}
|
|
518
879
|
function compileComponent(path, state) {
|
|
519
880
|
const node = path.node;
|
|
881
|
+
const run = enclosingRun(path, state);
|
|
520
882
|
const properties = [];
|
|
883
|
+
const cells = [];
|
|
884
|
+
const throughCell = (value) => {
|
|
885
|
+
const holder = t.identifier(`_cell$${String(run.next())}`);
|
|
886
|
+
cells.push(
|
|
887
|
+
t.variableDeclaration("const", [
|
|
888
|
+
t.variableDeclarator(
|
|
889
|
+
holder,
|
|
890
|
+
t.callExpression(runtime(state, "cell"), [
|
|
891
|
+
t.cloneNode(run.store),
|
|
892
|
+
t.numericLiteral(run.next()),
|
|
893
|
+
value
|
|
894
|
+
])
|
|
895
|
+
)
|
|
896
|
+
])
|
|
897
|
+
);
|
|
898
|
+
return t.memberExpression(t.cloneNode(holder), t.identifier("value"));
|
|
899
|
+
};
|
|
521
900
|
for (const attribute of node.openingElement.attributes) {
|
|
522
901
|
if (t.isJSXSpreadAttribute(attribute)) {
|
|
523
902
|
properties.push(t.spreadElement(attribute.argument));
|
|
@@ -525,12 +904,20 @@ function compileComponent(path, state) {
|
|
|
525
904
|
}
|
|
526
905
|
const name = attributeName(attribute);
|
|
527
906
|
const value = attributeValue(attribute);
|
|
907
|
+
if (state.firsthand.ssr && name === "key") {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
528
910
|
if (value === null || isStaticValue(value)) {
|
|
529
911
|
properties.push(t.objectProperty(propertyKey(name), value ?? t.booleanLiteral(true)));
|
|
530
912
|
} else {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
913
|
+
const held = dependsOnRun(value, run, path) ? throughCell(value) : value;
|
|
914
|
+
if (state.firsthand.ssr && isPure(value)) {
|
|
915
|
+
properties.push(t.objectProperty(propertyKey(name), held));
|
|
916
|
+
} else {
|
|
917
|
+
const read = t.returnStatement(held);
|
|
918
|
+
takePosition(read, value);
|
|
919
|
+
properties.push(t.objectMethod("get", propertyKey(name), [], t.blockStatement([read])));
|
|
920
|
+
}
|
|
534
921
|
}
|
|
535
922
|
}
|
|
536
923
|
const children = compileChildren(node.children, state);
|
|
@@ -553,11 +940,94 @@ function compileComponent(path, state) {
|
|
|
553
940
|
)
|
|
554
941
|
);
|
|
555
942
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
943
|
+
const tag = tagExpression(node.openingElement.name);
|
|
944
|
+
const make = isLocalView(path, state) ? t.callExpression(tag, [t.objectExpression(properties)]) : t.callExpression(runtime(state, "createComponent"), [tag, t.objectExpression(properties)]);
|
|
945
|
+
if (run !== null) {
|
|
946
|
+
return keptChild(state, run, make, cells);
|
|
947
|
+
}
|
|
948
|
+
if (isLocalView(path, state)) {
|
|
949
|
+
if (state.firsthand.ssr) {
|
|
950
|
+
return make;
|
|
951
|
+
}
|
|
952
|
+
const parent = path.parentPath;
|
|
953
|
+
if (parent.isArrowFunctionExpression() && parent.node.body === node && CHILD_THUNK in parent.node) {
|
|
954
|
+
return make;
|
|
955
|
+
}
|
|
956
|
+
return t.callExpression(runtime(state, "part"), [t.arrowFunctionExpression([], make)]);
|
|
957
|
+
}
|
|
958
|
+
return make;
|
|
959
|
+
}
|
|
960
|
+
function keptChild(state, run, make, cells) {
|
|
961
|
+
const slot = t.identifier(`_kept$${String(run.next())}`);
|
|
962
|
+
const held = t.identifier(`_own$${String(run.next())}`);
|
|
963
|
+
const made = () => t.memberExpression(t.cloneNode(slot), t.identifier("last"));
|
|
964
|
+
const body = [
|
|
965
|
+
...cells,
|
|
966
|
+
t.variableDeclaration("const", [
|
|
967
|
+
t.variableDeclarator(
|
|
968
|
+
slot,
|
|
969
|
+
t.callExpression(runtime(state, "site"), [
|
|
970
|
+
t.cloneNode(run.store),
|
|
971
|
+
t.numericLiteral(run.next())
|
|
972
|
+
])
|
|
973
|
+
)
|
|
974
|
+
]),
|
|
975
|
+
t.ifStatement(
|
|
976
|
+
t.binaryExpression("===", made(), t.identifier("undefined")),
|
|
977
|
+
t.blockStatement([
|
|
978
|
+
t.variableDeclaration("const", [
|
|
979
|
+
t.variableDeclarator(
|
|
980
|
+
held,
|
|
981
|
+
t.callExpression(runtime(state, "open"), [t.cloneNode(run.store), t.cloneNode(slot)])
|
|
982
|
+
)
|
|
983
|
+
]),
|
|
984
|
+
expressionStatement2(
|
|
985
|
+
t.assignmentExpression(
|
|
986
|
+
"=",
|
|
987
|
+
made(),
|
|
988
|
+
t.callExpression(runtime(state, "part"), [t.arrowFunctionExpression([], make)])
|
|
989
|
+
)
|
|
990
|
+
),
|
|
991
|
+
expressionStatement2(t.callExpression(runtime(state, "close"), [t.cloneNode(held)]))
|
|
992
|
+
])
|
|
993
|
+
),
|
|
994
|
+
t.returnStatement(made())
|
|
995
|
+
];
|
|
996
|
+
return t.callExpression(generated(t.arrowFunctionExpression([], t.blockStatement(body))), []);
|
|
997
|
+
}
|
|
998
|
+
function isPure(node) {
|
|
999
|
+
switch (node.type) {
|
|
1000
|
+
case "Identifier":
|
|
1001
|
+
case "ThisExpression":
|
|
1002
|
+
case "StringLiteral":
|
|
1003
|
+
case "NumericLiteral":
|
|
1004
|
+
case "BooleanLiteral":
|
|
1005
|
+
case "NullLiteral":
|
|
1006
|
+
case "BigIntLiteral":
|
|
1007
|
+
case "RegExpLiteral":
|
|
1008
|
+
return true;
|
|
1009
|
+
case "MemberExpression":
|
|
1010
|
+
case "OptionalMemberExpression":
|
|
1011
|
+
return isPure(node.object) && (!node.computed || isPure(node.property));
|
|
1012
|
+
case "UnaryExpression":
|
|
1013
|
+
return node.operator !== "delete" && isPure(node.argument);
|
|
1014
|
+
case "BinaryExpression":
|
|
1015
|
+
return isPure(node.left) && isPure(node.right);
|
|
1016
|
+
case "LogicalExpression":
|
|
1017
|
+
return isPure(node.left) && isPure(node.right);
|
|
1018
|
+
case "ConditionalExpression":
|
|
1019
|
+
return isPure(node.test) && isPure(node.consequent) && isPure(node.alternate);
|
|
1020
|
+
case "TemplateLiteral":
|
|
1021
|
+
return node.expressions.every((one) => t.isExpression(one) && isPure(one));
|
|
1022
|
+
case "ArrayExpression":
|
|
1023
|
+
return node.elements.every((one) => one === null || t.isExpression(one) && isPure(one));
|
|
1024
|
+
case "ObjectExpression":
|
|
1025
|
+
return node.properties.every(
|
|
1026
|
+
(one) => t.isObjectProperty(one) && !one.computed && t.isExpression(one.value) && isPure(one.value)
|
|
1027
|
+
);
|
|
1028
|
+
default:
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
561
1031
|
}
|
|
562
1032
|
function propertyKey(name) {
|
|
563
1033
|
return t.isValidIdentifier(name) ? t.identifier(name) : t.stringLiteral(name);
|
|
@@ -582,15 +1052,149 @@ function attributeValue(attribute) {
|
|
|
582
1052
|
function isStaticValue(value) {
|
|
583
1053
|
return t.isStringLiteral(value) || t.isNumericLiteral(value) || t.isBooleanLiteral(value) || t.isNullLiteral(value);
|
|
584
1054
|
}
|
|
1055
|
+
function pushText(markup, text) {
|
|
1056
|
+
markup.parts[markup.parts.length - 1] = markup.parts[markup.parts.length - 1] + text;
|
|
1057
|
+
}
|
|
1058
|
+
function pushHole(markup, value) {
|
|
1059
|
+
markup.values.push(value);
|
|
1060
|
+
markup.parts.push("");
|
|
1061
|
+
}
|
|
1062
|
+
function compileMarkup(path, state) {
|
|
1063
|
+
const markup = { parts: [""], values: [] };
|
|
1064
|
+
emitMarkupElement(path.node, markup, state);
|
|
1065
|
+
if (markup.values.length === 0) {
|
|
1066
|
+
return t.callExpression(runtime(state, "ssr"), [
|
|
1067
|
+
t.arrayExpression([t.stringLiteral(markup.parts[0])])
|
|
1068
|
+
]);
|
|
1069
|
+
}
|
|
1070
|
+
return t.callExpression(runtime(state, "ssr"), [
|
|
1071
|
+
t.arrayExpression(markup.parts.map((part) => t.stringLiteral(part))),
|
|
1072
|
+
...markup.values
|
|
1073
|
+
]);
|
|
1074
|
+
}
|
|
1075
|
+
function emitMarkupElement(node, markup, state) {
|
|
1076
|
+
const name = node.openingElement.name;
|
|
1077
|
+
if (!t.isJSXIdentifier(name)) {
|
|
1078
|
+
const namespaced = name;
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`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.`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
const tag = name.name;
|
|
1084
|
+
pushText(markup, `<${tag}`);
|
|
1085
|
+
for (const attribute of node.openingElement.attributes) {
|
|
1086
|
+
emitMarkupAttribute(attribute, markup, state);
|
|
1087
|
+
}
|
|
1088
|
+
pushText(markup, ">");
|
|
1089
|
+
emitMarkupChildren(node.children, markup, state);
|
|
1090
|
+
if (!VOID_ELEMENTS.has(tag)) {
|
|
1091
|
+
pushText(markup, `</${tag}>`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
function emitMarkupAttribute(attribute, markup, state) {
|
|
1095
|
+
if (t.isJSXSpreadAttribute(attribute)) {
|
|
1096
|
+
pushHole(markup, t.callExpression(runtime(state, "spread"), [attribute.argument]));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const name = attributeName(attribute);
|
|
1100
|
+
const value = attributeValue(attribute);
|
|
1101
|
+
if (name === "ref" || name.startsWith("on") && name.length > 2 && /[A-Z:]/.test(name[2])) {
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (name === "key") {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (value === null) {
|
|
1108
|
+
pushText(markup, ` ${name}=""`);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (isStaticValue(value)) {
|
|
1112
|
+
if (t.isBooleanLiteral(value) && !value.value) {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (t.isNullLiteral(value)) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const literal = t.isStringLiteral(value) ? value.value : t.isNumericLiteral(value) ? String(value.value) : "";
|
|
1119
|
+
pushText(markup, ` ${name}="${escapeAttribute(literal)}"`);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
pushHole(markup, markupAttributeCall(name, value, state));
|
|
1123
|
+
}
|
|
1124
|
+
function markupAttributeCall(name, value, state) {
|
|
1125
|
+
if (name.startsWith("prop:")) {
|
|
1126
|
+
return t.callExpression(runtime(state, "setProperty"), [t.stringLiteral(name.slice(5)), value]);
|
|
1127
|
+
}
|
|
1128
|
+
if (name.startsWith("attr:")) {
|
|
1129
|
+
return t.callExpression(runtime(state, "setAttribute"), [
|
|
1130
|
+
t.stringLiteral(name.slice(5)),
|
|
1131
|
+
value
|
|
1132
|
+
]);
|
|
1133
|
+
}
|
|
1134
|
+
if (name === "class" || name === "className") {
|
|
1135
|
+
return t.callExpression(runtime(state, "setClass"), [value]);
|
|
1136
|
+
}
|
|
1137
|
+
if (name === "style") {
|
|
1138
|
+
return t.callExpression(runtime(state, "setStyle"), [value]);
|
|
1139
|
+
}
|
|
1140
|
+
if (BOOLEAN_PROPERTIES.has(name)) {
|
|
1141
|
+
return t.callExpression(runtime(state, "setBoolean"), [t.stringLiteral(name), value]);
|
|
1142
|
+
}
|
|
1143
|
+
if (DOM_PROPERTIES.has(name)) {
|
|
1144
|
+
return t.callExpression(runtime(state, "setProperty"), [t.stringLiteral(name), value]);
|
|
1145
|
+
}
|
|
1146
|
+
return t.callExpression(runtime(state, "setAttribute"), [t.stringLiteral(name), value]);
|
|
1147
|
+
}
|
|
1148
|
+
function emitMarkupChildren(children, markup, state) {
|
|
1149
|
+
const entries = planChildren(children);
|
|
1150
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1151
|
+
const entry = entries[i];
|
|
1152
|
+
if (entry.kind === "text") {
|
|
1153
|
+
pushText(markup, escapeText(entry.text));
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (entry.kind === "element") {
|
|
1157
|
+
emitMarkupElement(entry.element, markup, state);
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (entries.length > 1) {
|
|
1161
|
+
pushText(markup, "<!--[-->");
|
|
1162
|
+
}
|
|
1163
|
+
pushHole(markup, t.callExpression(runtime(state, "child"), [entry.expression]));
|
|
1164
|
+
if (i !== entries.length - 1) {
|
|
1165
|
+
pushText(markup, "<!---->");
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
585
1169
|
function compileTemplate(path, state) {
|
|
1170
|
+
let names = 0;
|
|
1171
|
+
const statements = [];
|
|
586
1172
|
const build = {
|
|
587
1173
|
html: [],
|
|
588
|
-
statements
|
|
1174
|
+
statements,
|
|
1175
|
+
// Where a site is built once, these are three lists; where it is not, they
|
|
1176
|
+
// are one, and everything simply happens in the order it was written.
|
|
1177
|
+
once: statements,
|
|
1178
|
+
each: statements,
|
|
1179
|
+
run: null,
|
|
1180
|
+
at: path,
|
|
589
1181
|
next: /* @__PURE__ */ (() => {
|
|
590
1182
|
let n = 0;
|
|
591
1183
|
return () => t.identifier(`_el$${String(++n)}`);
|
|
592
|
-
})()
|
|
1184
|
+
})(),
|
|
1185
|
+
name: (prefix) => t.identifier(`${prefix}${String(++names)}`)
|
|
593
1186
|
};
|
|
1187
|
+
const run = enclosingRun(path, state);
|
|
1188
|
+
if (run !== null) {
|
|
1189
|
+
build.run = run;
|
|
1190
|
+
if (canRetain(path.node, build)) {
|
|
1191
|
+
build.once = [];
|
|
1192
|
+
build.each = [];
|
|
1193
|
+
} else {
|
|
1194
|
+
build.run = null;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
const kept = build.run !== null;
|
|
594
1198
|
const root = t.identifier("_el$");
|
|
595
1199
|
emitElement(path.node, build, root, state);
|
|
596
1200
|
const templateId = t.identifier(`_tmpl$${String(++state.firsthand.counter)}`);
|
|
@@ -604,14 +1208,107 @@ function compileTemplate(path, state) {
|
|
|
604
1208
|
)
|
|
605
1209
|
)
|
|
606
1210
|
);
|
|
1211
|
+
if (!kept) {
|
|
1212
|
+
const body2 = [
|
|
1213
|
+
t.variableDeclaration("const", [
|
|
1214
|
+
t.variableDeclarator(root, t.callExpression(t.cloneNode(templateId), []))
|
|
1215
|
+
]),
|
|
1216
|
+
...build.statements,
|
|
1217
|
+
t.returnStatement(t.cloneNode(root))
|
|
1218
|
+
];
|
|
1219
|
+
return t.callExpression(generated(t.arrowFunctionExpression([], t.blockStatement(body2))), []);
|
|
1220
|
+
}
|
|
1221
|
+
const slot = build.name("_site$");
|
|
1222
|
+
const fresh = build.name("_new$");
|
|
1223
|
+
const held = build.name("_own$");
|
|
1224
|
+
const owner = build.run;
|
|
607
1225
|
const body = [
|
|
608
1226
|
t.variableDeclaration("const", [
|
|
609
|
-
t.variableDeclarator(
|
|
1227
|
+
t.variableDeclarator(
|
|
1228
|
+
slot,
|
|
1229
|
+
t.callExpression(runtime(state, "site"), [
|
|
1230
|
+
t.cloneNode(owner.store),
|
|
1231
|
+
t.numericLiteral(owner.next())
|
|
1232
|
+
])
|
|
1233
|
+
)
|
|
1234
|
+
]),
|
|
1235
|
+
t.variableDeclaration("let", [
|
|
1236
|
+
t.variableDeclarator(root, t.memberExpression(t.cloneNode(slot), t.identifier("node")))
|
|
1237
|
+
]),
|
|
1238
|
+
t.variableDeclaration("const", [
|
|
1239
|
+
t.variableDeclarator(
|
|
1240
|
+
fresh,
|
|
1241
|
+
t.binaryExpression("===", t.cloneNode(root), t.identifier("undefined"))
|
|
1242
|
+
)
|
|
610
1243
|
]),
|
|
1244
|
+
t.variableDeclaration("let", [t.variableDeclarator(held)]),
|
|
1245
|
+
t.ifStatement(
|
|
1246
|
+
t.cloneNode(fresh),
|
|
1247
|
+
t.blockStatement([
|
|
1248
|
+
// What this site makes belongs to the instance. A run's own scope is
|
|
1249
|
+
// cleared before it runs again, and a part left there would be
|
|
1250
|
+
// disposed by the very next run.
|
|
1251
|
+
expressionStatement2(
|
|
1252
|
+
t.assignmentExpression(
|
|
1253
|
+
"=",
|
|
1254
|
+
t.cloneNode(held),
|
|
1255
|
+
t.callExpression(runtime(state, "open"), [t.cloneNode(owner.store), t.cloneNode(slot)])
|
|
1256
|
+
)
|
|
1257
|
+
),
|
|
1258
|
+
expressionStatement2(
|
|
1259
|
+
t.assignmentExpression(
|
|
1260
|
+
"=",
|
|
1261
|
+
t.cloneNode(root),
|
|
1262
|
+
t.assignmentExpression(
|
|
1263
|
+
"=",
|
|
1264
|
+
t.memberExpression(t.cloneNode(slot), t.identifier("node")),
|
|
1265
|
+
t.callExpression(t.cloneNode(templateId), [])
|
|
1266
|
+
)
|
|
1267
|
+
)
|
|
1268
|
+
)
|
|
1269
|
+
])
|
|
1270
|
+
),
|
|
611
1271
|
...build.statements,
|
|
612
|
-
t.
|
|
1272
|
+
t.ifStatement(
|
|
1273
|
+
t.cloneNode(fresh),
|
|
1274
|
+
t.blockStatement([
|
|
1275
|
+
...build.once,
|
|
1276
|
+
expressionStatement2(t.callExpression(runtime(state, "close"), [t.cloneNode(held)]))
|
|
1277
|
+
])
|
|
1278
|
+
)
|
|
613
1279
|
];
|
|
614
|
-
|
|
1280
|
+
body.push(...build.each, t.returnStatement(t.cloneNode(root)));
|
|
1281
|
+
return t.callExpression(generated(t.arrowFunctionExpression([], t.blockStatement(body))), []);
|
|
1282
|
+
}
|
|
1283
|
+
function guardedWrite(build, state, value, write) {
|
|
1284
|
+
const run = build.run;
|
|
1285
|
+
const slot = build.name("_w$");
|
|
1286
|
+
const held = build.name("_x$");
|
|
1287
|
+
const last = () => t.memberExpression(t.cloneNode(slot), t.identifier("last"));
|
|
1288
|
+
pushEach(
|
|
1289
|
+
build,
|
|
1290
|
+
t.variableDeclaration("const", [
|
|
1291
|
+
t.variableDeclarator(
|
|
1292
|
+
slot,
|
|
1293
|
+
t.callExpression(runtime(state, "site"), [
|
|
1294
|
+
t.cloneNode(run.store),
|
|
1295
|
+
t.numericLiteral(run.next())
|
|
1296
|
+
])
|
|
1297
|
+
),
|
|
1298
|
+
t.variableDeclarator(held, value)
|
|
1299
|
+
])
|
|
1300
|
+
);
|
|
1301
|
+
pushEach(
|
|
1302
|
+
build,
|
|
1303
|
+
t.ifStatement(
|
|
1304
|
+
t.binaryExpression("!==", last(), t.cloneNode(held)),
|
|
1305
|
+
t.blockStatement([
|
|
1306
|
+
expressionStatement2(t.assignmentExpression("=", last(), t.cloneNode(held))),
|
|
1307
|
+
expressionStatement2(t.callExpression(runtime(state, "wrote"), [t.cloneNode(run.store)])),
|
|
1308
|
+
expressionStatement2(write(t.cloneNode(held)))
|
|
1309
|
+
])
|
|
1310
|
+
)
|
|
1311
|
+
);
|
|
615
1312
|
}
|
|
616
1313
|
function emitElement(node, build, self, state) {
|
|
617
1314
|
const name = node.openingElement.name;
|
|
@@ -639,7 +1336,8 @@ function emitElement(node, build, self, state) {
|
|
|
639
1336
|
function emitAttribute(attribute, build, self, state, deferred, tag) {
|
|
640
1337
|
if (t.isJSXSpreadAttribute(attribute)) {
|
|
641
1338
|
deferred.push(() => {
|
|
642
|
-
|
|
1339
|
+
pushOnce(
|
|
1340
|
+
build,
|
|
643
1341
|
expressionStatement2(
|
|
644
1342
|
t.callExpression(runtime(state, "bind"), [
|
|
645
1343
|
t.arrowFunctionExpression(
|
|
@@ -656,7 +1354,8 @@ function emitAttribute(attribute, build, self, state, deferred, tag) {
|
|
|
656
1354
|
const value = attributeValue(attribute);
|
|
657
1355
|
if (name === "ref") {
|
|
658
1356
|
deferred.push(() => {
|
|
659
|
-
|
|
1357
|
+
pushOnce(
|
|
1358
|
+
build,
|
|
660
1359
|
expressionStatement2(t.callExpression(value, [t.cloneNode(self)]))
|
|
661
1360
|
);
|
|
662
1361
|
});
|
|
@@ -680,7 +1379,12 @@ function emitAttribute(attribute, build, self, state, deferred, tag) {
|
|
|
680
1379
|
t.objectExpression([t.objectProperty(propertyKey(modifier), t.booleanLiteral(true))])
|
|
681
1380
|
);
|
|
682
1381
|
}
|
|
683
|
-
|
|
1382
|
+
const attach = expressionStatement2(t.callExpression(runtime(state, "on"), args));
|
|
1383
|
+
if (dependsOnRun(value, build.run, build.at)) {
|
|
1384
|
+
pushEach(build, attach);
|
|
1385
|
+
} else {
|
|
1386
|
+
pushOnce(build, attach);
|
|
1387
|
+
}
|
|
684
1388
|
});
|
|
685
1389
|
return;
|
|
686
1390
|
}
|
|
@@ -700,7 +1404,17 @@ function emitAttribute(attribute, build, self, state, deferred, tag) {
|
|
|
700
1404
|
return;
|
|
701
1405
|
}
|
|
702
1406
|
deferred.push(() => {
|
|
703
|
-
build.
|
|
1407
|
+
if (dependsOnRun(value, build.run, build.at)) {
|
|
1408
|
+
guardedWrite(
|
|
1409
|
+
build,
|
|
1410
|
+
state,
|
|
1411
|
+
value,
|
|
1412
|
+
(held) => dynamicAttributeCall(name, held, self, state, tag)
|
|
1413
|
+
);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
pushOnce(
|
|
1417
|
+
build,
|
|
704
1418
|
expressionStatement2(
|
|
705
1419
|
t.callExpression(runtime(state, "bind"), [
|
|
706
1420
|
located(
|
|
@@ -860,7 +1574,7 @@ function emitChildren(children, build, self, state) {
|
|
|
860
1574
|
const id = build.next();
|
|
861
1575
|
build.statements.push(
|
|
862
1576
|
t.variableDeclaration("const", [
|
|
863
|
-
t.variableDeclarator(id, navigate(self, previous, previousIndex, index))
|
|
1577
|
+
t.variableDeclarator(id, navigate(self, previous, previousIndex, index, state))
|
|
864
1578
|
])
|
|
865
1579
|
);
|
|
866
1580
|
previous = id;
|
|
@@ -886,18 +1600,39 @@ function emitChildren(children, build, self, state) {
|
|
|
886
1600
|
continue;
|
|
887
1601
|
}
|
|
888
1602
|
const isLast = i === entries.length - 1;
|
|
1603
|
+
const expression = entry.expression;
|
|
1604
|
+
let marker = null;
|
|
1605
|
+
if (!isLast) {
|
|
1606
|
+
build.html.push("<!>");
|
|
1607
|
+
marker = t.cloneNode(reference());
|
|
1608
|
+
}
|
|
1609
|
+
if (entry.kind !== "list" && dependsOnRun(expression, build.run, build.at)) {
|
|
1610
|
+
const run = build.run;
|
|
1611
|
+
pushEach(
|
|
1612
|
+
build,
|
|
1613
|
+
expressionStatement2(
|
|
1614
|
+
t.callExpression(runtime(state, "writeChild"), [
|
|
1615
|
+
t.cloneNode(run.store),
|
|
1616
|
+
t.numericLiteral(run.next()),
|
|
1617
|
+
t.cloneNode(self),
|
|
1618
|
+
marker ?? t.nullLiteral(),
|
|
1619
|
+
expression
|
|
1620
|
+
])
|
|
1621
|
+
)
|
|
1622
|
+
);
|
|
1623
|
+
index++;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
889
1626
|
const args = [
|
|
890
1627
|
t.cloneNode(self),
|
|
891
1628
|
// A list part is already a thunk that owns its rows; wrapping it would
|
|
892
1629
|
// rebuild the whole list on every evaluation.
|
|
893
|
-
entry.kind === "list" ?
|
|
1630
|
+
entry.kind === "list" ? expression : thunk(expression)
|
|
894
1631
|
];
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
const id = reference();
|
|
898
|
-
args.push(t.cloneNode(id));
|
|
1632
|
+
if (marker !== null) {
|
|
1633
|
+
args.push(marker);
|
|
899
1634
|
}
|
|
900
|
-
build
|
|
1635
|
+
pushOnce(build, expressionStatement2(t.callExpression(runtime(state, "insert"), args)));
|
|
901
1636
|
index++;
|
|
902
1637
|
}
|
|
903
1638
|
}
|
|
@@ -925,18 +1660,21 @@ function needsReference(element) {
|
|
|
925
1660
|
}
|
|
926
1661
|
return false;
|
|
927
1662
|
}
|
|
928
|
-
function navigate(self, previous, previousIndex, index) {
|
|
1663
|
+
function navigate(self, previous, previousIndex, index, state) {
|
|
1664
|
+
const hydratable = state.firsthand.hydratable;
|
|
1665
|
+
const first = hydratable ? runtime(state, "first") : null;
|
|
1666
|
+
const next = hydratable ? runtime(state, "next") : null;
|
|
929
1667
|
let expression;
|
|
930
1668
|
let steps;
|
|
931
1669
|
if (previous === null) {
|
|
932
|
-
expression = t.memberExpression(t.cloneNode(self), t.identifier("firstChild"));
|
|
1670
|
+
expression = hydratable ? t.callExpression(first, [t.cloneNode(self)]) : t.memberExpression(t.cloneNode(self), t.identifier("firstChild"));
|
|
933
1671
|
steps = index;
|
|
934
1672
|
} else {
|
|
935
1673
|
expression = t.cloneNode(previous);
|
|
936
1674
|
steps = index - previousIndex;
|
|
937
1675
|
}
|
|
938
1676
|
for (let i = 0; i < steps; i++) {
|
|
939
|
-
expression = t.memberExpression(expression, t.identifier("nextSibling"));
|
|
1677
|
+
expression = hydratable ? t.callExpression(next, [expression]) : t.memberExpression(expression, t.identifier("nextSibling"));
|
|
940
1678
|
}
|
|
941
1679
|
return expression;
|
|
942
1680
|
}
|
|
@@ -1002,13 +1740,13 @@ function compileChildren(children, state) {
|
|
|
1002
1740
|
result.push(t.stringLiteral(entry.text));
|
|
1003
1741
|
} else if (entry.kind === "element") {
|
|
1004
1742
|
result.push(entry.element);
|
|
1743
|
+
} else if (state.firsthand.ssr) {
|
|
1744
|
+
result.push(entry.expression);
|
|
1005
1745
|
} else if (entry.kind === "list") {
|
|
1006
1746
|
result.push(t.callExpression(runtime(state, "part"), [entry.expression]));
|
|
1007
1747
|
} else {
|
|
1008
1748
|
result.push(
|
|
1009
|
-
t.callExpression(runtime(state, "part"), [
|
|
1010
|
-
t.arrowFunctionExpression([], entry.expression)
|
|
1011
|
-
])
|
|
1749
|
+
t.callExpression(runtime(state, "part"), [thunk(entry.expression)])
|
|
1012
1750
|
);
|
|
1013
1751
|
}
|
|
1014
1752
|
}
|
|
@@ -1016,12 +1754,16 @@ function compileChildren(children, state) {
|
|
|
1016
1754
|
}
|
|
1017
1755
|
function thunk(expression) {
|
|
1018
1756
|
const arrow = t.arrowFunctionExpression([], expression);
|
|
1757
|
+
arrow[CHILD_THUNK] = true;
|
|
1758
|
+
takePosition(arrow, expression);
|
|
1759
|
+
return arrow;
|
|
1760
|
+
}
|
|
1761
|
+
function takePosition(target, expression) {
|
|
1019
1762
|
const loc = expression.loc;
|
|
1020
1763
|
if (loc !== null && loc !== void 0) {
|
|
1021
|
-
|
|
1764
|
+
target.loc = { ...loc, end: loc.start };
|
|
1022
1765
|
expression.loc = null;
|
|
1023
1766
|
}
|
|
1024
|
-
return arrow;
|
|
1025
1767
|
}
|
|
1026
1768
|
function expressionStatement2(expression) {
|
|
1027
1769
|
return located(t.expressionStatement(expression), expression);
|
|
@@ -1070,7 +1812,9 @@ function buildPlugins(options) {
|
|
|
1070
1812
|
{
|
|
1071
1813
|
packageName: options.packageName,
|
|
1072
1814
|
strictReactivity: options.strictReactivity,
|
|
1073
|
-
devtools: options.devtools
|
|
1815
|
+
devtools: options.devtools,
|
|
1816
|
+
ssr: options.ssr,
|
|
1817
|
+
hydratable: options.hydratable
|
|
1074
1818
|
}
|
|
1075
1819
|
]);
|
|
1076
1820
|
return plugins;
|
package/dist/index.js
CHANGED
package/dist/transform.d.ts
CHANGED
|
@@ -18,6 +18,32 @@ type FirsthandState = {
|
|
|
18
18
|
templates: t.VariableDeclarator[];
|
|
19
19
|
counter: number;
|
|
20
20
|
moduleId: string;
|
|
21
|
+
/** Module-level functions markup was compiled into, in source order. */
|
|
22
|
+
views: Map<string, t.Identifier>;
|
|
23
|
+
/** Every function markup was written inside, for resolving tags locally. */
|
|
24
|
+
viewNodes: Set<t.Node>;
|
|
25
|
+
/** Whether this module is being compiled for a server render. */
|
|
26
|
+
ssr: boolean;
|
|
27
|
+
/** Whether this module's output has to be able to adopt server markup. */
|
|
28
|
+
hydratable: boolean;
|
|
29
|
+
/** Functions that run again as a whole, and where each keeps its sites. */
|
|
30
|
+
runs: Map<t.Node, RunContext>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* What a re-running function needs in order to keep its DOM.
|
|
34
|
+
*
|
|
35
|
+
* `store` is an array declared once per instance — in the setup, which runs
|
|
36
|
+
* once — and every site inside the run takes a numbered place in it. The index
|
|
37
|
+
* is fixed at compile time, so a site inside an `if` keeps its own place
|
|
38
|
+
* whether or not the branch was taken: nothing depends on the order the run
|
|
39
|
+
* happens to reach things in, which is the rule React needs for hooks and this
|
|
40
|
+
* does not.
|
|
41
|
+
*/
|
|
42
|
+
type RunContext = {
|
|
43
|
+
/** The function whose body re-runs. Bindings inside it belong to one run. */
|
|
44
|
+
node: t.Node;
|
|
45
|
+
store: t.Identifier;
|
|
46
|
+
next: () => number;
|
|
21
47
|
};
|
|
22
48
|
declare module '@babel/core' {
|
|
23
49
|
interface PluginPass {
|
|
@@ -49,6 +75,26 @@ export type FirsthandPluginOptions = {
|
|
|
49
75
|
* production build emits nothing.
|
|
50
76
|
*/
|
|
51
77
|
devtools?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Compile for a server render.
|
|
80
|
+
*
|
|
81
|
+
* The same source, emitted against `@firsthandjs/server/internal` instead of
|
|
82
|
+
* `@firsthandjs/dom/internal`: markup is built as a string rather than as
|
|
83
|
+
* nodes, and the things a server cannot do — listeners, refs, retained
|
|
84
|
+
* sites — are not emitted at all.
|
|
85
|
+
*
|
|
86
|
+
* The Vite plugin sets this from the bundler's own `ssr` flag, so an
|
|
87
|
+
* application configures nothing.
|
|
88
|
+
*/
|
|
89
|
+
ssr?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Emit navigation that can walk server markup.
|
|
92
|
+
*
|
|
93
|
+
* An application that hydrates needs it; one that does not should leave it
|
|
94
|
+
* off, because it turns two property reads per dynamic position into two
|
|
95
|
+
* calls. The Vite plugin sets it for a project that has a server build.
|
|
96
|
+
*/
|
|
97
|
+
hydratable?: boolean;
|
|
52
98
|
};
|
|
53
99
|
export default function firsthandPlugin(_api: unknown, options?: FirsthandPluginOptions): PluginObject;
|
|
54
100
|
export {};
|
package/dist/transform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAgBlC,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IACjC,4EAA4E;IAC5E,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACvB,iEAAiE;IACjE,GAAG,EAAE,OAAO,CAAC;IACb,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;GASG;AACH,KAAK,UAAU,GAAG;IAChB,6EAA6E;IAC7E,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC;IACpB,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAyBF,OAAO,QAAQ,aAAa,CAAC;IAC3B,UAAU,UAAU;QAClB,SAAS,EAAE,cAAc,CAAC;KAC3B;CACF;AA2BD,MAAM,MAAM,sBAAsB,GAAG;IACnC,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;OAUG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,sBAA2B,GACnC,YAAY,CAiFd"}
|
package/dist/vite.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export type VitePluginLike = {
|
|
|
7
7
|
configResolved(config: {
|
|
8
8
|
command: string;
|
|
9
9
|
}): void;
|
|
10
|
-
transform(code: string, id: string
|
|
10
|
+
transform(code: string, id: string, options?: {
|
|
11
|
+
ssr?: boolean | undefined;
|
|
12
|
+
}): {
|
|
11
13
|
code: string;
|
|
12
14
|
map: SourceMap | null;
|
|
13
15
|
} | null;
|
|
@@ -18,5 +20,28 @@ export type VitePluginLike = {
|
|
|
18
20
|
* Runs before the bundler's TypeScript step: Firsthand parses TS syntax but leaves
|
|
19
21
|
* the annotations in place, so exactly one tool strips them.
|
|
20
22
|
*/
|
|
21
|
-
export
|
|
23
|
+
export type FirsthandViteOptions = FirsthandPluginOptions & {
|
|
24
|
+
/**
|
|
25
|
+
* Files to compile. Everything with a JSX extension, by default.
|
|
26
|
+
*
|
|
27
|
+
* Patterns are regular expressions rather than globs, so that this package
|
|
28
|
+
* keeps its promise of having no dependencies — and so that what matches is
|
|
29
|
+
* something you can read rather than something you have to guess at.
|
|
30
|
+
*/
|
|
31
|
+
include?: readonly RegExp[];
|
|
32
|
+
/**
|
|
33
|
+
* Files to leave alone.
|
|
34
|
+
*
|
|
35
|
+
* This is how a project says *these are not mine*: a folder of React
|
|
36
|
+
* components kept during a migration, compiled by React's own transform.
|
|
37
|
+
* What this compiler does not compile, it does not claim — a component from
|
|
38
|
+
* an excluded file reaches the adapter like any other foreign one.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* firsthand({ exclude: [/\/legacy\//] })
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
exclude?: readonly RegExp[];
|
|
45
|
+
};
|
|
46
|
+
export declare function firsthand(options?: FirsthandViteOptions): VitePluginLike;
|
|
22
47
|
//# sourceMappingURL=vite.d.ts.map
|
package/dist/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE7D,0FAA0F;AAC1F,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;IACf,cAAc,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,SAAS,
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE7D,0FAA0F;AAC1F,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC;IACf,cAAc,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EAIV,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,GACtC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CACnD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG;IAC1D;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B,CAAC;AAEF,wBAAgB,SAAS,CAAC,OAAO,GAAE,oBAAyB,GAAG,cAAc,CAsC5E"}
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compileModule
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ACKKPAGI.js";
|
|
4
4
|
|
|
5
5
|
// packages/compiler/src/vite.ts
|
|
6
6
|
function firsthand(options = {}) {
|
|
@@ -11,17 +11,27 @@ function firsthand(options = {}) {
|
|
|
11
11
|
configResolved(config) {
|
|
12
12
|
serving = config.command === "serve";
|
|
13
13
|
},
|
|
14
|
-
transform(code, id) {
|
|
14
|
+
transform(code, id, hook) {
|
|
15
15
|
const file = id.split("?")[0];
|
|
16
16
|
if (!file.endsWith(".tsx") && !file.endsWith(".jsx")) {
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
+
if (options.exclude?.some((pattern) => pattern.test(file)) === true) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (options.include !== void 0 && !options.include.some((pattern) => pattern.test(file))) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
19
25
|
return compileModule(code, {
|
|
20
26
|
filename: file,
|
|
21
27
|
typescript: file.endsWith(".tsx"),
|
|
22
28
|
devtools: serving,
|
|
23
29
|
sourceMaps: true,
|
|
24
|
-
...options
|
|
30
|
+
...options,
|
|
31
|
+
// The bundler already knows which build this is. An explicit `ssr`
|
|
32
|
+
// option still wins, because a project that compiles a module by hand
|
|
33
|
+
// has its own reasons.
|
|
34
|
+
ssr: options.ssr ?? hook?.ssr === true
|
|
25
35
|
});
|
|
26
36
|
}
|
|
27
37
|
};
|
package/package.json
CHANGED