feat: add reserved scope indexes, and some hydrate stuff

This commit is contained in:
Dylan Piercey 2021-12-14 13:16:20 -07:00 committed by Dylan Piercey
parent 04efea60a8
commit bedc0cae43
9 changed files with 216 additions and 107 deletions

View File

@ -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 = "<span><!></span><button><!></button>";
export const walks = "D%l D%l";
export const template = "<div><button><!></button></div>";
export const walks = "D D%";
export const apply = _apply;
export default _createRenderFn(template, walks, [], apply);

View File

@ -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<t.MarkoTag>) {
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<t.MarkoAttribute>[]) {
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<t.MarkoAttribute | t.MarkoSpreadAttribute>
) {
return !evaluate(attr).confident;
): attr is t.NodePath<t.MarkoAttribute> {
return attr.type === "MarkoSpreadAttribute";
}

View File

@ -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<any>) {
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<any>) {
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<any>): 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;

View File

@ -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<t.MarkoTag | t.MarkoAttribute>,
size: number
) {
const section = getSection(tag);
const { extra } = tag.node;
const { sectionIndex } = section;
extra.reserve = {
sectionIndex,
reserveIndex: (section.reserves += size),
size,
};
}

View File

@ -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<t.MarkoTagBody | t.Program>) {
sectionIndex,
visits: 0,
bindings: 0,
reserves: 0,
};
if (sectionIndex) {

View File

@ -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<t.MarkoTag>) {
const { node } = tag;
@ -48,7 +47,7 @@ export default function enter(tag: t.NodePath<t.MarkoTag>) {
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<t.MarkoTag>) {
...identifiers.map((identifier) =>
t.expressionStatement(
t.callExpression(
writer.getApplyId(tag, identifier.extra as Reference),
writer.getApplyId(tag, identifier.extra.binding!),
[t.identifier(identifier.name)]
)
)

View File

@ -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<t.MarkoTag>) {
const { node } = tag;
@ -50,7 +49,7 @@ export default function enter(tag: t.NodePath<t.MarkoTag>) {
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,
])
)

View File

@ -38,65 +38,90 @@ export function enter(tag: t.NodePath<t.MarkoTag>) {
// TODO: this should iterate backward and filter out duplicated attrs.
for (const attr of attrs as t.NodePath<t.MarkoAttribute>[]) {
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!)
);
}
}
}

View File

@ -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<any>) {
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<any>) {
return (
strs: TemplateStringsArray,
...exprs: Array<string | t.Expression>
) => {
): 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<any>, ref: Reference) {
export function getApplyId(path: t.NodePath<any>, 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<any>, ref: Reference) {
}
}
function writeReferenceGroups(path: t.NodePath<any>, groups: ReferenceGroup[]) {
function writeApplyGroups(path: t.NodePath<any>, 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<any>, 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<any>, groups: ReferenceGroup[]) {
}
}
function writeHydrateGroups(path: t.NodePath<any>, 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<any>, ref: Reference) {
export function bindingToScopeId(
path: t.NodePath<any>,
{ 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<any>,
{ 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,