diff --git a/packages/translator/src/__tests__/fixtures/basic-counter/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/basic-counter/__snapshots__/dom.expected.js
index 0103ef723..5ceafbb1f 100644
--- a/packages/translator/src/__tests__/fixtures/basic-counter/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/basic-counter/__snapshots__/dom.expected.js
@@ -1,36 +1,26 @@
-_attr("onclick", function () {
- clickCount++;
-});
-
-import { data as _data, attr as _attr, createRenderFn as _createRenderFn } from "@marko/runtime-fluurt/src/dom";
-
-function _apply_clickCount() {
- "write clickCount";
-
- _apply_y(clickCount + 1);
-
- "queue _applyWith_clickCount_y";
-}
+import { write as _write, read as _read, data as _data, createRenderFn as _createRenderFn } from "@marko/runtime-fluurt/src/dom";
function _apply() {
_apply_clickCount(0);
-
- _data(new Date());
}
-function _apply_y() {
- "write y";
- "queue _applyWith_clickCount_y";
+function _apply_clickCount(clickCount) {
+ if (_write(2, clickCount)) {
+ _write(4, function () {
+ clickCount++;
+ });
+
+ _hydrate_clickCount();
+
+ _data(1, clickCount);
+ }
}
-function _applyWith_clickCount_y() {
- "read clickCount";
- "read y";
-
- _data(clickCount + y);
+function _hydrate_clickCount(clickCount = _read(2)) {
+ _read(4);
}
-export const template = "";
-export const walks = "D%l D%l";
+export const template = "
";
+export const walks = "D D%";
export const apply = _apply;
export default _createRenderFn(template, walks, [], apply);
\ No newline at end of file
diff --git a/packages/translator/src/analyze/tag/native-tag.ts b/packages/translator/src/analyze/tag/native-tag.ts
index 9ea88fb3c..2563ec870 100644
--- a/packages/translator/src/analyze/tag/native-tag.ts
+++ b/packages/translator/src/analyze/tag/native-tag.ts
@@ -1,17 +1,37 @@
import type { types as t } from "@marko/compiler";
import evaluate from "../util/evaluate";
+import reserveScope from "../util/reserves";
import visit from "../util/visit";
export default {
enter(tag: t.NodePath) {
- if (tag.has("var") || tag.get("attributes").some(isNotComputed)) {
+ const attrs = tag.get("attributes");
+ let shouldVisit = tag.has("var");
+
+ if (attrs.some(isSpreadAttr)) {
+ // TODO
+ } else {
+ for (const attr of attrs as t.NodePath[]) {
+ const { name } = attr.node;
+
+ if (name.startsWith("on")) {
+ shouldVisit = true;
+ reserveScope(attr, 1);
+ } else {
+ const { confident } = evaluate(attr);
+ shouldVisit = shouldVisit || !confident;
+ }
+ }
+ }
+
+ if (shouldVisit) {
visit(tag);
}
},
};
-function isNotComputed(
+function isSpreadAttr(
attr: t.NodePath
-) {
- return !evaluate(attr).confident;
+): attr is t.NodePath {
+ return attr.type === "MarkoSpreadAttribute";
}
diff --git a/packages/translator/src/analyze/util/references.ts b/packages/translator/src/analyze/util/references.ts
index 05fa4eb3f..06882da37 100644
--- a/packages/translator/src/analyze/util/references.ts
+++ b/packages/translator/src/analyze/util/references.ts
@@ -10,12 +10,12 @@ type MarkoExprRootPath = t.NodePath<
| t.MarkoPlaceholder
>;
-export interface Reference {
+export interface Binding {
name: string;
sectionIndex: number;
bindingIndex: number;
}
-export type References = undefined | Reference | Reference[];
+export type References = undefined | Binding | Binding[];
declare module "@marko/compiler/dist/types" {
export interface ProgramExtra {
@@ -23,9 +23,7 @@ declare module "@marko/compiler/dist/types" {
}
export interface IdentifierExtra {
- name?: string;
- sectionIndex?: number;
- bindingIndex?: number;
+ binding?: Binding;
}
export interface MarkoTagExtra {
@@ -76,14 +74,11 @@ function trackReferencesForBindings(section: Section, path: t.NodePath) {
const identifier = bindings[name];
const identifierExtra = (identifier.extra ??= {});
const bindingIndex = section.bindings++;
- const ref: Reference = {
+ const binding: Binding = (identifierExtra.binding = {
name,
sectionIndex,
bindingIndex,
- };
- identifierExtra.name = name;
- identifierExtra.sectionIndex = sectionIndex;
- identifierExtra.bindingIndex = bindingIndex;
+ });
for (const reference of references) {
const exprRoot = getExprRoot(reference);
@@ -94,17 +89,17 @@ function trackReferencesForBindings(section: Section, path: t.NodePath) {
if (curRefs) {
if (Array.isArray(curRefs)) {
- sorted.insert(curRefs, ref, compareReferences);
+ sorted.insert(curRefs, binding, compareReferences);
} else {
- const compareResult = compareReferences(curRefs, ref);
+ const compareResult = compareReferences(curRefs, binding);
if (compareResult !== 0) {
exprExtra[refsKey] =
- compareResult > 0 ? [curRefs, ref] : [ref, curRefs];
+ compareResult > 0 ? [curRefs, binding] : [binding, curRefs];
}
}
} else {
- exprExtra[refsKey] = ref;
+ exprExtra[refsKey] = binding;
}
}
}
@@ -135,7 +130,7 @@ function isMarkoPath(path: t.NodePath): path is MarkoExprRootPath {
}
}
-export function compareReferences(a: Reference, b: Reference) {
+export function compareReferences(a: Binding, b: Binding) {
return a.sectionIndex === b.sectionIndex
? a.bindingIndex - b.bindingIndex
: a.sectionIndex - b.sectionIndex;
diff --git a/packages/translator/src/analyze/util/reserves.ts b/packages/translator/src/analyze/util/reserves.ts
new file mode 100644
index 000000000..0d44067d9
--- /dev/null
+++ b/packages/translator/src/analyze/util/reserves.ts
@@ -0,0 +1,36 @@
+import type { types as t } from "@marko/compiler";
+import { getSection } from "./sections";
+
+export interface Reserve {
+ sectionIndex: number;
+ reserveIndex: number;
+ size: number;
+}
+
+declare module "@marko/compiler/dist/types" {
+ export interface ProgramExtra {
+ reserves?: number;
+ }
+
+ export interface MarkoTagExtra {
+ reserve?: Reserve;
+ }
+
+ export interface MarkoAttributeExtra {
+ reserve?: Reserve;
+ }
+}
+
+export default function reserveScope(
+ tag: t.NodePath,
+ size: number
+) {
+ const section = getSection(tag);
+ const { extra } = tag.node;
+ const { sectionIndex } = section;
+ extra.reserve = {
+ sectionIndex,
+ reserveIndex: (section.reserves += size),
+ size,
+ };
+}
diff --git a/packages/translator/src/analyze/util/sections.ts b/packages/translator/src/analyze/util/sections.ts
index 6ae1d9ac4..d4907086d 100644
--- a/packages/translator/src/analyze/util/sections.ts
+++ b/packages/translator/src/analyze/util/sections.ts
@@ -5,6 +5,7 @@ export interface Section {
sectionIndex: number;
visits: number;
bindings: number;
+ reserves: number;
}
declare module "@marko/compiler/dist/types" {
@@ -49,6 +50,7 @@ export function startSection(path: t.NodePath) {
sectionIndex,
visits: 0,
bindings: 0,
+ reserves: 0,
};
if (sectionIndex) {
diff --git a/packages/translator/src/core/translate-const.ts b/packages/translator/src/core/translate-const.ts
index 97811ee6b..f67fda58b 100644
--- a/packages/translator/src/core/translate-const.ts
+++ b/packages/translator/src/core/translate-const.ts
@@ -4,7 +4,6 @@ import { assertNoBodyContent } from "../util/assert";
import translateVar from "../util/translate-var";
import { isOutputDOM } from "../util/marko-config";
import * as writer from "../util/writer";
-import type { Reference } from "../analyze/util/references";
export default function enter(tag: t.NodePath) {
const { node } = tag;
@@ -48,7 +47,7 @@ export default function enter(tag: t.NodePath) {
identifiers.length === 1
? t.expressionStatement(
t.callExpression(
- writer.getApplyId(tag, identifiers[0].extra as Reference),
+ writer.getApplyId(tag, identifiers[0].extra.binding!),
[defaultAttr.value]
)
)
@@ -59,7 +58,7 @@ export default function enter(tag: t.NodePath) {
...identifiers.map((identifier) =>
t.expressionStatement(
t.callExpression(
- writer.getApplyId(tag, identifier.extra as Reference),
+ writer.getApplyId(tag, identifier.extra.binding!),
[t.identifier(identifier.name)]
)
)
diff --git a/packages/translator/src/core/translate-let.ts b/packages/translator/src/core/translate-let.ts
index 177c54c23..75b20a7c6 100644
--- a/packages/translator/src/core/translate-let.ts
+++ b/packages/translator/src/core/translate-let.ts
@@ -4,7 +4,6 @@ import { assertNoBodyContent } from "../util/assert";
import translateVar from "../util/translate-var";
import { isOutputDOM } from "../util/marko-config";
import * as writer from "../util/writer";
-import type { Reference } from "../analyze/util/references";
export default function enter(tag: t.NodePath) {
const { node } = tag;
@@ -50,7 +49,7 @@ export default function enter(tag: t.NodePath) {
tag,
defaultAttr.extra?.valueReferences,
t.expressionStatement(
- t.callExpression(writer.getApplyId(tag, node.var.extra as Reference), [
+ t.callExpression(writer.getApplyId(tag, node.var.extra.binding!), [
defaultAttr.value,
])
)
diff --git a/packages/translator/src/tag/native-tag.ts b/packages/translator/src/tag/native-tag.ts
index 097ac9bdf..1fe3a074b 100644
--- a/packages/translator/src/tag/native-tag.ts
+++ b/packages/translator/src/tag/native-tag.ts
@@ -38,65 +38,90 @@ export function enter(tag: t.NodePath) {
// TODO: this should iterate backward and filter out duplicated attrs.
for (const attr of attrs as t.NodePath[]) {
const name = attr.node.name;
-
- if (isHTML && name[0] === "o" && name[1] === "n") {
- continue;
- }
-
const extra = attr.node.extra;
const value = attr.get("value");
- const { confident, computed } = extra;
- let staticContent: string | undefined;
- let dynamicExpr: t.Expression | undefined;
+ const { confident, computed, valueReferences } = extra;
switch (name) {
case "class":
- case "style":
+ case "style": {
+ const helper = `${name}Attr` as "classAttr" | "styleAttr";
if (confident) {
- staticContent = getHTMLRuntime(tag)[`${name}Attr`](computed);
+ write`${getHTMLRuntime(tag)[helper](computed)}`;
+ } else if (isHTML) {
+ write`${callRuntime(tag, helper, value.node)}`;
} else {
- dynamicExpr = isHTML
- ? callRuntime(
+ writer.addStatement(
+ "apply",
+ tag,
+ valueReferences,
+ t.expressionStatement(
+ callRuntime(
tag,
- `${name}Attr` as "classAttr" | "styleAttr",
+ helper,
+ t.numericLiteral(visitIndex!),
value.node
)
- : callRuntime(
- tag,
- `${name}Attr` as "classAttr" | "styleAttr",
- t.numericLiteral(visitIndex!),
- value.node
- );
+ )
+ );
}
break;
+ }
default:
if (confident) {
- staticContent = getHTMLRuntime(tag).attr(name, computed);
+ write`${getHTMLRuntime(tag).attr(name, computed)}`;
+ } else if (isHTML && !name.startsWith("on")) {
+ write`${callRuntime(
+ tag,
+ "attr",
+ t.stringLiteral(name),
+ value.node
+ )}`;
} else {
- dynamicExpr = isHTML
- ? callRuntime(tag, "attr", t.stringLiteral(name), value.node)
- : callRuntime(
- tag,
- "attr",
- t.numericLiteral(visitIndex!),
- t.stringLiteral(name),
- value.node
- );
+ if (name.startsWith("on")) {
+ const reserveIndex = writer.reserveToScopeId(tag, extra.reserve!);
+ writer.addStatement(
+ "apply",
+ tag,
+ valueReferences,
+ t.expressionStatement(
+ callRuntime(
+ tag,
+ "write",
+ t.numericLiteral(reserveIndex),
+ value.node
+ )
+ )
+ );
+
+ writer.addStatement(
+ "hydrate",
+ tag,
+ extra.valueReferences,
+ t.expressionStatement(
+ callRuntime(tag, "read", t.numericLiteral(reserveIndex))
+ )
+ );
+ } else {
+ writer.addStatement(
+ "apply",
+ tag,
+ valueReferences,
+ t.expressionStatement(
+ callRuntime(
+ tag,
+ "attr",
+ t.numericLiteral(visitIndex!),
+ t.stringLiteral(name),
+ value.node
+ )
+ )
+ );
+ }
}
break;
}
-
- if (isHTML || staticContent !== undefined) {
- write`${dynamicExpr || staticContent!}`;
- } else {
- writer.addStatement(
- "apply",
- tag,
- extra.valueReferences,
- t.expressionStatement(dynamicExpr!)
- );
- }
}
}
diff --git a/packages/translator/src/util/writer.ts b/packages/translator/src/util/writer.ts
index d350be2e4..106439461 100644
--- a/packages/translator/src/util/writer.ts
+++ b/packages/translator/src/util/writer.ts
@@ -2,8 +2,9 @@ import { types as t } from "@marko/compiler";
import {
compareReferences,
References,
- Reference,
+ Binding,
} from "../analyze/util/references";
+import type { Reserve } from "../analyze/util/reserves";
import * as sorted from "../util/sorted-arr";
import { isOutputHTML } from "./marko-config";
import { callRuntime } from "./runtime";
@@ -95,8 +96,8 @@ export function end(path: t.NodePath) {
if (isOutputHTML(path)) {
flushInto(path);
} else {
- writeReferenceGroups(path, section.apply);
- writeReferenceGroups(path, section.hydrate);
+ writeApplyGroups(path, section.apply);
+ writeHydrateGroups(path, section.hydrate);
}
path.state.section = section.parent;
@@ -192,7 +193,7 @@ export function writeTo(path: t.NodePath) {
return (
strs: TemplateStringsArray,
...exprs: Array
- ) => {
+ ): void => {
const exprsLen = exprs.length;
const { writes } = path.state.section as Section;
writes[writes.length - 1] += strs[0];
@@ -268,18 +269,27 @@ export function addStatement(
compareReferenceGroups
);
+ if (type === "hydrate") {
+ addStatement(
+ "apply",
+ path,
+ references,
+ t.expressionStatement(t.callExpression(identifier, []))
+ );
+ }
+
if (Array.isArray(references)) {
- for (const ref of references) {
+ for (const binding of references) {
addStatement(
type,
path,
- ref,
+ binding,
t.expressionStatement(
callRuntime(
path,
"queue",
identifier,
- t.numericLiteral(bindingToScopeId(path, ref))
+ t.numericLiteral(bindingToScopeId(path, binding))
)
)
);
@@ -296,23 +306,23 @@ export function addStatement(
}
}
-export function getApplyId(path: t.NodePath, ref: Reference) {
+export function getApplyId(path: t.NodePath, binding: Binding) {
const section = path.state.section as Section;
const groupIndex = sorted.findIndex(
section.apply,
- { references: [ref] } as ReferenceGroup,
+ { references: [binding] } as ReferenceGroup,
compareReferenceGroups
);
if (groupIndex === -1) {
const identifier = t.identifier(
- generateReferenceGroupName("apply", path, ref)
+ generateReferenceGroupName("apply", path, binding)
);
sorted.insert(
section.apply,
{
identifier,
- references: ref,
+ references: binding,
statements: [],
},
compareReferenceGroups
@@ -323,7 +333,7 @@ export function getApplyId(path: t.NodePath, ref: Reference) {
}
}
-function writeReferenceGroups(path: t.NodePath, groups: ReferenceGroup[]) {
+function writeApplyGroups(path: t.NodePath, groups: ReferenceGroup[]) {
const program = path.hub.file.path;
for (const { identifier, references, statements } of groups) {
let params: (t.Identifier | t.RestElement | t.Pattern)[];
@@ -331,13 +341,13 @@ function writeReferenceGroups(path: t.NodePath, groups: ReferenceGroup[]) {
if (references) {
if (Array.isArray(references)) {
- params = references.map((ref) =>
+ params = references.map((binding) =>
t.assignmentPattern(
- t.identifier(ref.name),
+ t.identifier(binding.name),
callRuntime(
path,
"read",
- t.numericLiteral(bindingToScopeId(path, ref))
+ t.numericLiteral(bindingToScopeId(path, binding))
)
)
);
@@ -371,6 +381,29 @@ function writeReferenceGroups(path: t.NodePath, groups: ReferenceGroup[]) {
}
}
+function writeHydrateGroups(path: t.NodePath, groups: ReferenceGroup[]) {
+ const program = path.hub.file.path;
+ for (const { identifier, references, statements } of groups) {
+ const params = references
+ ? (Array.isArray(references) ? references : [references]).map((binding) =>
+ t.assignmentPattern(
+ t.identifier(binding.name),
+ callRuntime(
+ path,
+ "read",
+ t.numericLiteral(bindingToScopeId(path, binding))
+ )
+ )
+ )
+ : [];
+
+ program.pushContainer(
+ "body",
+ t.functionDeclaration(identifier, params, t.blockStatement(statements))
+ );
+ }
+}
+
/**
* reference group priority is sorted by number of references,
* then if needed by reference order.
@@ -445,13 +478,23 @@ function toCharString(number: number, startCode: number, rangeSize: number) {
return result;
}
-function bindingToScopeId(path: t.NodePath, ref: Reference) {
+export function bindingToScopeId(
+ path: t.NodePath,
+ { sectionIndex, bindingIndex }: Binding
+) {
return (
- path.hub.file.path.node.extra.sections![ref.sectionIndex!].visits +
- ref.bindingIndex!
+ path.hub.file.path.node.extra.sections![sectionIndex].visits + bindingIndex
);
}
+export function reserveToScopeId(
+ path: t.NodePath,
+ { sectionIndex, reserveIndex }: Reserve
+) {
+ const section = path.hub.file.path.node.extra.sections![sectionIndex];
+ return section.visits + section.bindings + reserveIndex;
+}
+
function generateReferenceGroupName(
type: "apply" | "hydrate",
path: t.NodePath,