mirror of
https://github.com/marko-js/marko.git
synced 2026-02-01 16:07:13 +00:00
feat: add reserved scope indexes, and some hydrate stuff
This commit is contained in:
parent
04efea60a8
commit
bedc0cae43
@ -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);
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
36
packages/translator/src/analyze/util/reserves.ts
Normal file
36
packages/translator/src/analyze/util/reserves.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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)]
|
||||
)
|
||||
)
|
||||
|
||||
@ -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,
|
||||
])
|
||||
)
|
||||
|
||||
@ -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!)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user