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