Skip to content

Commit

Permalink
Caching (#44)
Browse files Browse the repository at this point in the history
* fix: don't cache call expressions unless event handlers

* fix: cache function calls

* chore: add changeset
  • Loading branch information
rturnq authored May 31, 2023
1 parent 0f352f0 commit 49637c2
Show file tree
Hide file tree
Showing 21 changed files with 150 additions and 40 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-avocados-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marko/tags-api-preview": patch
---

improve member expression caching
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>
9 string length = 1
</p>
<button>
increment
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>
9 string length = 1
</p>
<button>
increment
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>
10 string length = 2
</p>
<button>
increment
</button>
10 changes: 10 additions & 0 deletions src/__tests__/fixtures/const/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ describe(
])
);

describe(
"<const> state derived from prototype function",
fixture("./templates/state-derived-prototype-function.marko", [
{ value: 1 },
async ({ screen, fireEvent }) => {
await fireEvent.click(screen.getByText("increment"));
},
])
);

describe(
"<const> assignment error",
fixture("./templates/error-assignment.marko")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<let/value=9/>
<const/length=value.toString().length/>
<p>${value} string length = ${length}</p>
<button onClick=(() => value++)>increment</button>
20 changes: 0 additions & 20 deletions src/__tests__/fixtures/misc/cached-conditional-value/index.test.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<button>
increment
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<button>
increment
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<button>
increment
</button>
51 changes: 51 additions & 0 deletions src/__tests__/fixtures/misc/caching/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { resetHistory, spy } from "sinon";
import fixture, { FixtureHelpers } from "../../../fixture";
const increment = click("Increment");
const toggle = click("Toggle");

const onRender = spy();

describe("cached", () => {
beforeEach(() => resetHistory());

describe(
"conditional value",
fixture("./templates/conditional-value.marko", [
{},
increment,
toggle,
toggle,
increment,
toggle,
])
);

describe(
"function call",
fixture("./templates/function-call.marko", [
{ fn: onRender },
async ({ expect, screen, fireEvent, rerender }) => {
expect(onRender).calledOnce;
resetHistory();
expect(onRender).not.called;

await fireEvent.click(screen.getByText("increment"));
expect(onRender).not.called;
resetHistory();

await rerender();
expect(onRender).not.called;
resetHistory();

await rerender({ fn: () => onRender() });
expect(onRender).calledOnce;
resetHistory();
},
])
);
});

function click(text: string) {
return async ({ fireEvent, screen }: FixtureHelpers) =>
await fireEvent.click(screen.getByText(text));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<let/count=0/>
<button onClick=(() => count++)>increment</button>
<const/_=input.fn()/>
70 changes: 50 additions & 20 deletions src/transform/cached-values/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ interface Deps {
}

interface DepsVisitorState {
parent: t.Node;
node: t.Node;
parentTag: t.NodePath;
deps: Deps | undefined;
shouldCache: boolean;
}

const depsVisitor = {
enter(path, state) {
if (path.parent === state.parent && path.node !== state.node) {
path.skip();
}
},
Function(_, state) {
state.shouldCache = true;
},
Expand Down Expand Up @@ -55,22 +62,25 @@ const depsVisitor = {
const curDeps = deps[name];
if (curDeps === true) return;

if (
parent.isMemberExpression() &&
(!parent.node.computed ||
isStringOrNumericLiteral(parent.node.property.type))
) {
const prop = parent.node.property as
| t.Identifier
| t.StringLiteral
| t.NumericLiteral;
parent = parent.parentPath;
deps = curDeps || (deps[name] = {} as Deps);
name = prop.type === "Identifier" ? prop.name : prop.value + "";
} else {
deps[name] = true;
return;
if (parent.isMemberExpression()) {
const literalName = getPropertyNameLiteral(parent.node);
if (
literalName !== undefined &&
!(
parent.parentPath.isCallExpression() &&
parent.parentPath.node.callee === parent.node &&
!isEventHandlerName(literalName)
)
) {
parent = parent.parentPath;
deps = curDeps || (deps[name] = {} as Deps);
name = literalName;
continue;
}
}

deps[name] = true;
return;
// eslint-disable-next-line no-constant-condition
} while (true);
}
Expand Down Expand Up @@ -114,19 +124,22 @@ export default {

function cacheExprIfNeeded(
parentTag: t.NodePath<t.MarkoTag>,
expr: t.NodePath<any>
exprPath: t.NodePath<any>
) {
const parentPath = exprPath.parentPath!;
const state: DepsVisitorState = {
parentTag,
node: exprPath.node,
parent: parentPath.node,
deps: undefined,
shouldCache: false,
};
expr.traverse(depsVisitor, state);
parentPath.traverse(depsVisitor, state);

if (state.shouldCache) {
const { file } = expr.hub;
const { file } = exprPath.hub;
const { component } = ensureLifecycle(parentTag)!;
expr
exprPath
.replaceWith(
t.callExpression(
importRuntimeNamed(file, "transform/cached-values", "cache"),
Expand All @@ -140,7 +153,7 @@ function cacheExprIfNeeded(
t.arrayExpression(state.deps ? toDepsArray(state.deps) : []),
]
),
expr.node
exprPath.node
),
]
)
Expand Down Expand Up @@ -174,6 +187,19 @@ function* getDefaultExpressions(
}
}

function getPropertyNameLiteral(memberExpression: t.MemberExpression) {
if (memberExpression.computed) {
if (isStringOrNumericLiteral(memberExpression.property.type)) {
return (
(memberExpression.property as t.StringLiteral | t.NumericLiteral)
.value + ""
);
}
} else if (memberExpression.property.type === "Identifier") {
return memberExpression.property.name;
}
}

function isStringOrNumericLiteral(type: string) {
switch (type) {
case "StringLiteral":
Expand All @@ -184,6 +210,10 @@ function isStringOrNumericLiteral(type: string) {
}
}

function isEventHandlerName(name: string) {
return /^on[A-Z]/.test(name);
}

function toDepsArray(
deps: Deps,
object?: t.Expression,
Expand Down

0 comments on commit 49637c2

Please sign in to comment.