From cd8f6b292c0bc52df34220b78d8f7985c93a472c Mon Sep 17 00:00:00 2001 From: Michael Rawlings Date: Fri, 8 Apr 2022 23:52:06 -0400 Subject: [PATCH] refactor: move referenceGroup creation into analyze --- packages/runtime/src/common/helpers.ts | 6 + .../__snapshots__/dom.expected.js | 8 +- .../__snapshots__/dom.expected.error.js | 6 - .../attr-class/__snapshots__/dom.expected.js | 59 ++-- .../__snapshots__/dom.expected.js | 2 +- .../dom.expected/components/comments.js | 24 +- .../__snapshots__/dom.expected.js | 2 +- .../__snapshots__/dom.expected.js | 2 +- .../__snapshots__/dom.expected.js | 12 + .../for-tag/__snapshots__/dom.expected.js | 4 - packages/translator/src/core/attrs.ts | 20 +- .../translator/src/core/condition/else-if.ts | 13 +- .../translator/src/core/condition/else.ts | 13 +- packages/translator/src/core/condition/if.ts | 73 +++-- packages/translator/src/core/const.ts | 11 +- packages/translator/src/core/effect.ts | 10 +- packages/translator/src/core/for.ts | 11 +- packages/translator/src/core/let.ts | 13 +- packages/translator/src/util/apply-hydrate.ts | 263 +++++------------ .../translator/src/util/attrs-to-object.ts | 32 +- packages/translator/src/util/references.ts | 279 ++++++++++++++++-- packages/translator/src/util/reserve.ts | 6 +- packages/translator/src/util/runtime.ts | 8 +- packages/translator/src/util/sorted-arr.ts | 67 +++-- .../translator/src/visitors/program/dom.ts | 19 +- .../translator/src/visitors/program/index.ts | 2 + .../translator/src/visitors/tag/custom-tag.ts | 21 +- packages/translator/src/visitors/tag/index.ts | 2 +- .../translator/src/visitors/tag/native-tag.ts | 7 +- 29 files changed, 568 insertions(+), 427 deletions(-) delete mode 100644 packages/translator/src/__tests__/fixtures/attr-class/__snapshots__/dom.expected.error.js 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 - | ^^^^^ - 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",