$global hydration (#2093)

This commit is contained in:
Luke LaValva 2024-01-29 15:03:29 -08:00 committed by GitHub
parent 54575cf53a
commit 688982e757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 545 additions and 228 deletions

View File

@ -7,81 +7,81 @@
{
"name": "*",
"total": {
"min": 13105,
"gzip": 5618,
"brotli": 5130
"min": 12923,
"gzip": 5520,
"brotli": 5018
}
},
{
"name": "counter",
"user": {
"min": 351,
"gzip": 276,
"gzip": 274,
"brotli": 234
},
"runtime": {
"min": 4083,
"gzip": 1894,
"brotli": 1681
"min": 4123,
"gzip": 1905,
"brotli": 1705
},
"total": {
"min": 4434,
"gzip": 2170,
"brotli": 1915
"min": 4474,
"gzip": 2179,
"brotli": 1939
}
},
{
"name": "counter 💧",
"user": {
"min": 204,
"gzip": 179,
"brotli": 151
"gzip": 178,
"brotli": 154
},
"runtime": {
"min": 2612,
"gzip": 1350,
"brotli": 1210
"min": 2664,
"gzip": 1370,
"brotli": 1223
},
"total": {
"min": 2816,
"gzip": 1529,
"brotli": 1361
"min": 2868,
"gzip": 1548,
"brotli": 1377
}
},
{
"name": "comments",
"user": {
"min": 1216,
"gzip": 701,
"brotli": 636
"gzip": 708,
"brotli": 638
},
"runtime": {
"min": 7506,
"gzip": 3457,
"brotli": 3116
"min": 7536,
"gzip": 3491,
"brotli": 3142
},
"total": {
"min": 8722,
"gzip": 4158,
"brotli": 3752
"min": 8752,
"gzip": 4199,
"brotli": 3780
}
},
{
"name": "comments 💧",
"user": {
"min": 988,
"gzip": 587,
"brotli": 544
"gzip": 591,
"brotli": 554
},
"runtime": {
"min": 7999,
"gzip": 3683,
"brotli": 3342
"min": 8047,
"gzip": 3690,
"brotli": 3345
},
"total": {
"min": 8987,
"gzip": 4270,
"brotli": 3886
"min": 9035,
"gzip": 4281,
"brotli": 3899
}
}
]

View File

@ -4,8 +4,6 @@ export type Renderer = (...args: unknown[]) => unknown;
export type CommentWalker = TreeWalker & Record<string, Comment>;
export type ScopeContext = Record<string, [Scope, number | string]>;
export type Scope<
T extends { [x: string | number]: unknown } = {
[x: string | number]: unknown;
@ -18,7 +16,7 @@ export type Scope<
___client: boolean;
___bound: Map<unknown, unknown> | undefined;
___renderer: ClientRenderer | undefined;
___context: ScopeContext | undefined;
$global: Record<string, unknown>;
_: Scope | undefined;
[x: string | number]: any;
} & T;
@ -47,12 +45,9 @@ export const enum AccessorChars {
TAG_VARIABLE = "/",
COND_SCOPE = "!",
LOOP_SCOPE_ARRAY = "!",
COND_CONTEXT = "^",
LOOP_CONTEXT = "^",
COND_RENDERER = "(",
LOOP_SCOPE_MAP = "(",
LOOP_VALUE = ")",
CONTEXT_VALUE = ":",
PREVIOUS_ATTRIBUTES = "~",
}

View File

