diff --git a/packages/runtime/src/common/helpers.ts b/packages/runtime/src/common/helpers.ts
index 6857b3ab3..3cf748faa 100644
--- a/packages/runtime/src/common/helpers.ts
+++ b/packages/runtime/src/common/helpers.ts
@@ -69,3 +69,9 @@ function toDelimitedString(
export function isVoid(value: unknown) {
return value == null || value === false;
}
+
+export function alphaEncode(num: number): string {
+ return num < 52
+ ? String.fromCharCode(num < 26 ? num + 97 : num + (65 - 26))
+ : alphaEncode((num / 52) | 0) + alphaEncode(num % 52);
+}
diff --git a/packages/translator/src/__tests__/fixtures/at-tags-dynamic-with-params/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/at-tags-dynamic-with-params/__snapshots__/dom.expected.js
index 416e2a179..0615b4737 100644
--- a/packages/translator/src/__tests__/fixtures/at-tags-dynamic-with-params/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/at-tags-dynamic-with-params/__snapshots__/dom.expected.js
@@ -3,7 +3,7 @@ let _item;
import { data as _data, write as _write, setConditionalRenderer as _setConditionalRenderer, createRenderer as _createRenderer, createRenderFn as _createRenderFn } from "@marko/runtime-fluurt/src/dom";
import { apply as _hello, template as _hello_template, walks as _hello_walks } from "./components/hello/index.marko";
-function _apply(_scope) {
+function _apply4(_scope) {
_data(_scope[0], y);
}
@@ -11,7 +11,7 @@ function _apply1_x(_scope, x = _scope._[1]) {
_setConditionalRenderer(_scope, 0, x ? _if : null);
}
-function _apply2(_scope) {
+function _apply(_scope) {
_hello(_scope[0]);
}
@@ -21,7 +21,7 @@ const _temp2 = _createRenderer("",
_if = _createRenderer("", "", null),
_temp3 = _createRenderer("",
/* get */
-" ", _apply);
+" ", _apply4);
export const applyAttrs = function (_scope, {
x
@@ -33,5 +33,5 @@ export const template = `${_hello_template}`;
export const walks =
/* beginChild(0), _hello_walks, endChild */
`/${_hello_walks}&`;
-export const apply = _apply2;
+export const apply = _apply;
export default _createRenderFn(template, walks, apply, applyAttrs);
\ No newline at end of file
diff --git a/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.error.js b/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.error.js
deleted file mode 100644
index 23031915b..000000000
--- a/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.error.js
+++ /dev/null
@@ -1,6 +0,0 @@
-packages/translator/src/__tests__/fixtures/attr-class/template.marko(12,4): Dynamic @tags cannot be mixed with body content.
- 10 |
- 11 | <${input.test} class=["a", { b: c, d }]>
-> 12 | <@test class=["a", { b: c, d }]>Hello@test>
- | ^^^^^
- 13 | >
\ No newline at end of file
diff --git a/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.js
index ae20a5e83..68daefd7c 100644
--- a/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.js
@@ -1,15 +1,4 @@
-_customTag({
- class: ["a", {
- b: c,
- d
- }]
-});
-
-_customTag({
- class: ["a", false, "b"]
-});
-
-_dynamicTag(input.test, {
+_dynamicTag(_scope, input.test, {
class: ["a", {
b: c,
d
@@ -27,11 +16,18 @@ _dynamicTag(input.test, {
}
});
-import { classAttr as _classAttr, queue as _queue, write as _write, dynamicTag as _dynamicTag, read as _read, createRenderFn as _createRenderFn } from "@marko/runtime-fluurt/src/dom";
-import { hydrate as _customTag, template as _customTag_template, walks as _customTag_walks } from "./components/custom-tag.marko";
+import { classAttr as _classAttr, write as _write, dynamicTag as _dynamicTag, queue as _queue, createRenderer as _createRenderer, createRenderFn as _createRenderFn } from "@marko/runtime-fluurt/src/dom";
+import { apply as _customTag, template as _customTag_template, walks as _customTag_walks } from "./components/custom-tag.marko";
-function _apply_input(input) {
- if (_write(1, input)) {
+function _applyWith_c_d(_scope, c = _scope[1], d = _scope[2]) {
+ _classAttr(_scope[0], ["a", {
+ b: c,
+ d
+ }]);
+}
+
+function _apply_input(_scope, input) {
+ if (_write(_scope, 5, input)) {
const {
c,
d
@@ -43,22 +39,29 @@ function _apply_input(input) {
}
}
-function _apply_c(c) {
- if (_write(2, c)) _queue(_applyWith_d_c, 2);
+function _apply_d(_scope, d) {
+ if (_write(_scope, 2, d)) _queue(_scope, _applyWith_c_d, 3);
}
-function _apply_d(d) {
- if (_write(3, d)) _queue(_applyWith_d_c, 3);
+function _apply_c(_scope, c) {
+ if (_write(_scope, 1, c)) _queue(_scope, _applyWith_c_d, 3);
}
-function _applyWith_d_c(d = _read(3), c = _read(2)) {
- _classAttr(0, ["a", {
- b: c,
- d
- }]);
+function _apply(_scope) {
+ _customTag(_scope[3]);
+
+ _customTag(_scope[4]);
}
+const _temp = _createRenderer("", "", null);
+
+export const applyAttrs = function (_scope, input) {
+ _apply_input(_scope, input);
+};
+export { _apply_input };
export const template = `
${_customTag_template}${_customTag_template}`;
-export const walks = ` ${_customTag_walks}${_customTag_walks}`;
-export const apply = null;
-export default _createRenderFn(template, walks, apply);
\ No newline at end of file
+export const walks =
+/* get, over(3), beginChild(3), _customTag_walks, endChild, beginChild(4), _customTag_walks, endChild */
+` d2${_customTag_walks}&3${_customTag_walks}&`;
+export const apply = _apply;
+export default _createRenderFn(template, walks, apply, applyAttrs);
\ No newline at end of file
diff --git a/packages/translator/src/__tests__/fixtures/basic-execution-order/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/basic-execution-order/__snapshots__/dom.expected.js
index 074aa7751..ec1b0e5a7 100644
--- a/packages/translator/src/__tests__/fixtures/basic-execution-order/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/basic-execution-order/__snapshots__/dom.expected.js
@@ -25,7 +25,7 @@ function _apply_show(_scope, show) {
}
function _apply_message(_scope, message) {
- if (_write(_scope, 5, message)) _queueInBranch(_scope, 1, _if, _apply1_message, 0, 1);
+ if (_write(_scope, 5, message)) _queueInBranch(_scope, 1, _if, _apply1_message, 1, 2);
}
function _apply(_scope) {
diff --git a/packages/translator/src/__tests__/fixtures/basic-inert-collapsible-tree/__snapshots__/dom.expected/components/comments.js b/packages/translator/src/__tests__/fixtures/basic-inert-collapsible-tree/__snapshots__/dom.expected/components/comments.js
index b38553888..5543b9e50 100644
--- a/packages/translator/src/__tests__/fixtures/basic-inert-collapsible-tree/__snapshots__/dom.expected/components/comments.js
+++ b/packages/translator/src/__tests__/fixtures/basic-inert-collapsible-tree/__snapshots__/dom.expected/components/comments.js
@@ -16,7 +16,7 @@ function _apply2_comment(_scope, comment = _scope._[8]) {
_queue(_scope, _apply2With_comment_id, 2);
}
-function _apply2(_scope) {
+function _apply3(_scope) {
_comments(_scope[0]);
_queue(_scope, _apply2_id, 1);
@@ -54,7 +54,7 @@ function _apply1_id(_scope, id) {
if (_write(_scope, 10, id)) {
_attr(_scope[0], "id", id);
- _queueInBranch(_scope, 4, _if, _apply2_id, 1, 3);
+ _queueInBranch(_scope, 4, _if, _apply2_id, 2, 4);
}
}
@@ -68,7 +68,7 @@ function _apply1_comment(_scope, comment) {
_setConditionalRenderer(_scope, 4, comment.comments ? _if : null);
- _queueInBranch(_scope, 4, _if, _apply2_comment, 0, 4);
+ _queueInBranch(_scope, 4, _if, _apply2_comment, 1, 5);
}
}
@@ -76,24 +76,14 @@ function _apply1_path(_scope, path = _scope._[5]) {
_queue(_scope, _apply1With_path_i, 5);
}
-function _apply1_path(_scope, path = _scope._[5]) {
- _queue(_scope, _apply1With_path_i, 5);
-}
-
-function _apply(_scope) {
+function _apply2(_scope) {
_apply1_open(_scope, true);
_queue(_scope, _apply1_path, 0);
-
- _queue(_scope, _apply1_path, 0);
}
function _apply_path(_scope, path) {
- if (_write(_scope, 5, path)) {
- _queueForEach(_scope, 0, _apply1_path, 0, 6);
-
- _queueForEach(_scope, 0, _apply1_path, 0, 7);
- }
+ if (_write(_scope, 5, path)) _queueForEach(_scope, 0, _apply1_path, 1, 7);
}
function _apply_comments(_scope, comments) {
@@ -102,10 +92,10 @@ function _apply_comments(_scope, comments) {
const _for = _createRenderer(" ",
/* get, next(2), get, out(1), get, next(1), get, out(1), replace, skip(3) */
-" E l D l%+", _apply),
+" E l D l%+", _apply2),
_if = _createRenderer(`${_comments_template}`,
/* beginChild(0), _comments_walks, endChild */
-`/${_comments_walks}&`, _apply2);
+`/${_comments_walks}&`, _apply3);
export const applyAttrs = function (_scope, {
comments,
diff --git a/packages/translator/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/dom.expected.js
index 16339b23f..85f6403be 100644
--- a/packages/translator/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/basic-nested-scope-for/__snapshots__/dom.expected.js
@@ -37,7 +37,7 @@ function _apply2(_scope) {
}
function _apply_selected(_scope, selected) {
- if (_write(_scope, 4, selected)) _queueForEach(_scope, 0, _apply1_selected, 0, 3);
+ if (_write(_scope, 4, selected)) _queueForEach(_scope, 0, _apply1_selected, 1, 4);
}
function _apply(_scope) {
diff --git a/packages/translator/src/__tests__/fixtures/basic-nested-scope-if/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/basic-nested-scope-if/__snapshots__/dom.expected.js
index 1496e6109..013bab449 100644
--- a/packages/translator/src/__tests__/fixtures/basic-nested-scope-if/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/basic-nested-scope-if/__snapshots__/dom.expected.js
@@ -26,7 +26,7 @@ function _apply_clickCount(_scope, clickCount) {
if (_write(_scope, 4, clickCount)) {
_setConditionalRenderer(_scope, 0, clickCount < 3 ? _if : null);
- _queueInBranch(_scope, 0, _if, _apply1_clickCount, 0, 1);
+ _queueInBranch(_scope, 0, _if, _apply1_clickCount, 1, 2);
}
}
diff --git a/packages/translator/src/__tests__/fixtures/dynamic-tag-name/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/dynamic-tag-name/__snapshots__/dom.expected.js
index 9f2241158..e8f3fcf17 100644
--- a/packages/translator/src/__tests__/fixtures/dynamic-tag-name/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/dynamic-tag-name/__snapshots__/dom.expected.js
@@ -94,10 +94,22 @@ function _apply_isLarge(_scope, isLarge) {
if (_write(_scope, 16, isLarge)) _apply_largeHeading(_scope, isLarge && "h1");
}
+function _apply_showTagA(_scope, showTagA) {
+ if (_write(_scope, 15, showTagA)) {}
+}
+
function _apply_show(_scope, show) {
if (_write(_scope, 14, show)) _apply_tagConstB(_scope, show ? "div" : null);
}
+function _apply_x(_scope, x) {
+ if (_write(_scope, 13, x)) {}
+}
+
+function _apply_renderBody(_scope, renderBody) {
+ if (_write(_scope, 12, renderBody)) {}
+}
+
function _apply_tagConstB(_scope, tagConstB) {
if (_write(_scope, 11, tagConstB)) {}
}
diff --git a/packages/translator/src/__tests__/fixtures/for-tag/__snapshots__/dom.expected.js b/packages/translator/src/__tests__/fixtures/for-tag/__snapshots__/dom.expected.js
index 8a54cb884..d898d5660 100644
--- a/packages/translator/src/__tests__/fixtures/for-tag/__snapshots__/dom.expected.js
+++ b/packages/translator/src/__tests__/fixtures/for-tag/__snapshots__/dom.expected.js
@@ -99,10 +99,6 @@ function _apply1_val(_scope, val) {
if (_write(_scope, 2, val)) _data(_scope[1], val);
}
-function _apply_obj(_scope, obj) {
- if (_write(_scope, 41, obj)) {}
-}
-
function _apply_arr(_scope, arr) {
if (_write(_scope, 40, arr)) {
_setLoopOf(_scope, 0, arr, _for, null, _apply1_val);
diff --git a/packages/translator/src/core/attrs.ts b/packages/translator/src/core/attrs.ts
index cfc7b3c91..968417f1a 100644
--- a/packages/translator/src/core/attrs.ts
+++ b/packages/translator/src/core/attrs.ts
@@ -1,6 +1,9 @@
import type { types as t } from "@marko/compiler";
import type { Tag } from "@marko/babel-utils";
-import { trackReferencesForBindings } from "../util/references";
+import {
+ getReferenceGroup,
+ trackReferencesForBindings,
+} from "../util/references";
import { ReserveType } from "../util/reserve";
import { getOrCreateSectionId } from "../util/sections";
import { currentProgramPath } from "../visitors/program";
@@ -22,14 +25,15 @@ export default {
string,
t.Identifier
>;
- trackReferencesForBindings(
- getOrCreateSectionId(tag),
- varPath,
- ReserveType.Attr
- );
+ const sectionId = getOrCreateSectionId(tag);
+ trackReferencesForBindings(sectionId, varPath, ReserveType.Attr);
for (const key in bindings) {
- bindings[key].extra!.reserve!.exportName =
- currentProgramPath.scope.generateUid("apply_" + key);
+ const binding = bindings[key].extra!.reserve!;
+ binding!.exportIdentifier = getReferenceGroup(
+ sectionId,
+ binding,
+ true
+ ).apply;
}
(currentProgramPath.node.extra ??= {}).attrs = {
bindings,
diff --git a/packages/translator/src/core/condition/else-if.ts b/packages/translator/src/core/condition/else-if.ts
index 81fa1fd40..930be8523 100644
--- a/packages/translator/src/core/condition/else-if.ts
+++ b/packages/translator/src/core/condition/else-if.ts
@@ -1,8 +1,17 @@
import { types as t } from "@marko/compiler";
import { Tag, assertNoParams, assertNoVar } from "@marko/babel-utils";
-import { exitBranch } from "./if";
+import { exitBranchTranslate, exitBranchAnalyze } from "./if";
+import customTag from "../../visitors/tag/custom-tag";
export default {
+ analyze: {
+ enter(tag) {
+ customTag.analyze.enter(tag);
+ },
+ exit(tag) {
+ exitBranchAnalyze(tag);
+ },
+ },
translate: {
enter(tag) {
const { node } = tag;
@@ -36,7 +45,7 @@ export default {
}
},
exit(tag) {
- exitBranch(tag);
+ exitBranchTranslate(tag);
},
},
attributes: {},
diff --git a/packages/translator/src/core/condition/else.ts b/packages/translator/src/core/condition/else.ts
index 08534e734..54050e8ae 100644
--- a/packages/translator/src/core/condition/else.ts
+++ b/packages/translator/src/core/condition/else.ts
@@ -1,8 +1,17 @@
import type { types as t } from "@marko/compiler";
import { Tag, assertNoParams, assertNoVar } from "@marko/babel-utils";
-import { exitBranch } from "./if";
+import { exitBranchTranslate, exitBranchAnalyze } from "./if";
+import customTag from "../../visitors/tag/custom-tag";
export default {
+ analyze: {
+ enter(tag) {
+ customTag.analyze.enter(tag);
+ },
+ exit(tag) {
+ exitBranchAnalyze(tag);
+ },
+ },
translate: {
enter(tag) {
const { node } = tag;
@@ -31,7 +40,7 @@ export default {
}
},
exit(tag) {
- exitBranch(tag);
+ exitBranchTranslate(tag);
},
},
attributes: {},
diff --git a/packages/translator/src/core/condition/if.ts b/packages/translator/src/core/condition/if.ts
index ba2576323..afde2a4ee 100644
--- a/packages/translator/src/core/condition/if.ts
+++ b/packages/translator/src/core/condition/if.ts
@@ -2,7 +2,6 @@ import { types as t } from "@marko/compiler";
import { Tag, assertNoParams, assertNoVar } from "@marko/babel-utils";
import * as writer from "../../util/writer";
import * as walks from "../../util/walks";
-import * as sorted from "../../util/sorted-arr";
import {
addStatement,
setQueueBuilder,
@@ -12,16 +11,12 @@ import { callRuntime } from "../../util/runtime";
import { isCoreTagName } from "../../util/is-core-tag";
import toFirstStatementOrBlock from "../../util/to-first-statement-or-block";
import { getOrCreateSectionId, getSectionId } from "../../util/sections";
-import {
- Reserve,
- ReserveType,
- reserveScope,
- compareReserves,
-} from "../../util/reserve";
+import { ReserveType, reserveScope } from "../../util/reserve";
import { isOutputDOM, isOutputHTML } from "../../util/marko-config";
import analyzeAttributeTags from "../../util/nested-attribute-tags";
import customTag from "../../visitors/tag/custom-tag";
import { scopeIdentifier } from "../../visitors/program";
+import { mergeReferenceGroups, ReferenceGroup } from "../../util/references";
export default {
analyze: {
@@ -37,6 +32,7 @@ export default {
},
exit(tag) {
analyzeAttributeTags(tag);
+ exitBranchAnalyze(tag);
},
},
translate: {
@@ -78,7 +74,7 @@ export default {
}
},
exit(tag) {
- exitBranch(tag);
+ exitBranchTranslate(tag);
},
},
attributes: {},
@@ -99,29 +95,54 @@ const BRANCHES_LOOKUP = new WeakMap<
}[]
>();
-export function exitBranch(tag: t.NodePath) {
- const tagBody = tag.get("body");
- const bodySectionId = getSectionId(tagBody);
+function getBranches(tag: t.NodePath, bodySectionId: number) {
+ const branches = BRANCHES_LOOKUP.get(tag) ?? [];
const nextTag = tag.getNextSibling();
const isLast = !(
isCoreTagName(nextTag, "else") || isCoreTagName(nextTag, "else-if")
);
- const branches = BRANCHES_LOOKUP.get(tag) || [];
- const reserve = tag.node.extra.reserve!;
branches.push({
tag,
sectionId: bodySectionId,
});
- setQueueBuilder(tag, ({ identifier, queuePriority }, closurePriority) =>
+ if (!isLast) {
+ BRANCHES_LOOKUP.set(nextTag as t.NodePath, branches);
+ }
+
+ return [isLast, branches] as const;
+}
+
+export function exitBranchAnalyze(tag: t.NodePath) {
+ const sectionId = getOrCreateSectionId(tag);
+ const tagBody = tag.get("body");
+ const bodySectionId = getOrCreateSectionId(tagBody);
+ const [isLast, branches] = getBranches(tag, bodySectionId);
+ if (isLast) {
+ branches[0].tag.node.extra.conditionalReferences = mergeReferenceGroups(
+ sectionId,
+ branches
+ .filter(({ tag }) => tag.node.attributes[0]?.extra?.valueReferences)
+ .map(({ tag }) => [tag.node.attributes[0].extra, "valueReferences"])
+ );
+ }
+}
+
+export function exitBranchTranslate(tag: t.NodePath) {
+ const tagBody = tag.get("body");
+ const bodySectionId = getSectionId(tagBody);
+ const reserve = tag.node.extra.reserve!;
+ const [isLast, branches] = getBranches(tag, bodySectionId);
+
+ setQueueBuilder(tag, ({ apply, index }, closurePriority) =>
callRuntime(
"queueInBranch",
scopeIdentifier,
t.numericLiteral(reserve.id),
writer.getRenderer(bodySectionId),
- identifier,
- queuePriority,
+ apply,
+ t.numericLiteral(index),
closurePriority
)
);
@@ -135,7 +156,6 @@ export function exitBranch(tag: t.NodePath) {
if (isOutputDOM()) {
const sectionId = getSectionId(tag);
const { extra } = branches[0].tag.node;
- const refs: Reserve[] = [];
let expr: t.Expression = t.nullLiteral();
for (let i = branches.length; i--; ) {
@@ -146,17 +166,6 @@ export function exitBranch(tag: t.NodePath) {
tag.remove();
if (testAttr) {
- const curRefs = testAttr.extra.valueReferences;
- if (curRefs) {
- if (Array.isArray(curRefs)) {
- for (const ref of curRefs) {
- sorted.insert(compareReserves, refs, ref);
- }
- } else {
- sorted.insert(compareReserves, refs, curRefs);
- }
- }
-
expr = t.conditionalExpression(testAttr.value, id, expr);
} else {
expr = id;
@@ -166,7 +175,9 @@ export function exitBranch(tag: t.NodePath) {
addStatement(
"apply",
sectionId,
- refs.length === 0 ? undefined : refs.length === 1 ? refs[0] : refs,
+ // TODO: It's possible this group may not exist because the `refs`,
+ // created above, is a unique group that wasn't found during analyze
+ extra.conditionalReferences as ReferenceGroup,
t.expressionStatement(
callRuntime(
"setConditionalRenderer",
@@ -177,6 +188,8 @@ export function exitBranch(tag: t.NodePath) {
)
);
} else {
+ const nextTag = tag.getNextSibling();
+
let statement: t.Statement | undefined;
for (let i = branches.length; i--; ) {
const { tag } = branches[i];
@@ -194,7 +207,5 @@ export function exitBranch(tag: t.NodePath) {
nextTag.insertBefore(statement!);
}
- } else {
- BRANCHES_LOOKUP.set(nextTag as t.NodePath, branches);
}
}
diff --git a/packages/translator/src/core/const.ts b/packages/translator/src/core/const.ts
index 753839911..a7c98a0a0 100644
--- a/packages/translator/src/core/const.ts
+++ b/packages/translator/src/core/const.ts
@@ -4,7 +4,8 @@ import { assertNoBodyContent } from "../util/assert";
import translateVar from "../util/translate-var";
import { isOutputDOM } from "../util/marko-config";
import { getSectionId } from "../util/sections";
-import { addStatement, bindingToApplyGroup } from "../util/apply-hydrate";
+import { addStatement } from "../util/apply-hydrate";
+import { getReferenceGroup } from "../util/references";
import { scopeIdentifier } from "../visitors/program";
export default {
@@ -51,8 +52,8 @@ export default {
identifiers.length === 1
? t.expressionStatement(
t.callExpression(
- bindingToApplyGroup(identifiers[0].extra.reserve!, sectionId)
- .identifier,
+ getReferenceGroup(sectionId, identifiers[0].extra.reserve)
+ .apply,
[scopeIdentifier, defaultAttr.value]
)
)
@@ -63,8 +64,8 @@ export default {
...identifiers.map((identifier) =>
t.expressionStatement(
t.callExpression(
- bindingToApplyGroup(identifier.extra.reserve!, sectionId)
- .identifier,
+ getReferenceGroup(sectionId, identifier.extra.reserve)
+ .apply,
[t.identifier(identifier.name)]
)
)
diff --git a/packages/translator/src/core/effect.ts b/packages/translator/src/core/effect.ts
index 2296ddede..f9f34ed0c 100644
--- a/packages/translator/src/core/effect.ts
+++ b/packages/translator/src/core/effect.ts
@@ -2,10 +2,7 @@ import { types as t } from "@marko/compiler";
import { Tag, assertNoParams } from "@marko/babel-utils";
import { assertNoBodyContent } from "../util/assert";
import { isOutputDOM } from "../util/marko-config";
-import {
- addStatement,
- ensureHydrateReferenceGroup,
-} from "../util/apply-hydrate";
+import { addStatement, addHTMLHydrateCall } from "../util/apply-hydrate";
import { callRuntime } from "../util/runtime";
import { getSectionId } from "../util/sections";
import { ReserveType, reserveScope } from "../util/reserve";
@@ -58,10 +55,7 @@ export default {
)
);
} else {
- ensureHydrateReferenceGroup(
- sectionId,
- defaultAttr.extra?.valueReferences
- );
+ addHTMLHydrateCall(sectionId, defaultAttr.extra?.valueReferences);
}
tag.remove();
diff --git a/packages/translator/src/core/for.ts b/packages/translator/src/core/for.ts
index 1fe0bc94c..d95b3c5f4 100644
--- a/packages/translator/src/core/for.ts
+++ b/packages/translator/src/core/for.ts
@@ -10,7 +10,6 @@ import * as writer from "../util/writer";
import * as walks from "../util/walks";
import {
addStatement,
- bindingToApplyGroup,
setQueueBuilder,
writeHTMLHydrateStatements,
} from "../util/apply-hydrate";
@@ -20,6 +19,7 @@ import { callRuntime } from "../util/runtime";
import analyzeAttributeTags from "../util/nested-attribute-tags";
import customTag from "../visitors/tag/custom-tag";
import { scopeIdentifier } from "../visitors/program";
+import { getReferenceGroup } from "../util/references";
export default {
analyze: {
@@ -150,13 +150,13 @@ const translateDOM = {
const ofAttr = findName(attributes, "of");
const byAttr = findName(attributes, "by");
- setQueueBuilder(tag, ({ identifier, queuePriority }, closurePriority) => {
+ setQueueBuilder(tag, ({ apply, index }, closurePriority) => {
return callRuntime(
"queueForEach",
scopeIdentifier,
t.numericLiteral(reserve!.id),
- identifier,
- queuePriority,
+ apply,
+ t.numericLiteral(index),
closurePriority
);
});
@@ -189,8 +189,7 @@ const translateDOM = {
ofAttrValue,
rendererId,
byAttr ? byAttr.value! : t.nullLiteral(),
- bindingToApplyGroup(valParam.extra.reserve!, bodySectionId)
- .identifier
+ getReferenceGroup(bodySectionId, valParam.extra.reserve).apply
)
)
);
diff --git a/packages/translator/src/core/let.ts b/packages/translator/src/core/let.ts
index 6e6417e8f..59e274e76 100644
--- a/packages/translator/src/core/let.ts
+++ b/packages/translator/src/core/let.ts
@@ -3,11 +3,12 @@ import { Tag, assertNoParams } from "@marko/babel-utils";
import { assertNoBodyContent } from "../util/assert";
import translateVar from "../util/translate-var";
import { isOutputDOM } from "../util/marko-config";
-import { addStatement, bindingToApplyGroup } from "../util/apply-hydrate";
+import { addStatement } from "../util/apply-hydrate";
import { callQueue } from "../util/runtime";
import replaceAssignments from "../util/replace-assignments";
import { getSectionId } from "../util/sections";
import { scopeIdentifier } from "../visitors/program";
+import { getReferenceGroup } from "../util/references";
export default {
translate(tag) {
@@ -51,22 +52,24 @@ export default {
if (isOutputDOM()) {
const sectionId = getSectionId(tag);
const binding = tagVar.extra.reserve!;
- const applyGroup = bindingToApplyGroup(binding, sectionId);
- const applyId = applyGroup.identifier;
+ const referenceGroup = getReferenceGroup(sectionId, binding);
// TODO: add defined guard if bindings exist.
addStatement(
"apply",
sectionId,
defaultAttr.extra?.valueReferences,
t.expressionStatement(
- t.callExpression(applyId, [scopeIdentifier, defaultAttr.value])
+ t.callExpression(referenceGroup.apply, [
+ scopeIdentifier,
+ defaultAttr.value,
+ ])
)
);
replaceAssignments(
tag.scope.getBinding(binding.name)!,
(assignment, value) =>
- callQueue(applyGroup, binding, value, getSectionId(assignment))
+ callQueue(referenceGroup, binding, value, getSectionId(assignment))
);
} else {
translateVar(tag, defaultAttr.value);
diff --git a/packages/translator/src/util/apply-hydrate.ts b/packages/translator/src/util/apply-hydrate.ts
index b5d6bdb9f..deb27c1f0 100644
--- a/packages/translator/src/util/apply-hydrate.ts
+++ b/packages/translator/src/util/apply-hydrate.ts
@@ -1,31 +1,27 @@
import { types as t } from "@marko/compiler";
-import type { References } from "../util/references";
+import { getReferenceGroup, ReferenceGroup } from "../util/references";
import {
getSectionId,
createSectionState,
forEachSectionIdReverse,
getOrCreateSectionId,
} from "../util/sections";
-import { Reserve, compareReserves } from "../util/reserve";
-import * as sorted from "../util/sorted-arr";
+import { Reserve, insertReserve } from "../util/reserve";
import { currentProgramPath, scopeIdentifier } from "../visitors/program";
import { callRuntime, callRead } from "./runtime";
import { getTemplateId } from "@marko/babel-utils";
-export interface ReferenceGroup {
- identifier: t.Identifier;
- references: References;
- statements: t.Statement[];
- queuePriority: t.NumericLiteral;
-}
-
export type queueBuilder = (
group: ReferenceGroup,
closurePriority: t.NumericLiteral
) => t.Expression;
-const [getApply] = createSectionState("apply", () => []);
-const [getHydrate] = createSectionState("hydrate", () => []);
+const [getApplyStatements] = createSectionState<
+ Array
+>("applyStatements", () => []);
+const [getHydrateStatements] = createSectionState<
+ Array
+>("hydrateStatements", () => []);
const [getQueueBuilder, _setQueueBuilder] =
createSectionState("queue");
@@ -39,46 +35,27 @@ export function setQueueBuilder(
export function addStatement(
type: "apply" | "hydrate",
targetSectionId: number,
- references: References,
+ references: ReferenceGroup | undefined,
statement: t.Statement | t.Statement[]
) {
- const groups =
- type === "apply" ? getApply(targetSectionId) : getHydrate(targetSectionId);
- const existingGroup = getGroupByReferences(groups, references);
- const isNew = !existingGroup;
- const { statements } = isNew
- ? createAndInsertGroup(type, groups, targetSectionId, references)
- : existingGroup;
+ const statementsIndex = references?.index ?? 0;
+ const allStatements =
+ type === "apply"
+ ? getApplyStatements(targetSectionId)
+ : getHydrateStatements(targetSectionId);
+ const statements = (allStatements[statementsIndex] ??= []);
if (Array.isArray(statement)) {
statements.push(...statement);
} else {
statements.push(statement);
}
- return isNew ? 1 : 0;
}
-export function ensureHydrateReferenceGroup(
- targetSectionId: number,
- references: References
+function getHydrateRegisterId(
+ sectionId: number,
+ references: ReferenceGroup["references"]
) {
- const groups = getHydrate(targetSectionId);
- const existingGroup = getGroupByReferences(groups, references);
- if (!existingGroup) {
- const identifier = t.identifier(
- generateReferenceGroupName("hydrate", targetSectionId, references)
- );
- const group: ReferenceGroup = {
- identifier,
- references,
- statements: [],
- queuePriority: t.numericLiteral(-1),
- };
- sorted.insert(compareReferenceGroups, groups, group);
- }
-}
-
-function getHydrateRegisterId(sectionId: number, references: References) {
const {
markoOpts: { optimize },
opts: { filename },
@@ -96,43 +73,6 @@ function getHydrateRegisterId(sectionId: number, references: References) {
return `${getTemplateId(optimize, filename as string)}_${sectionId}${name}`;
}
-export function bindingToApplyGroup(binding: Reserve, sectionId: number) {
- const applyGroups = getApply(sectionId);
- const group =
- getGroupByReferences(applyGroups, binding) ??
- createAndInsertGroup("apply", applyGroups, sectionId, binding);
- return group;
-}
-
-function createAndInsertGroup(
- type: "apply" | "hydrate",
- groups: ReferenceGroup[],
- sectionId: number,
- references: References
-) {
- const identifier = t.identifier(
- generateReferenceGroupName(type, sectionId, references)
- );
- const group: ReferenceGroup = {
- identifier,
- references,
- statements: [],
- queuePriority: t.numericLiteral(NaN),
- };
- sorted.insert(compareReferenceGroups, groups, group);
- return group;
-}
-
-function getGroupByReferences(
- groups: ReferenceGroup[],
- references: References
-) {
- const groupIndex = sorted.findIndex(compareReferenceGroups, groups, {
- references,
- } as ReferenceGroup);
- return groups[groupIndex];
-}
-
export function writeAllStatementGroups() {
forEachSectionIdReverse((sectionId) => {
writeHydrateGroups(sectionId);
@@ -141,14 +81,19 @@ export function writeAllStatementGroups() {
}
export function writeApplyGroups(sectionId: number) {
- const groups = getApply(sectionId);
- if (!groups.length) return;
+ const allStatements = getApplyStatements(sectionId);
+ if (!allStatements.length) return;
const closurePriorities = [];
- for (let i = groups.length; i--; ) {
- const group = groups[i];
- const { identifier, references, statements, queuePriority } = group;
+ for (let i = allStatements.length; i--; ) {
+ const statements = allStatements[i] ?? [];
+
+ if (i === 0 && !statements.length) continue;
+
+ const referenceGroup = getReferenceGroup(sectionId, i);
+ const { references, apply: identifier } = referenceGroup;
+ const queuePriority = t.numericLiteral(i - 1);
let params: (t.Identifier | t.RestElement | t.Pattern)[];
let body: t.BlockStatement;
@@ -163,10 +108,10 @@ export function writeApplyGroups(sectionId: number) {
body = t.blockStatement(statements);
for (const binding of references) {
- i += addStatement(
+ addStatement(
"apply",
sectionId,
- binding,
+ getReferenceGroup(sectionId, binding),
t.expressionStatement(
// TODO: might need to queue in a child scope
callRuntime("queue", scopeIdentifier, identifier, queuePriority)
@@ -186,13 +131,13 @@ export function writeApplyGroups(sectionId: number) {
if (factory) {
const closurePriority = t.numericLiteral(NaN);
closurePriorities.push(closurePriority);
- i += addStatement(
+ addStatement(
"apply",
references.sectionId,
- references,
- t.expressionStatement(factory(group, closurePriority))
+ getReferenceGroup(references.sectionId, references),
+ t.expressionStatement(factory(referenceGroup, closurePriority))
);
- i += addStatement(
+ addStatement(
"apply",
sectionId,
undefined,
@@ -230,19 +175,19 @@ export function writeApplyGroups(sectionId: number) {
fnPath.traverse(bindFunctionsVisitor, { root: fnPath, sectionId });
}
- const offset = groups[0].references ? 0 : 1;
- for (let i = offset; i < groups.length; i++) {
- groups[i].queuePriority.value = i - offset;
- }
for (let i = 0; i < closurePriorities.length; i++) {
- closurePriorities[i].value = i + groups.length - offset;
+ closurePriorities[i].value = i + allStatements.length;
}
}
export function writeHydrateGroups(sectionId: number) {
- const groups = getHydrate(sectionId);
- for (let i = groups.length; i--; ) {
- const { identifier, references, statements } = groups[i];
+ const allStatements = getHydrateStatements(sectionId);
+ for (let i = allStatements.length; i--; ) {
+ const statements = allStatements[i];
+ if (!statements?.length) continue;
+
+ const referenceGroup = getReferenceGroup(sectionId, i)!;
+ const { references, hydrate: identifier } = referenceGroup;
const params: Parameters[1] = references
? (Array.isArray(references) ? references : [references]).map((binding) =>
t.assignmentPattern(
@@ -271,7 +216,7 @@ export function writeHydrateGroups(sectionId: number) {
addStatement(
"apply",
sectionId,
- references,
+ getReferenceGroup(sectionId, references),
t.expressionStatement(
callRuntime("queueHydrate", scopeIdentifier, identifier)
)
@@ -279,11 +224,18 @@ export function writeHydrateGroups(sectionId: number) {
}
}
+export function addHTMLHydrateCall(
+ sectionId: number,
+ references?: ReferenceGroup
+) {
+ addStatement("hydrate", sectionId, references, undefined as any);
+}
+
export function writeHTMLHydrateStatements(
path: t.NodePath
) {
const sectionId = getOrCreateSectionId(path);
- const groups = getHydrate(sectionId);
+ const allStatements = getHydrateStatements(sectionId);
path.unshiftContainer(
"body",
@@ -292,31 +244,33 @@ export function writeHTMLHydrateStatements(
])
);
- if (!groups.length) return;
+ if (!allStatements.length) return;
const refs: Reserve[] = [];
- for (let i = groups.length; i--; ) {
- const { references } = groups[i];
- if (references) {
- if (Array.isArray(references)) {
- for (const ref of references) {
- sorted.insert(compareReserves, refs, ref);
+ for (let i = allStatements.length; i--; ) {
+ if (allStatements[i]?.length) {
+ const { references } = getReferenceGroup(sectionId, i)!;
+ if (references) {
+ if (Array.isArray(references)) {
+ for (const ref of references) {
+ insertReserve(refs, ref);
+ }
+ } else {
+ insertReserve(refs, references);
}
- } else {
- sorted.insert(compareReserves, refs, references);
}
- }
- path.pushContainer(
- "body",
- t.expressionStatement(
- callRuntime(
- "writeHydrateCall",
- scopeIdentifier,
- t.stringLiteral(getHydrateRegisterId(sectionId, references))
+ path.pushContainer(
+ "body",
+ t.expressionStatement(
+ callRuntime(
+ "writeHydrateCall",
+ scopeIdentifier,
+ t.stringLiteral(getHydrateRegisterId(sectionId, references))
+ )
)
- )
- );
+ );
+ }
}
path.pushContainer(
@@ -339,69 +293,6 @@ export function writeHTMLHydrateStatements(
);
}
-/**
- * reference group priority is sorted by number of references,
- * then if needed by reference order.
- */
-function compareReferenceGroups(
- { references: a }: ReferenceGroup,
- { references: b }: ReferenceGroup
-) {
- if (a) {
- if (b) {
- if (Array.isArray(a)) {
- if (Array.isArray(b)) {
- const len = a.length;
- const lenDelta = len - b.length;
- if (lenDelta !== 0) {
- return lenDelta;
- }
-
- for (let i = 0; i < len; i++) {
- const compareResult = compareReserves(a[i], b[i]);
- if (compareResult !== 0) {
- return compareResult;
- }
- }
-
- return 0;
- } else {
- return 1;
- }
- } else if (Array.isArray(b)) {
- return -1;
- } else {
- return compareReserves(a, b);
- }
- } else {
- return 1;
- }
- } else {
- return b ? -1 : 0;
- }
-}
-
-function generateReferenceGroupName(
- type: "apply" | "hydrate",
- sectionId: number,
- references: References
-) {
- let name = type + (sectionId || "");
-
- if (references) {
- if (Array.isArray(references)) {
- name += "With";
- for (const ref of references) {
- name += `_${ref.name}`;
- }
- } else {
- name += `_${references.name}`;
- }
- }
-
- return currentProgramPath.scope.generateUid(name);
-}
-
const bindFunctionsVisitor: t.Visitor<{
root: t.NodePath;
sectionId: number;
@@ -416,7 +307,7 @@ function bindFunction(
) {
const { node } = fn;
const { extra } = node;
- const references = extra?.references;
+ const references = extra?.references?.references;
const program = fn.hub.file.path;
const functionIdentifier = program.scope.generateUidIdentifier(extra?.name);
@@ -449,8 +340,8 @@ function bindFunction(
}
export function getDefaultApply(sectionId: number) {
- const [firstApply] = getApply(sectionId);
- const defaultApply =
- firstApply && !firstApply.references && firstApply.identifier;
- return defaultApply || t.nullLiteral();
+ const [firstApplyStatements] = getApplyStatements(sectionId);
+ return firstApplyStatements
+ ? getReferenceGroup(sectionId, 0).apply
+ : t.nullLiteral();
}
diff --git a/packages/translator/src/util/attrs-to-object.ts b/packages/translator/src/util/attrs-to-object.ts
index b0ca56d9f..dba564b57 100644
--- a/packages/translator/src/util/attrs-to-object.ts
+++ b/packages/translator/src/util/attrs-to-object.ts
@@ -1,16 +1,14 @@
import { types as t } from "@marko/compiler";
import toPropertyName from "./to-property-name";
-import * as sorted from "./sorted-arr";
-import { compareReserves } from "./reserve";
-import type { References } from "./references";
+import type { ReferenceGroup } from "./references";
export default function attrsToObject(
tag: t.NodePath,
withRenderBody = false
-): (t.Expression & { extra: { references: References } }) | undefined {
+): t.Expression | undefined {
const { node } = tag;
let result: t.Expression = t.objectExpression([]);
- const resultExtra: { references?: References } = (result.extra = {});
+ const resultExtra: { references?: ReferenceGroup } = (result.extra = {});
for (const attr of node.attributes) {
const value = attr.value!;
@@ -22,28 +20,6 @@ export default function attrsToObject(
t.objectProperty(toPropertyName(attr.name), value)
);
}
-
- const references = attr.extra?.valueReferences;
-
- if (references) {
- if (Array.isArray(references)) {
- for (const binding of references) {
- sorted.insertProp(
- compareReserves,
- resultExtra,
- `references`,
- binding
- );
- }
- } else {
- sorted.insertProp(
- compareReserves,
- resultExtra,
- `references`,
- references
- );
- }
- }
}
if (withRenderBody) {
@@ -85,7 +61,7 @@ export default function attrsToObject(
}
}
- return result as t.Expression & { extra: { references: References } };
+ return result as t.Expression;
}
}
diff --git a/packages/translator/src/util/references.ts b/packages/translator/src/util/references.ts
index f809563fe..31b1012db 100644
--- a/packages/translator/src/util/references.ts
+++ b/packages/translator/src/util/references.ts
@@ -1,7 +1,18 @@
-import type { types as t } from "@marko/compiler";
-import * as sorted from "./sorted-arr";
-import { getOrCreateSectionId } from "./sections";
-import { Reserve, ReserveType, reserveScope, compareReserves } from "./reserve";
+import { types as t } from "@marko/compiler";
+import { createSortedCollection } from "./sorted-arr";
+import {
+ getOrCreateSectionId,
+ createSectionState,
+ forEachSectionId,
+} from "./sections";
+import {
+ Reserve,
+ ReserveType,
+ reserveScope,
+ compareReserves,
+ insertReserve,
+} from "./reserve";
+import { currentProgramPath } from "../visitors/program";
type MarkoExprRootPath = t.NodePath<
| t.MarkoTag
@@ -11,38 +22,62 @@ type MarkoExprRootPath = t.NodePath<
| t.MarkoPlaceholder
>;
-export type References = undefined | Reserve | Reserve[];
+const [getReferenceGroups] = createSectionState(
+ "apply",
+ () => [
+ {
+ sectionId: 0,
+ index: 0,
+ count: 0,
+ references: undefined,
+ apply: t.identifier(""),
+ hydrate: t.identifier(""),
+ },
+ ]
+);
+export interface ReferenceGroup {
+ sectionId: number;
+ index: number;
+ count: number;
+ references: undefined | Reserve | Reserve[];
+ apply: t.Identifier;
+ hydrate: t.Identifier;
+}
declare module "@marko/compiler/dist/types" {
+ export interface ProgramExtra {
+ referenceGroups?: ReferenceGroup[][];
+ }
+
export interface FunctionExpressionExtra {
- references?: References;
+ references?: ReferenceGroup;
name?: string;
}
export interface ArrowFunctionExpressionExtra {
- references?: References;
+ references?: ReferenceGroup;
name?: string;
}
export interface MarkoTagExtra {
- varReferences?: References;
- nameReferences?: References;
+ varReferences?: ReferenceGroup;
+ nameReferences?: ReferenceGroup;
}
export interface MarkoTagBodyExtra {
- paramsReferences?: References;
+ paramsReferences?: ReferenceGroup;
}
export interface MarkoAttributeExtra {
- valueReferences?: References;
+ valueReferences?: ReferenceGroup;
}
export interface MarkoSpreadAttributeExtra {
- valueReferences?: References;
+ valueReferences?: ReferenceGroup;
}
export interface MarkoPlaceholderExtra {
- valueReferences?: References;
+ valueReferences?: ReferenceGroup;
}
}
@@ -72,30 +107,34 @@ export function trackReferencesForBindings(
const identifier = bindings[name];
const binding = reserveScope(reserveType, sectionId, identifier, name);
+ insertReferenceGroup(getReferenceGroups(sectionId), {
+ sectionId,
+ index: 0,
+ count: 0,
+ references: binding,
+ apply: t.identifier(""),
+ hydrate: t.identifier(""),
+ });
+
for (const reference of references) {
const fnRoot = getFnRoot(reference.scope.path);
const exprRoot = getExprRoot(fnRoot || reference);
- const exprExtra = (exprRoot.parentPath.node.extra ??= {});
+ const markoRoot = exprRoot.parentPath;
if (fnRoot) {
- const fnExtra = (fnRoot.node.extra ??= {});
- let name = (fnRoot.node as t.FunctionExpression).id?.name;
+ const name = (fnRoot.node as t.FunctionExpression).id?.name;
if (!name) {
- const { parentPath } = exprRoot;
-
- if (parentPath.isMarkoAttribute() && !parentPath.node.default) {
- name = parentPath.node.name;
+ if (markoRoot.isMarkoAttribute() && !markoRoot.node.default) {
+ (fnRoot.node.extra ??= {}).name = markoRoot.node.name;
}
}
- fnExtra.name = name;
- sorted.insertProp(compareReserves, fnExtra, "references", binding);
+ updateReferenceGroup(fnRoot, "references", binding);
}
- sorted.insertProp(
- compareReserves,
- exprExtra,
+ updateReferenceGroup(
+ markoRoot,
`${exprRoot.listKey || exprRoot.key}References`,
binding
);
@@ -103,6 +142,82 @@ export function trackReferencesForBindings(
}
}
+function updateReferenceGroup(
+ path: t.NodePath,
+ extraKey: string,
+ newBinding: Reserve
+) {
+ const sectionId = getOrCreateSectionId(path);
+ const currentGroup = (path.node.extra ??= {})[extraKey] as
+ | ReferenceGroup
+ | undefined;
+ const newReferences = insertReserve(
+ currentGroup?.references,
+ newBinding,
+ true
+ );
+
+ if (currentGroup) {
+ currentGroup.count--;
+ }
+
+ path.node.extra![extraKey] = getOrCreateReferenceGroup(
+ sectionId,
+ newReferences
+ );
+}
+
+export function mergeReferenceGroups(
+ sectionId: number,
+ groupEntries: [Record, string][]
+) {
+ let newReferences: ReferenceGroup["references"];
+ for (const [extra, key] of groupEntries) {
+ const group = extra[key] as ReferenceGroup;
+ const references = group.references;
+ delete extra[key];
+ group.count--;
+ sectionId = group.sectionId;
+
+ if (references) {
+ if (Array.isArray(references)) {
+ for (const binding of references) {
+ newReferences = insertReserve(newReferences, binding);
+ }
+ } else {
+ newReferences = insertReserve(newReferences, references);
+ }
+ }
+ }
+
+ return getOrCreateReferenceGroup(sectionId, newReferences);
+}
+
+function getOrCreateReferenceGroup(
+ sectionId: number,
+ references: ReferenceGroup["references"]
+) {
+ const newGroup: ReferenceGroup = {
+ sectionId,
+ index: 0,
+ count: 1,
+ references,
+ apply: t.identifier(""),
+ hydrate: t.identifier(""),
+ };
+
+ const referenceGroups = getReferenceGroups(sectionId);
+ const existingGroup = findReferenceGroup(referenceGroups, newGroup);
+
+ if (existingGroup) {
+ existingGroup.count++;
+ } else {
+ insertReferenceGroup(referenceGroups, newGroup);
+ }
+
+ return existingGroup ?? newGroup;
+}
+
function getExprRoot(path: t.NodePath) {
let curPath = path;
while (!isMarkoPath(curPath.parentPath!)) {
@@ -152,3 +267,117 @@ function isFunctionExpression(
return false;
}
}
+
+/**
+ * reference group priority is sorted by number of references,
+ * then if needed by reference order.
+ */
+const { insert: insertReferenceGroup, find: findReferenceGroup } =
+ createSortedCollection(function compareReferenceGroups(
+ { references: a }: ReferenceGroup,
+ { references: b }: ReferenceGroup
+ ) {
+ if (a) {
+ if (b) {
+ if (Array.isArray(a)) {
+ if (Array.isArray(b)) {
+ const len = a.length;
+ const lenDelta = len - b.length;
+ if (lenDelta !== 0) {
+ return lenDelta;
+ }
+
+ for (let i = 0; i < len; i++) {
+ const compareResult = compareReserves(a[i], b[i]);
+ if (compareResult !== 0) {
+ return compareResult;
+ }
+ }
+
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (Array.isArray(b)) {
+ return -1;
+ } else {
+ return compareReserves(a, b);
+ }
+ } else {
+ return 1;
+ }
+ } else {
+ return b ? -1 : 0;
+ }
+ });
+
+export function finalizeReferences() {
+ const allReferenceGroups: ReferenceGroup[][] = [];
+ forEachSectionId((sectionId) => {
+ const referenceGroups = getReferenceGroups(sectionId).filter(
+ (g) => g.count > 0 || !Array.isArray(g.references)
+ );
+ referenceGroups.forEach((g, i) => {
+ g.index = i;
+ g.apply.name = generateReferenceGroupName(
+ "apply",
+ sectionId,
+ g.references
+ );
+ g.hydrate.name = generateReferenceGroupName(
+ "hydrate",
+ sectionId,
+ g.references
+ );
+ });
+ allReferenceGroups[sectionId] = referenceGroups;
+ });
+ (currentProgramPath.node.extra ??= {}).referenceGroups = allReferenceGroups;
+}
+
+export function getReferenceGroup(
+ sectionId: number,
+ lookup: number | ReferenceGroup["references"],
+ analyze = false
+) {
+ const referenceGroups = analyze
+ ? getReferenceGroups(sectionId)
+ : currentProgramPath.node.extra!.referenceGroups![sectionId];
+ let found: ReferenceGroup | undefined;
+ if (typeof lookup === "number") {
+ found = referenceGroups[lookup];
+ } else {
+ found = findReferenceGroup(referenceGroups, {
+ references: lookup,
+ } as ReferenceGroup);
+ }
+
+ if (!found) {
+ throw new Error(
+ `Reference group not found for section ${sectionId}: ${lookup}`
+ );
+ }
+
+ return found;
+}
+
+function generateReferenceGroupName(
+ type: "apply" | "hydrate",
+ sectionId: number,
+ references: ReferenceGroup["references"]
+) {
+ let name = type + (sectionId || "");
+
+ if (references) {
+ if (Array.isArray(references)) {
+ name += "With";
+ for (const ref of references) {
+ name += `_${ref.name}`;
+ }
+ } else {
+ name += `_${references.name}`;
+ }
+ }
+
+ return currentProgramPath.scope.generateUid(name);
+}
diff --git a/packages/translator/src/util/reserve.ts b/packages/translator/src/util/reserve.ts
index aba09bd91..5a27bd82a 100644
--- a/packages/translator/src/util/reserve.ts
+++ b/packages/translator/src/util/reserve.ts
@@ -1,5 +1,6 @@
import type { types as t } from "@marko/compiler";
import { createSectionState, forEachSectionId } from "./sections";
+import { createSortedCollection } from "./sorted-arr";
const [getReservesByType] = createSectionState<
[Reserve[] | undefined, Reserve[] | undefined, Reserve[] | undefined]
@@ -17,7 +18,7 @@ export interface Reserve {
name: string;
size: number;
id: number;
- exportName?: string;
+ exportIdentifier?: t.Identifier;
}
declare module "@marko/compiler/dist/types" {
@@ -99,3 +100,6 @@ export function assignFinalIds() {
export function compareReserves(a: Reserve, b: Reserve) {
return a.sectionId - b.sectionId || a.type - b.type || a.id - b.id;
}
+
+export const { insert: insertReserve } =
+ createSortedCollection(compareReserves);
diff --git a/packages/translator/src/util/runtime.ts b/packages/translator/src/util/runtime.ts
index c7c8c8244..0abb89b07 100644
--- a/packages/translator/src/util/runtime.ts
+++ b/packages/translator/src/util/runtime.ts
@@ -3,7 +3,7 @@ import { importNamed } from "@marko/babel-utils";
import { getMarkoOpts } from "./marko-config";
import type { Reserve } from "./reserve";
import { currentProgramPath, scopeIdentifier } from "../visitors/program";
-import type { ReferenceGroup } from "./apply-hydrate";
+import type { ReferenceGroup } from "./references";
declare const MARKO_SRC: boolean;
@@ -49,7 +49,7 @@ export function callRead(reference: Reserve, targetSectionId: number) {
}
export function callQueue(
- { identifier, queuePriority }: ReferenceGroup,
+ { apply, index }: ReferenceGroup,
reference: Reserve,
value: t.Expression,
targetSectionId: number
@@ -57,8 +57,8 @@ export function callQueue(
return callRuntime(
"queue",
getScopeExpression(reference, targetSectionId),
- identifier,
- queuePriority,
+ apply,
+ t.numericLiteral(index - 1),
value
);
}
diff --git a/packages/translator/src/util/sorted-arr.ts b/packages/translator/src/util/sorted-arr.ts
index d8451edcd..c3742c692 100644
--- a/packages/translator/src/util/sorted-arr.ts
+++ b/packages/translator/src/util/sorted-arr.ts
@@ -1,8 +1,8 @@
-export function insert(
+function insertInArray(
compare: (a: T[number], b: T[number]) => number,
arr: T,
val: T[number]
-) {
+): T {
const len = arr.length;
let max = len;
let pos = 0;
@@ -10,7 +10,7 @@ export function insert(
while (pos < max) {
const mid = (pos + max) >>> 1;
const compareResult = compare(arr[mid], val);
- if (compareResult === 0) return;
+ if (compareResult === 0) return arr;
if (compareResult > 0) max = mid;
else pos = mid + 1;
}
@@ -23,12 +23,14 @@ export function insert(
}
arr[len] = cur;
+
+ return arr;
}
-export function findIndex(
- compare: (a: T[number], b: T[number]) => number,
- arr: T,
- val: T[number]
+export function findIndex(
+ compare: (a: V, b: V) => number,
+ arr: V[],
+ val: V
) {
let max = arr.length;
let pos = 0;
@@ -44,33 +46,30 @@ export function findIndex(
return -1;
}
-type KeysWithValueType = keyof {
- [K in keyof T as string extends K
- ? never
- : number extends K
- ? never
- : T[K] extends undefined | M | M[]
- ? K
- : never]: never;
-};
+export function createSortedCollection(compare: (a: V, b: V) => number) {
+ return {
+ insert(data: undefined | V | V[], val: V, immutable = false): V | V[] {
+ if (data) {
+ if (Array.isArray(data)) {
+ return insertInArray(compare, immutable ? [...data] : data, val);
+ } else {
+ const compareResult = compare(data, val);
-export function insertProp<
- V,
- T extends Record,
- K extends string extends K ? keyof T : KeysWithValueType
->(compare: (a: V, b: V) => number, data: T, key: K & keyof T, val: V) {
- const cur = data[key] as undefined | V | V[];
- if (cur) {
- if (Array.isArray(cur)) {
- insert(compare, cur, val);
- } else {
- const compareResult = compare(cur, val);
-
- if (compareResult !== 0) {
- (data[key] as V[]) = compareResult < 0 ? [cur, val] : [val, cur];
+ if (compareResult !== 0) {
+ return compareResult < 0 ? [data, val] : [val, data];
+ }
+ }
}
- }
- } else {
- (data[key] as V) = val;
- }
+ return val;
+ },
+ find(data: undefined | V | V[], val: V) {
+ if (data) {
+ if (Array.isArray(data)) {
+ return data[findIndex(compare, data, val)];
+ } else {
+ return data === val ? data : undefined;
+ }
+ }
+ },
+ };
}
diff --git a/packages/translator/src/visitors/program/dom.ts b/packages/translator/src/visitors/program/dom.ts
index 6c48ebeba..5cd4eaf16 100644
--- a/packages/translator/src/visitors/program/dom.ts
+++ b/packages/translator/src/visitors/program/dom.ts
@@ -1,13 +1,11 @@
import { types as t } from "@marko/compiler";
import { callRuntime } from "../../util/runtime";
import { forEachSectionId, getSectionId } from "../../util/sections";
-import {
- bindingToApplyGroup,
- writeAllStatementGroups,
-} from "../../util/apply-hydrate";
+import { writeAllStatementGroups } from "../../util/apply-hydrate";
import * as writer from "../../util/writer";
import { visit } from "../../util/walks";
import { scopeIdentifier } from ".";
+import { getReferenceGroup } from "../../util/references";
export default {
translate: {
@@ -57,17 +55,14 @@ export default {
t.blockStatement(
Object.keys(attrs.bindings).map((name) => {
const bindingIdentifier = attrs.bindings[name];
- const exportName =
- bindingIdentifier.extra!.reserve!.exportName!;
- const { identifier: applyIdentifier } =
- bindingToApplyGroup(
- bindingIdentifier.extra!.reserve!,
- sectionId
- );
+ const { apply: applyIdentifier } = getReferenceGroup(
+ sectionId,
+ bindingIdentifier.extra!.reserve
+ );
exportSpecifiers.push(
t.exportSpecifier(
applyIdentifier,
- t.identifier(exportName)
+ bindingIdentifier.extra!.reserve!.exportIdentifier!
)
);
return t.expressionStatement(
diff --git a/packages/translator/src/visitors/program/index.ts b/packages/translator/src/visitors/program/index.ts
index f3dc31df6..b77dda172 100644
--- a/packages/translator/src/visitors/program/index.ts
+++ b/packages/translator/src/visitors/program/index.ts
@@ -4,6 +4,7 @@ import programHTML from "./html";
import programDOM from "./dom";
import { startSection } from "../../util/sections";
import { assignFinalIds } from "../../util/reserve";
+import { finalizeReferences } from "../../util/references";
export let currentProgramPath: t.NodePath;
export let scopeIdentifier: t.Identifier;
@@ -24,6 +25,7 @@ export default {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
exit() {
assignFinalIds();
+ finalizeReferences();
currentProgramPath = previousProgramPath.get(currentProgramPath)!;
},
},
diff --git a/packages/translator/src/visitors/tag/custom-tag.ts b/packages/translator/src/visitors/tag/custom-tag.ts
index cf3d44645..c05638557 100644
--- a/packages/translator/src/visitors/tag/custom-tag.ts
+++ b/packages/translator/src/visitors/tag/custom-tag.ts
@@ -17,7 +17,10 @@ import {
getSectionId,
getOrCreateSectionId,
} from "../../util/sections";
-import trackReferences from "../../util/references";
+import trackReferences, {
+ mergeReferenceGroups,
+ ReferenceGroup,
+} from "../../util/references";
import {
addStatement,
writeHTMLHydrateStatements,
@@ -43,6 +46,20 @@ export default {
);
}
},
+ exit(tag: t.NodePath) {
+ // TODO: only if dynamic attributes
+ const tagDef = getTagDef(tag);
+ const template = tagDef?.template;
+ const sectionId = getOrCreateSectionId(tag);
+ if (template) {
+ tag.node.extra.attrsReferences = mergeReferenceGroups(
+ sectionId,
+ tag.node.attributes
+ .filter((attr) => attr.extra?.valueReferences)
+ .map((attr) => [attr.extra, "valueReferences"])
+ );
+ }
+ },
},
translate: {
enter(tag: t.NodePath) {
@@ -204,7 +221,7 @@ export default {
addStatement(
"apply",
tagSectionId,
- attrsObject.extra.references,
+ tag.node.extra.attrsReferences as ReferenceGroup,
t.expressionStatement(
t.callExpression(tagAttrsIdentifier, [
callRead(binding, tagSectionId),
diff --git a/packages/translator/src/visitors/tag/index.ts b/packages/translator/src/visitors/tag/index.ts
index 0a9e4d9ad..7bec06061 100644
--- a/packages/translator/src/visitors/tag/index.ts
+++ b/packages/translator/src/visitors/tag/index.ts
@@ -58,7 +58,7 @@ export default {
analyzeAttributeTags(tag);
switch (type) {
case TagNameTypes.CustomTag:
- // CustomTag.analyze.exit(tag);
+ CustomTag.analyze.exit(tag);
break;
case TagNameTypes.AttributeTag:
// AttributeTag.analyze.exit(tag);
diff --git a/packages/translator/src/visitors/tag/native-tag.ts b/packages/translator/src/visitors/tag/native-tag.ts
index 26b4cd2f2..94baa9caf 100644
--- a/packages/translator/src/visitors/tag/native-tag.ts
+++ b/packages/translator/src/visitors/tag/native-tag.ts
@@ -7,10 +7,7 @@ import translateVar from "../../util/translate-var";
import evaluate from "../../util/evaluate";
import { getOrCreateSectionId, getSectionId } from "../../util/sections";
import { ReserveType, reserveScope } from "../../util/reserve";
-import {
- addStatement,
- ensureHydrateReferenceGroup,
-} from "../../util/apply-hydrate";
+import { addStatement, addHTMLHydrateCall } from "../../util/apply-hydrate";
import * as writer from "../../util/writer";
import * as walks from "../../util/walks";
import { scopeIdentifier } from "../program";
@@ -124,7 +121,7 @@ export default {
write`${getHTMLRuntime().attr(name, computed)}`;
} else if (isHTML) {
if (name.startsWith("on")) {
- ensureHydrateReferenceGroup(sectionId, extra.valueReferences);
+ addHTMLHydrateCall(sectionId, extra.valueReferences);
} else {
write`${callRuntime(
"attr",