mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
feat: new queuing/dirty checking approach
This commit is contained in:
parent
95284be78c
commit
331d58dc94
@ -6,9 +6,9 @@
|
||||
{
|
||||
"name": "*",
|
||||
"individual": {
|
||||
"min": 8522,
|
||||
"gzip": 3629,
|
||||
"brotli": 3330
|
||||
"min": 8729,
|
||||
"gzip": 3767,
|
||||
"brotli": 3466
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { Context, setContext } from "../common/context";
|
||||
import { reconcile } from "./reconcile";
|
||||
import { Renderer, initRenderer } from "./renderer";
|
||||
import { getQueuedScope } from "./queue";
|
||||
import { Scope, createScope, getEmptyScope } from "./scope";
|
||||
import { Scope, createScope, getEmptyScope, set } from "./scope";
|
||||
import { NodeType } from "./dom";
|
||||
|
||||
export type Conditional = {
|
||||
@ -199,9 +198,8 @@ export function setLoopOf(loop: Loop, newArray: unknown[]) {
|
||||
loop.___parentOffset
|
||||
);
|
||||
} else {
|
||||
const queuedScope = getQueuedScope(childScope);
|
||||
queuedScope[0] = item;
|
||||
queuedScope[1] = index;
|
||||
set(childScope, 0, item);
|
||||
set(childScope, 1, index);
|
||||
}
|
||||
newScopes.set(key, childScope);
|
||||
}
|
||||
|
||||
@ -31,14 +31,8 @@ export { init, register } from "./hydrate";
|
||||
|
||||
export { pushContext, popContext, getInContext } from "../common/context";
|
||||
|
||||
export {
|
||||
queue,
|
||||
getQueuedScope,
|
||||
run,
|
||||
checkDirty,
|
||||
checkDirtyNotEqual
|
||||
} from "./queue";
|
||||
export { queue, setQueued, run } from "./queue";
|
||||
|
||||
export { Scope } from "./scope";
|
||||
export { Scope, set, checkDirty } from "./scope";
|
||||
|
||||
export { createRenderer, createRenderFn } from "./renderer";
|
||||
|
||||
@ -1,63 +1,73 @@
|
||||
import { Scope } from "./scope";
|
||||
import { set, cleanScopes, Scope } from "./scope";
|
||||
|
||||
type ExecFn = (scope: Scope, offset: number) => void;
|
||||
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
let queued: boolean;
|
||||
|
||||
port1.onmessage = () => {
|
||||
queued = false;
|
||||
run();
|
||||
};
|
||||
|
||||
function flushAndWaitFrame() {
|
||||
run();
|
||||
requestAnimationFrame(triggerMacroTask);
|
||||
}
|
||||
|
||||
function triggerMacroTask() {
|
||||
port2.postMessage(0);
|
||||
}
|
||||
|
||||
const fns: Set<ExecFn> = new Set();
|
||||
let queuedFns: unknown[] = [];
|
||||
export function queue(fn: ExecFn, scope: Scope, offset: number) {
|
||||
// TODO: maybe don't do this and
|
||||
// 1. required a queued scope to be passed in OR
|
||||
// 2. get the queued scope when running the queue
|
||||
const stagedScope = getQueuedScope(scope);
|
||||
|
||||
export function queue(fn: ExecFn, scope: Scope, offset: number) {
|
||||
if (fns.has(fn))
|
||||
for (let i = 0; i < queuedFns.length; i += 3)
|
||||
if (
|
||||
queuedFns[i] === fn &&
|
||||
queuedFns[i + 1] === stagedScope &&
|
||||
queuedFns[i + 1] === scope &&
|
||||
queuedFns[i + 2] === offset
|
||||
)
|
||||
return;
|
||||
else fns.add(fn);
|
||||
|
||||
queuedFns.push(fn, stagedScope, offset);
|
||||
if (!queued) {
|
||||
queued = true;
|
||||
queueMicrotask(flushAndWaitFrame);
|
||||
}
|
||||
|
||||
queuedFns.push(fn, scope, offset);
|
||||
}
|
||||
|
||||
let queuedScopes: Map<Scope, Scope> = new Map();
|
||||
export function getQueuedScope(scope: Scope) {
|
||||
let queuedScope = queuedScopes.get(scope);
|
||||
if (!queuedScope) {
|
||||
queuedScopes.set(scope, (queuedScope = Object.create(scope)));
|
||||
}
|
||||
return queuedScope || scope;
|
||||
let queuedValues: unknown[] = [];
|
||||
export function setQueued(scope: Scope, index: number, value: unknown) {
|
||||
// TODO: if the same index is set twice for a scope,
|
||||
// the first one should be removed from the queue
|
||||
queuedValues.push(scope, index, value);
|
||||
}
|
||||
|
||||
export function run() {
|
||||
const runningFns = queuedFns;
|
||||
const runningScopes = queuedScopes;
|
||||
queuedFns = [];
|
||||
queuedScopes = new Map();
|
||||
fns.clear();
|
||||
for (let i = 0; i < runningFns.length; i += 3) {
|
||||
(runningFns[i] as ExecFn)(
|
||||
runningFns[i + 1] as Scope,
|
||||
runningFns[i + 2] as number
|
||||
);
|
||||
}
|
||||
for (const runningScope of runningScopes.values()) {
|
||||
Object.assign(Object.getPrototypeOf(runningScope), runningScope);
|
||||
if (queuedFns.length) {
|
||||
const runningFns = queuedFns;
|
||||
const runningValues = queuedValues;
|
||||
queuedFns = [];
|
||||
queuedValues = [];
|
||||
fns.clear();
|
||||
for (let i = 0; i < runningValues.length; i += 3) {
|
||||
set(
|
||||
runningValues[i] as Scope,
|
||||
runningValues[i + 1] as number,
|
||||
runningValues[i + 2] as unknown
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < runningFns.length; i += 3) {
|
||||
(runningFns[i] as ExecFn)(
|
||||
runningFns[i + 1] as Scope,
|
||||
runningFns[i + 2] as number
|
||||
);
|
||||
}
|
||||
cleanScopes();
|
||||
}
|
||||
}
|
||||
|
||||
export function getRunningScope() {}
|
||||
|
||||
export function checkDirty(scope: unknown[], index: number) {
|
||||
return scope.hasOwnProperty(index);
|
||||
}
|
||||
|
||||
export function checkDirtyNotEqual(scope: unknown[], index: number) {
|
||||
return (
|
||||
checkDirty(scope, index) &&
|
||||
scope[index] !== Object.getPrototypeOf(scope)[index]
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Conditional, Loop } from "./control-flow";
|
||||
import { DOMMethods } from "./dom";
|
||||
import { getQueuedScope, queue, run } from "./queue";
|
||||
import { createScope, Scope } from "./scope";
|
||||
import { createScope, Scope, cleanScopes } from "./scope";
|
||||
import { WalkCodes, detachedWalk } from "./walker";
|
||||
|
||||
const enum NodeType {
|
||||
@ -94,11 +93,11 @@ export function createRenderFn<I extends Input>(
|
||||
const scope = createScope(size!, domMethods!);
|
||||
const dom = initRenderer(renderer, scope, 0) as RenderResult;
|
||||
dynamicInput && dynamicInput(input, scope, 0);
|
||||
cleanScopes();
|
||||
|
||||
dom.update = (newInput: I) => {
|
||||
const queuedScope = getQueuedScope(scope);
|
||||
dynamicInput && dynamicInput(newInput, queuedScope, 0);
|
||||
run();
|
||||
dynamicInput && dynamicInput(newInput, scope, 0);
|
||||
cleanScopes();
|
||||
};
|
||||
|
||||
dom.destroy = () => {
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import { Conditional, Loop } from "./control-flow";
|
||||
import { DOMMethods, staticNodeMethods } from "./dom";
|
||||
|
||||
const dirtyScopes: Set<Scope> = new Set();
|
||||
|
||||
export type Scope = unknown[] &
|
||||
DOMMethods & {
|
||||
___startNode: Conditional | Loop | Node | undefined;
|
||||
___endNode: Conditional | Loop | Node | undefined;
|
||||
___dirty: Record<number, true> | true | undefined;
|
||||
};
|
||||
|
||||
export function createScope(size: number, methods: DOMMethods): Scope {
|
||||
const scope = new Array(size).fill(undefined) as Scope;
|
||||
scope.___startNode = scope.___endNode = undefined;
|
||||
return Object.assign(scope, methods);
|
||||
scope.___dirty = true;
|
||||
dirtyScopes.add(Object.assign(scope, methods));
|
||||
return scope;
|
||||
}
|
||||
|
||||
const emptyScope = createScope(0, staticNodeMethods);
|
||||
@ -19,4 +24,28 @@ export function getEmptyScope(marker?: Comment) {
|
||||
return emptyScope;
|
||||
}
|
||||
|
||||
export function attachDOMToScope(scope: Scope, dom: Node) {}
|
||||
export function set(scope: Scope, index: number, value: unknown) {
|
||||
if (scope[index] !== value) {
|
||||
if (!scope.___dirty) {
|
||||
scope.___dirty = {};
|
||||
dirtyScopes.add(scope);
|
||||
}
|
||||
|
||||
scope[index] = value;
|
||||
if (scope.___dirty !== true) {
|
||||
scope.___dirty[index] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function checkDirty(scope: Scope, index: number) {
|
||||
const dirty = scope.___dirty;
|
||||
return dirty === true || (dirty && dirty[index]);
|
||||
}
|
||||
|
||||
export function cleanScopes() {
|
||||
for (const scope of dirtyScopes) {
|
||||
scope.___dirty = undefined;
|
||||
}
|
||||
dirtyScopes.clear();
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
walk,
|
||||
data,
|
||||
loop,
|
||||
set,
|
||||
setLoopOf,
|
||||
Loop,
|
||||
Scope,
|
||||
@ -73,7 +74,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.children;
|
||||
set(scope, offset, input.children);
|
||||
execInputChildren(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
Scope,
|
||||
on,
|
||||
ensureDelegated,
|
||||
getQueuedScope,
|
||||
setQueued,
|
||||
queue,
|
||||
checkDirty
|
||||
} from "../../../../src/dom/index";
|
||||
@ -39,7 +39,7 @@ const execClickCount = (scope: Scope, offset: number) => {
|
||||
"click",
|
||||
scope[offset] <= 1
|
||||
? () => {
|
||||
(getQueuedScope(scope)[offset] as number)++;
|
||||
setQueued(scope, offset, (scope[offset] as number) + 1);
|
||||
queue(execClickCount, scope, offset);
|
||||
}
|
||||
: false
|
||||
|
||||
@ -2,12 +2,14 @@ import {
|
||||
walk,
|
||||
data,
|
||||
loop,
|
||||
set,
|
||||
setLoopOf,
|
||||
Loop,
|
||||
Scope,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
staticNodeMethods,
|
||||
checkDirty
|
||||
} from "../../../../src/dom/index";
|
||||
import { get, next, over } from "../../utils/walks";
|
||||
|
||||
@ -73,7 +75,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.children;
|
||||
set(scope, offset, input.children);
|
||||
execInputChildren(scope, offset);
|
||||
};
|
||||
|
||||
@ -90,5 +92,10 @@ const iter0 = createRenderer(
|
||||
);
|
||||
|
||||
const iter0_execItem = (scope: Scope) => {
|
||||
data(scope[3] as Text, (scope[0] as Input["children"][number]).text);
|
||||
if (checkDirty(scope, 0)) {
|
||||
set(scope, 4, (scope[0] as Input["children"][number]).text);
|
||||
if (checkDirty(scope, 4)) {
|
||||
data(scope[3] as Text, scope[4]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -20,7 +20,7 @@ inserted div0
|
||||
|
||||
# Mutations
|
||||
```
|
||||
removed #text before div0/#text0
|
||||
removed div0/#text2 before div0/#text0
|
||||
inserted div0/#text2
|
||||
```
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
walk,
|
||||
data,
|
||||
loop,
|
||||
set,
|
||||
setLoopOf,
|
||||
Loop,
|
||||
Scope,
|
||||
@ -73,7 +74,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.children;
|
||||
set(scope, offset, input.children);
|
||||
execInputChildren(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ inserted #text0, #text1, #text2
|
||||
# Mutations
|
||||
```
|
||||
inserted #comment0
|
||||
removed #text before
|
||||
removed #text before
|
||||
removed #text before #text
|
||||
removed #text before #text
|
||||
removed #text before #comment0
|
||||
```
|
||||
|
||||
|
||||
@ -2,9 +2,11 @@ import {
|
||||
walk,
|
||||
data,
|
||||
loop,
|
||||
set,
|
||||
setLoopOf,
|
||||
Loop,
|
||||
Scope,
|
||||
checkDirty,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
@ -78,7 +80,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.children;
|
||||
set(scope, offset, input.children);
|
||||
execInputChildren(scope, offset);
|
||||
};
|
||||
|
||||
@ -95,5 +97,10 @@ const iter0 = createRenderer(
|
||||
);
|
||||
|
||||
const iter0_execItem = (scope: Scope) => {
|
||||
data(scope[3] as Text, (scope[0] as Input["children"][number]).text);
|
||||
if (checkDirty(scope, 0)) {
|
||||
set(scope, 4, (scope[0] as Input["children"][number]).text);
|
||||
if (checkDirty(scope, 4)) {
|
||||
data(scope[3] as Text, scope[4]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,9 +2,11 @@ import {
|
||||
walk,
|
||||
data,
|
||||
loop,
|
||||
set,
|
||||
setLoopOf,
|
||||
Loop,
|
||||
Scope,
|
||||
checkDirty,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
@ -74,7 +76,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.children;
|
||||
set(scope, offset, input.children);
|
||||
execInputChildren(scope, offset);
|
||||
};
|
||||
|
||||
@ -91,5 +93,10 @@ const iter0 = createRenderer(
|
||||
);
|
||||
|
||||
const iter0_execItem = (scope: Scope) => {
|
||||
data(scope[3] as Text, (scope[0] as Input["children"][number]).text);
|
||||
if (checkDirty(scope, 0)) {
|
||||
set(scope, 4, (scope[0] as Input["children"][number]).text);
|
||||
if (checkDirty(scope, 4)) {
|
||||
data(scope[3] as Text, scope[4]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -20,10 +20,10 @@ inserted div0
|
||||
|
||||
# Mutations
|
||||
```
|
||||
removed #text after div0/#text1
|
||||
removed div0/#text0 after div0/#text1
|
||||
inserted div0/#text0
|
||||
div0/#text1: "a" => "d"
|
||||
div0/#text0: "b" => "c"
|
||||
div0/#text1: "a" => "d"
|
||||
```
|
||||
|
||||
|
||||
@ -36,6 +36,6 @@ div0/#text0: "b" => "c"
|
||||
|
||||
# Mutations
|
||||
```
|
||||
removed #text after div0/#text1
|
||||
removed div0/#text0 after div0/#text1
|
||||
inserted div0/#text0
|
||||
```
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
walk,
|
||||
data,
|
||||
set,
|
||||
conditional,
|
||||
setConditionalRenderer,
|
||||
Conditional,
|
||||
@ -49,8 +50,8 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
scope[offset + 1] = input.visible;
|
||||
set(scope, offset, input.value);
|
||||
set(scope, offset + 1, input.visible);
|
||||
execInputValueVisible(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
setConditionalRenderer,
|
||||
Conditional,
|
||||
Scope,
|
||||
set,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
@ -46,7 +47,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
setConditionalRenderer,
|
||||
Conditional,
|
||||
Scope,
|
||||
set,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
@ -46,7 +47,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
setConditionalRenderer,
|
||||
Conditional,
|
||||
Scope,
|
||||
set,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
@ -46,7 +47,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -5,12 +5,12 @@ import {
|
||||
setConditionalRenderer,
|
||||
Conditional,
|
||||
Scope,
|
||||
set,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods,
|
||||
dynamicFragmentMethods,
|
||||
checkDirty,
|
||||
checkDirtyNotEqual
|
||||
checkDirty
|
||||
} from "../../../../src/dom/index";
|
||||
import { next, over, get } from "../../utils/walks";
|
||||
|
||||
@ -59,15 +59,12 @@ export const hydrate = (scope: Scope, offset: number) => {
|
||||
|
||||
export const execInputShowValue1Value2 = (scope: Scope, offset: number) => {
|
||||
const cond0 = scope[offset + 3] as Conditional;
|
||||
if (checkDirtyNotEqual(scope, offset)) {
|
||||
if (checkDirty(scope, offset)) {
|
||||
setConditionalRenderer(cond0, scope[offset] ? branch0 : undefined);
|
||||
}
|
||||
if (cond0.renderer === branch0) {
|
||||
const cond0_scope = cond0.scope;
|
||||
if (
|
||||
checkDirtyNotEqual(scope, offset) ||
|
||||
checkDirtyNotEqual(scope, offset + 1)
|
||||
) {
|
||||
if (checkDirty(scope, offset) || checkDirty(scope, offset + 1)) {
|
||||
const cond0_0 = cond0_scope[0] as Conditional;
|
||||
setConditionalRenderer(
|
||||
cond0_0,
|
||||
@ -78,10 +75,7 @@ export const execInputShowValue1Value2 = (scope: Scope, offset: number) => {
|
||||
data(cond0_0_scope[0] as Text, scope[offset + 1]);
|
||||
}
|
||||
}
|
||||
if (
|
||||
checkDirtyNotEqual(scope, offset) ||
|
||||
checkDirtyNotEqual(scope, offset + 2)
|
||||
) {
|
||||
if (checkDirty(scope, offset) || checkDirty(scope, offset + 2)) {
|
||||
const cond0_1 = cond0_scope[1] as Conditional;
|
||||
setConditionalRenderer(
|
||||
cond0_1,
|
||||
@ -100,9 +94,9 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.show;
|
||||
scope[offset + 1] = input.value1;
|
||||
scope[offset + 2] = input.value2;
|
||||
set(scope, offset, input.show);
|
||||
set(scope, offset + 1, input.value1);
|
||||
set(scope, offset + 2, input.value2);
|
||||
execInputShowValue1Value2(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
setConditionalRendererOnlyChild,
|
||||
Conditional,
|
||||
Scope,
|
||||
set,
|
||||
createRenderer,
|
||||
createRenderFn,
|
||||
staticNodeMethods
|
||||
@ -46,7 +47,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
attr,
|
||||
walk,
|
||||
register,
|
||||
set,
|
||||
createRenderFn,
|
||||
Scope
|
||||
} from "../../../../src/dom/index";
|
||||
@ -44,7 +45,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
attrs,
|
||||
walk,
|
||||
set,
|
||||
register,
|
||||
createRenderFn,
|
||||
Scope
|
||||
@ -41,7 +42,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
data,
|
||||
walk,
|
||||
set,
|
||||
enableExtendedWalk,
|
||||
register,
|
||||
createRenderFn,
|
||||
@ -36,7 +37,7 @@ export const execDynamicInput = (
|
||||
scope: Scope,
|
||||
offset: number
|
||||
) => {
|
||||
scope[offset] = input.value;
|
||||
set(scope, offset, input.value);
|
||||
execInputValue(scope, offset);
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user