@ -83,11 +83,7 @@ export function setConditionalRenderer<ChildScope extends Scope>(
if (newRenderer) {
newScope = scope[nodeAccessor + AccessorChars.COND_SCOPE] =
createScopeWithRenderer(
newRenderer,
(scope[nodeAccessor + AccessorChars.COND_CONTEXT] ||= scope.___context),
scope,
) as ChildScope;
createScopeWithRenderer(newRenderer, scope.$global, scope) as ChildScope;
prevScope = prevScope || getEmptyScope(scope[nodeAccessor] as Comment);
} else {
newScope = getEmptyScope(scope[nodeAccessor] as Comment) as ChildScope;
@ -132,11 +128,7 @@ export function setConditionalRendererOnlyChild(
if (newRenderer) {
const newScope = (scope[nodeAccessor + AccessorChars.COND_SCOPE] =
createScopeWithRenderer(
newRenderer,
(scope[nodeAccessor + AccessorChars.COND_CONTEXT] ||= scope.___context),
scope,
));
createScopeWithRenderer(newRenderer, scope.$global, scope));
(newRenderer.___fragment ?? defaultFragment).___insertBefore(
newScope,
referenceNode,
@ -243,12 +235,7 @@ function loop(
let childScope = oldMap.get(key);
const isNew = !childScope;
if (!childScope) {
childScope = createScopeWithRenderer(
renderer,
(scope[nodeAccessor + AccessorChars.LOOP_CONTEXT] ||=
scope.___context),
scope,
);
childScope = createScopeWithRenderer(renderer, scope.$global, scope);
// TODO: once we can track moves
// needsReconciliation = true;
} else {

View File

@ -35,7 +35,6 @@ export type { Scope } from "../common/types";
export {
createRenderer,
initContextProvider,
dynamicTagAttrs,
createScopeWithRenderer,
} from "./renderer";
@ -48,7 +47,6 @@ export {
intersection,
closure,
dynamicClosure,
contextClosure,
dynamicSubscribers,
childClosures,
setTagVar,

View File

@ -1,12 +1,7 @@
import {
type Accessor,
AccessorChars,
type Scope,
type ScopeContext,
} from "../common/types";
import { type Accessor, AccessorChars, type Scope } from "../common/types";
import { setConditionalRendererOnlyChild } from "./control-flow";
import { attrs } from "./dom";
import { type DOMFragment, defaultFragment } from "./fragment";
import { type DOMFragment } from "./fragment";
import { bindRenderer, createScope } from "./scope";
import type { IntersectionSignal, ValueSignal } from "./signals";
import { WalkCodes, trimWalkString, walk } from "./walker";
@ -41,10 +36,10 @@ type SetupFn = (scope: Scope) => void;
export function createScopeWithRenderer(
renderer: RendererOrElementName,
context: ScopeContext,
$global: Scope["___global"],
ownerScope?: Scope,
) {
const newScope = createScope(context as ScopeContext);
const newScope = createScope($global);
newScope._ = renderer.___owner || ownerScope;
newScope.___renderer = renderer as Renderer;
initRenderer(renderer, newScope);
@ -56,34 +51,6 @@ export function createScopeWithRenderer(
return newScope;
}
export function initContextProvider(
scope: Scope,
scopeAccessor: number,
valueAccessor: number,
contextKey: string,
renderer: Renderer,
) {
const node: Node = scope[scopeAccessor];
const newScope = createScopeWithRenderer(
renderer,
{
...scope.___context,
[contextKey]: [scope, valueAccessor],
},
scope,
);
(renderer.___fragment ?? defaultFragment).___insertBefore(
newScope,
node.parentNode!,
node.nextSibling,
);
for (const signal of renderer.___closureSignals) {
signal(newScope, true);
}
}
export function initRenderer(renderer: RendererOrElementName, scope: Scope) {
const dom =
typeof renderer === "string"

View File

@ -16,7 +16,7 @@ export function register<T>(id: string, obj: T): T {
return obj;
}
export const scopeLookup = {} as Record<number, Scope>;
export const scopeLookup = {} as Record<number | string, Scope>;
export function init(
runtimeId = ResumeSymbols.DEFAULT_RUNTIME_ID /* [a-zA-Z0-9]+ */,
@ -46,50 +46,56 @@ export function init(
}
};
Object.defineProperty(window, resumeVar, {
get() {
return fakeArray;
},
});
if (initialHydration) {
for (let i = 0; i < initialHydration.length; i += 2) {
resume(initialHydration[i], initialHydration[i + 1]);
}
} else {
(window as any)[resumeVar] = fakeArray;
}
function resume(
scopesFn: (
b: typeof bind,
s: typeof scopeLookup,
...rest: unknown[]
) => Record<string, Scope>,
scopesFn:
| null
| ((
b: typeof bind,
s: typeof scopeLookup,
...rest: unknown[]
) => Record<string, Scope>),
calls: Array<string | number>,
) {
// TODO: Can be refactored/removed when adding runtimeId and componentIdPrefix
/**
* Necessary for injecting content into an existing document (e.g. microframe)
*/
if (doc.readyState !== "loading") {
walker.currentNode = doc;
}
const scopes = scopesFn?.(bind, scopeLookup);
if (scopesFn) {
const scopes = scopesFn(bind, scopeLookup);
scopeLookup.$global ||= scopes.$global || {};
/**
* Loop over all the new hydration scopes and see if a previous walk
* had to create a dummy scope to store Nodes of interest.
* If so merge them and set/replace the scope in the scopeLookup.
*/
for (const scopeIdAsString in scopes) {
const scopeId = parseInt(scopeIdAsString);
const scope = scopes[scopeId];
const storedScope = scopeLookup[scopeId];
if (storedScope !== scope) {
scopeLookup[scopeId] = Object.assign(scope, storedScope);
/**
* Loop over all the new hydration scopes and see if a previous walk
* had to create a dummy scope to store Nodes of interest.
* If so merge them and set/replace the scope in the scopeLookup.
*/
for (const scopeIdAsString in scopes) {
if (scopeIdAsString === "$global") continue;
const scopeId = parseInt(scopeIdAsString);
const scope = scopes[scopeId];
const storedScope = scopeLookup[scopeId];
scope.$global = scopes.$global;
if (storedScope !== scope) {
scopeLookup[scopeId] = Object.assign(scope, storedScope) as Scope;
}
}
}
while ((currentNode = walker.nextNode() as ChildNode)) {
const nodeValue = currentNode.nodeValue;
if (nodeValue?.startsWith(`${runtimeId}`)) {
const nodeValue = currentNode.nodeValue!;
if (nodeValue.startsWith(runtimeId)) {
const token = nodeValue[runtimeLength];
const scopeId = parseInt(nodeValue.slice(runtimeLength + 1));
const scope = getScope(scopeId);

View File

@ -1,20 +1,20 @@
import type { Scope, ScopeContext } from "../common/types";
import type { Scope } from "../common/types";
import { queueEffect } from "./queue";
import type { Renderer } from "./renderer";
let debugID = 0;
export function createScope(context?: ScopeContext): Scope {
export function createScope($global: Scope["$global"]): Scope {
const scope = {} as Scope;
if (MARKO_DEBUG) {
scope.___debugId = debugID++;
}
scope.___client = true;
scope.___context = context;
scope.$global = $global;
return scope;
}
const emptyScope = createScope();
const emptyScope = createScope({});
export function getEmptyScope(marker?: Comment) {
emptyScope.___startNode = emptyScope.___endNode = marker;
return emptyScope;

View File

@ -202,24 +202,6 @@ export function dynamicClosure<T>(
});
}
export function contextClosure<T>(
valueAccessor: Accessor,
contextKey: string,
fn: ValueSignal<T>,
intersection?: IntersectionSignal,
valueWithIntersection?: ValueSignal,
) {
// TODO: might be viable as a reliable way to get a unique id
// const dirtyAccessor = valueAccessor - 2;
return dynamicClosure(
(scope) => scope.___context![contextKey][1],
value(valueAccessor, fn),
(scope) => scope.___context![contextKey][0],
intersection,
valueWithIntersection,
);
}
export function childClosures(
closureSignals: IntersectionSignal[],
childAccessor: Accessor,

View File

@ -22,14 +22,15 @@ export class ClientTemplate implements Template {
}
mount(
input: Input,
templateInput: Input & { $global?: Record<string, unknown> } = {},
reference: ParentNode & Node,
position?: InsertPosition,
): TemplateInstance {
let scope!: Scope, dom!: Node;
const { $global = {}, ...input } = templateInput;
const attrs = this._.___attrs;
const effects = prepare(() => {
scope = createScope();
scope = createScope($global);
dom = initRenderer(this._, scope);
if (attrs) {
attrs(scope, input);

View File

@ -107,7 +107,7 @@ function walkInternal(
MARKO_DEBUG
? getDebugKey(currentScopeIndex++, "#childScope")
: currentScopeIndex++
] = createScope(scope.___context)),
] = createScope(scope.$global)),
currentWalkIndex,
)!;
} else if (value === WalkCodes.EndChild) {

View File

@ -49,7 +49,7 @@ export function createRenderFn(renderer: Renderer) {
return (
stream: Writable,
input: Input = {},
global: Record<string, unknown> = {},
$global?: Record<string, unknown>,
streamState: Partial<StreamData> = {},
) => {
let remainingChildren = 1;
@ -67,7 +67,7 @@ export function createRenderFn(renderer: Renderer) {
};
$_buffer = createInitialBuffer(stream);
streamState.global = global;
streamState.global = $global;
$_streamData = createStreamState(streamState);
$_buffer.onReject = reject;
@ -473,7 +473,9 @@ export function writeScope(
scope = Object.assign(assignTo, scope);
}
}
$_buffer!.scopes = $_buffer!.scopes || {};
$_buffer!.scopes ??= {
$global: getFilteredGlobals($_streamData!.global) as any,
};
$_buffer!.scopes[scopeId] = scope;
$_streamData!.scopeLookup.set(scopeId, scope);
}
@ -524,3 +526,44 @@ function getResumeScript(
}
return "";
}
function getFilteredGlobals($global: Record<string, unknown>) {
if (!$global) return undefined;
const serializedGlobals = $global.serializedGlobals as
| string[]
| Record<string, boolean>
| undefined;
if (!serializedGlobals) return undefined;
let filtered: undefined | Record<string, unknown>;
if (Array.isArray(serializedGlobals)) {
for (const key of serializedGlobals) {
const value = $global[key];
if (value !== undefined) {
if (filtered) {
filtered[key] = value;
} else {
filtered = { [key]: value };
}
}
}
} else {
for (const key in serializedGlobals) {
if (serializedGlobals[key]) {
const value = $global[key];
if (value !== undefined) {
if (filtered) {
filtered[key] = value;
} else {
filtered = { [key]: value };
}
}
}
}
}
return filtered;
}

View File

@ -35,7 +35,6 @@ type Result = {
};
type TestConfig = {
context?: Record<string, unknown>;
steps?: unknown[] | (() => Promise<unknown[]>);
skip_dom?: boolean;
skip_html?: boolean;
@ -134,14 +133,12 @@ describe("translator-interop", () => {
}),
});
const document = browser.window.document;
const [input = {}] = (
const [input] = (
typeof config.steps === "function"
? await config.steps()
: config.steps || []
) as [Input];
input.$global = config.context;
document.open();
const tracker = createMutationTracker(browser.window, document);

View File

@ -0,0 +1,50 @@
# Render {"$global":{"x":1,"serializedGlobals":["x"]}}
```html
<div>
<button>
Toggle
</button>
</div>
```
# Render
container.querySelector("button").click()
```html
<div>
<span>
1
</span>
<button>
Toggle
</button>
</div>
```
# Render
container.querySelector("button").click()
```html
<div>
<button>
Toggle
</button>
</div>
```
# Render
container.querySelector("button").click()
```html
<div>
<span>
1
</span>
<button>
Toggle
</button>
</div>
```

View File

@ -0,0 +1,73 @@
# Render {"$global":{"x":1,"serializedGlobals":["x"]}}
```html
<div>
<button>
Toggle
</button>
</div>
```
# Mutations
```
inserted div0
```
# Render
container.querySelector("button").click()
```html
<div>
<span>
1
</span>
<button>
Toggle
</button>
</div>
```
# Mutations
```
inserted div0/span0
removed #text after div0/span0
```
# Render
container.querySelector("button").click()
```html
<div>
<button>
Toggle
</button>
</div>
```
# Mutations
```
inserted div0/#text0
removed span after div0/#text0
```
# Render
container.querySelector("button").click()
```html
<div>
<span>
1
</span>
<button>
Toggle
</button>
</div>
```
# Mutations
```
inserted div0/span0
removed #text after div0/span0
```

View File

@ -0,0 +1,23 @@
import { data as _data, on as _on, queueSource as _queueSource, createRenderer as _createRenderer, register as _register, conditional as _conditional, queueEffect as _queueEffect, value as _value, createTemplate as _createTemplate } from "@marko/runtime-tags/src/dom";
const _setup$ifBody = _scope => {
_data(_scope["#text/0"], _scope.$global.x);
};
const _ifBody = _register("packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_1_renderer", /* @__PURE__ */_createRenderer("<span> </span>", /* next(1), get */"D ", _setup$ifBody));
const _if = /* @__PURE__ */_conditional("#text/0");
const _show_effect = _register("packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show", _scope => _on(_scope["#button/1"], "click", function () {
const {
show
} = _scope;
_queueSource(_scope, _show, !show);
}));
const _show = /* @__PURE__ */_value("show", (_scope, show) => {
_queueEffect(_scope, _show_effect);
_if(_scope, show ? _ifBody : null);
});
const _setup = _scope => {
_show(_scope, false);
};
export const template = "<div><!><button>Toggle</button></div>";
export const walks = /* next(1), replace, over(1), get, out(1) */"D%b l";
export const setup = _setup;
export default /* @__PURE__ */_createTemplate( /* @__PURE__ */_createRenderer(template, walks, setup), "packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko");

View File

@ -0,0 +1,24 @@
import { write as _write, $_streamData as _$_streamData, escapeXML as _escapeXML, markResumeNode as _markResumeNode, serializedScope as _serializedScope, writeScope as _writeScope, nextScopeId as _nextScopeId, createRenderer as _createRenderer, register as _register, markResumeControlSingleNodeEnd as _markResumeControlSingleNodeEnd, writeEffect as _writeEffect, createTemplate as _createTemplate } from "@marko/runtime-tags/src/html";
const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar) => {
const _scope0_id = _nextScopeId();
const show = false;
_write("<div>");
let _ifScopeId, _scope1_, _ifRenderer;
if (show) {
const _scope1_id = _nextScopeId();
_write(`<span>${_escapeXML(_$_streamData.global.x)}${_markResumeNode(_scope1_id, "#text/0")}</span>`);
_writeScope(_scope1_id, _scope1_ = {
"_": _serializedScope(_scope0_id)
});
_register(_ifRenderer = /* @__PURE__ */_createRenderer(() => {}), "packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_1_renderer");
_ifScopeId = _scope1_id;
}
_write(`${_markResumeControlSingleNodeEnd(_scope0_id, "#text/0", _ifScopeId)}<button>Toggle</button>${_markResumeNode(_scope0_id, "#button/1")}</div>`);
_writeEffect(_scope0_id, "packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show");
_writeScope(_scope0_id, {
"show": show,
"#text/0!": _scope1_,
"#text/0(": _ifRenderer
});
});
export default /* @__PURE__ */_createTemplate(_renderer, "packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko");

View File

@ -0,0 +1,50 @@
# Render {"$global":{"x":1,"serializedGlobals":["x"]}}
```html
<div>
<button>
Toggle
</button>
</div>
```
# Render
container.querySelector("button").click()
```html
<div>
<span>
1
</span>
<button>
Toggle
</button>
</div>
```
# Render
container.querySelector("button").click()
```html
<div>
<button>
Toggle
</button>
</div>
```
# Render
container.querySelector("button").click()
```html
<div>
<span>
1
</span>
<button>
Toggle
</button>
</div>
```

View File

@ -0,0 +1,111 @@
# Render {"$global":{"x":1,"serializedGlobals":["x"]}}
```html
<html>
<head />
<body>
<div>
<!--M|0 #text/0 -->
<button>
Toggle
</button>
<!--M*0 #button/1-->
</div>
<script>
(M$h=[]).push((b,s)=&gt;({0:{show:!1},$global:{x:1}}),[0,"packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show",])
</script>
</body>
</html>
```
# Mutations
```
```
# Render
container.querySelector("button").click()
```html
<html>
<head />
<body>
<div>
<span>
1
</span>
<button>
Toggle
</button>
<!--M*0 #button/1-->
</div>
<script>
(M$h=[]).push((b,s)=&gt;({0:{show:!1},$global:{x:1}}),[0,"packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show",])
</script>
</body>
</html>
```
# Mutations
```
inserted #document/html0/body1/div0/span0
removed #comment after #document/html0/body1/div0/span0
```
# Render
container.querySelector("button").click()
```html
<html>
<head />
<body>
<div>
<!--M|0 #text/0 -->
<button>
Toggle
</button>
<!--M*0 #button/1-->
</div>
<script>
(M$h=[]).push((b,s)=&gt;({0:{show:!1},$global:{x:1}}),[0,"packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show",])
</script>
</body>
</html>
```
# Mutations
```
inserted #document/html0/body1/div0/#comment0
removed span after #document/html0/body1/div0/#comment0
```
# Render
container.querySelector("button").click()
```html
<html>
<head />
<body>
<div>
<span>
1
</span>
<button>
Toggle
</button>
<!--M*0 #button/1-->
</div>
<script>
(M$h=[]).push((b,s)=&gt;({0:{show:!1},$global:{x:1}}),[0,"packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show",])
</script>
</body>
</html>
```
# Mutations
```
inserted #document/html0/body1/div0/span0
removed #comment after #document/html0/body1/div0/span0
```

View File

@ -0,0 +1,8 @@
# Render "End"
```html
<div>
<button>
Toggle
</button>
</div>
```

View File

@ -0,0 +1,36 @@
# Write
<div><!M|0 #text/0 ><button>Toggle</button><!M*0 #button/1></div><script>(M$h=[]).push((b,s)=>({0:{show:!1},$global:{x:1}}),[0,"packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show",])</script>
# Render "End"
```html
<html>
<head />
<body>
<div>
<!--M|0 #text/0 -->
<button>
Toggle
</button>
<!--M*0 #button/1-->
</div>
<script>
(M$h=[]).push((b,s)=&gt;({0:{show:!1},$global:{x:1}}),[0,"packages/translator-tags/src/__tests__/fixtures/dollar-global-client/template.marko_0_show",])
</script>
</body>
</html>
```
# Mutations
```
inserted #document/html0
inserted #document/html0/head0
inserted #document/html0/body1
inserted #document/html0/body1/div0
inserted #document/html0/body1/div0/#comment0
inserted #document/html0/body1/div0/button1
inserted #document/html0/body1/div0/button1/#text0
inserted #document/html0/body1/div0/#comment2
inserted #document/html0/body1/script1
inserted #document/html0/body1/script1/#text0
```

View File

@ -0,0 +1,9 @@
<div>
<let/show=false/>
<if=show>
<span>${$global.x}</span>
</if>
<button onClick() { show = !show; }>
Toggle
</button>
</div>

View File

@ -0,0 +1,10 @@
export const steps = [
{ $global: { x: 1, serializedGlobals: ["x"] } },
click,
click,
click,
];
function click(container: Element) {
container.querySelector("button")!.click();
}

View File

@ -1,6 +1,6 @@
import { $_streamData as _$_streamData, data as _data, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-tags/src/dom";
import { data as _data, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-tags/src/dom";
const _setup = _scope => {
_data(_scope["#text/0"], _$_streamData.global.x);
_data(_scope["#text/0"], _scope.$global.x);
};
export const template = "<div><span> </span></div>";
export const walks = /* next(2), get, out(2) */"E m";

View File

@ -1,3 +1,3 @@
export const steps = [{ $global: { x: 1 } }];
export const skip_csr = true;
export const skip_resume = true;
export const context = { x: 1 };

View File

@ -21,7 +21,6 @@ const reorderRuntimeString = String(reorderRuntime).replace(
);
type TestConfig = {
context?: Record<string, unknown>;
steps?: unknown[] | (() => Promise<unknown[]>);
skip_dom?: boolean;
skip_html?: boolean;
@ -167,8 +166,6 @@ describe("translator-tags", () => {
: config.steps || []
) as [Input];
input.$global = config.context;
document.open();
const tracker = createMutationTracker(browser.window, document);

View File

@ -27,7 +27,6 @@ const pureFunctions: Array<keyof typeof import("@marko/runtime-tags/src/dom")> =
"intersection",
"closure",
"dynamicClosure",
"contextClosure",
"loopOf",
"loopIn",
"loopTo",

View File

@ -66,12 +66,9 @@ const enum AccessorChars {
TAG_VARIABLE = "/",
COND_SCOPE = "!",
LOOP_SCOPE_ARRAY = "!",
COND_CONTEXT = "^",
LOOP_CONTEXT = "^",
COND_RENDERER = "(",
LOOP_SCOPE_MAP = "(",
LOOP_VALUE = ")",
CONTEXT_VALUE = ":",
}
const [getSignals] = createSectionState<Map<unknown, Signal>>(
@ -236,59 +233,6 @@ export function initValue(
return signal;
}
export function initContextProvider(
templateId: string,
reserve: Reserve,
providers: References,
compute: t.Expression,
renderer: t.Identifier,
) {
const section = reserve.section;
const scopeAccessor = getScopeAccessorLiteral(reserve);
const valueAccessor = t.stringLiteral(
`${reserve.id}${AccessorChars.CONTEXT_VALUE}`,
);
const signal = initValue(reserve, valueAccessor);
addValue(section, providers, signal, compute);
signal.hasDynamicSubscribers = true;
signal.hasDownstreamIntersections = () => true;
addStatement(
"render",
reserve.section,
undefined,
t.expressionStatement(
callRuntime(
"initContextProvider",
scopeIdentifier,
scopeAccessor,
valueAccessor,
t.stringLiteral(templateId),
renderer,
),
),
);
return signal;
}
export function initContextConsumer(templateId: string, reserve: Reserve) {
const section = reserve.section;
const signal = getSignal(section, reserve);
getClosures(section).push(signal.identifier);
signal.build = () => {
return callRuntime(
"contextClosure",
getScopeAccessorLiteral(reserve),
t.stringLiteral(templateId),
getSignalFn(signal, [scopeIdentifier, t.identifier(reserve.name)]),
);
};
return signal;
}
export function getSignalFn(
signal: Signal,
params: Array<t.Identifier | t.Pattern>,

View File

@ -1,7 +1,8 @@
import { types as t } from "@marko/compiler";
import isStatic from "../util/is-static";
import { isOutputHTML } from "../util/marko-config";
import { importRuntime } from "../util/runtime";
import { currentProgramPath } from "./program";
import { currentProgramPath, scopeIdentifier } from "./program";
const globalImportIdentifier = new WeakMap<
t.NodePath<t.Program>,
@ -50,18 +51,24 @@ export default {
switch (name) {
case "$global":
{
let streamDataIdentifier =
globalImportIdentifier.get(currentProgramPath);
if (!streamDataIdentifier) {
streamDataIdentifier = importRuntime("$_streamData");
globalImportIdentifier.set(
currentProgramPath,
streamDataIdentifier,
if (isOutputHTML()) {
let streamDataIdentifier =
globalImportIdentifier.get(currentProgramPath);
if (!streamDataIdentifier) {
streamDataIdentifier = importRuntime("$_streamData");
globalImportIdentifier.set(
currentProgramPath,
streamDataIdentifier,
);
}
identifier.replaceWith(
t.memberExpression(streamDataIdentifier, t.identifier("global")),
);
} else {
identifier.replaceWith(
t.memberExpression(scopeIdentifier, t.identifier("$global")),
);
}
identifier.replaceWith(
t.memberExpression(streamDataIdentifier, t.identifier("global")),
);
}
break;
}