mirror of
https://github.com/marko-js/marko.git
synced 2026-02-01 16:07:13 +00:00
feat: pull in custom serializer
This commit is contained in:
parent
2905a7abcf
commit
d2e723b5df
@ -6,9 +6,9 @@
|
||||
{
|
||||
"name": "*",
|
||||
"individual": {
|
||||
"min": 11965,
|
||||
"gzip": 5131,
|
||||
"brotli": 4621
|
||||
"min": 11962,
|
||||
"gzip": 5140,
|
||||
"brotli": 4628
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Write
|
||||
<!M^ROOT><body><!M#6 ROOT 6><button><!M#1 ROOT 7>0</button></body><!M/ROOT><script>(M$h=[]).push({"ROOT":["ROOT",null,null,null,null,null,null,null,0]},["counter",6,"ROOT",])</script>
|
||||
<!M^ROOT><body><!M#6 ROOT 6><button><!M#1 ROOT 7>0</button></body><!M/ROOT><script>(M$h=[]).push((b,s)=>({ROOT:["ROOT",,,,,,,,0]}),["counter",6,"ROOT",])</script>
|
||||
|
||||
|
||||
# Render "End"
|
||||
@ -14,7 +14,7 @@
|
||||
0
|
||||
</button>
|
||||
<script>
|
||||
(M$h=[]).push({"ROOT":["ROOT",null,null,null,null,null,null,null,0]},["counter",6,"ROOT",])
|
||||
(M$h=[]).push((b,s)=>({ROOT:["ROOT",,,,,,,,0]}),["counter",6,"ROOT",])
|
||||
</script>
|
||||
</body>
|
||||
<!--M/ROOT-->
|
||||
@ -49,7 +49,7 @@ inserted #document/html1/body1/script2/#text0
|
||||
0
|
||||
</button>
|
||||
<script>
|
||||
(M$h=[]).push({"ROOT":["ROOT",null,null,null,null,null,null,null,0]},["counter",6,"ROOT",])
|
||||
(M$h=[]).push((b,s)=>({ROOT:["ROOT",,,,,,,,0]}),["counter",6,"ROOT",])
|
||||
</script>
|
||||
</body>
|
||||
<!--M/ROOT-->
|
||||
@ -76,7 +76,7 @@ container.querySelector("button").click();
|
||||
1
|
||||
</button>
|
||||
<script>
|
||||
(M$h=[]).push({"ROOT":["ROOT",null,null,null,null,null,null,null,0]},["counter",6,"ROOT",])
|
||||
(M$h=[]).push((b,s)=>({ROOT:["ROOT",,,,,,,,0]}),["counter",6,"ROOT",])
|
||||
</script>
|
||||
</body>
|
||||
<!--M/ROOT-->
|
||||
@ -103,7 +103,7 @@ container.querySelector("button").click();
|
||||
2
|
||||
</button>
|
||||
<script>
|
||||
(M$h=[]).push({"ROOT":["ROOT",null,null,null,null,null,null,null,0]},["counter",6,"ROOT",])
|
||||
(M$h=[]).push((b,s)=>({ROOT:["ROOT",,,,,,,,0]}),["counter",6,"ROOT",])
|
||||
</script>
|
||||
</body>
|
||||
<!--M/ROOT-->
|
||||
@ -130,7 +130,7 @@ container.querySelector("button").click();
|
||||
3
|
||||
</button>
|
||||
<script>
|
||||
(M$h=[]).push({"ROOT":["ROOT",null,null,null,null,null,null,null,0]},["counter",6,"ROOT",])
|
||||
(M$h=[]).push((b,s)=>({ROOT:["ROOT",,,,,,,,0]}),["counter",6,"ROOT",])
|
||||
</script>
|
||||
</body>
|
||||
<!--M/ROOT-->
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Scope, ScopeOffsets, HydrateSymbols } from "../common/types";
|
||||
import { runWithScope } from "./scope";
|
||||
import { bind, runWithScope } from "./scope";
|
||||
|
||||
type HydrateFn = () => void;
|
||||
|
||||
@ -23,9 +23,14 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
|
||||
let currentScope: Scope;
|
||||
let currentOffset: number;
|
||||
let currentNode: Node;
|
||||
const scopeLookup: Map<string, Scope> = new Map();
|
||||
const scopeLookup: Record<string, Scope> = {};
|
||||
const stack: Array<string | number> = [];
|
||||
const fakeArray = { push: hydrate };
|
||||
const bindFunction = (fnId: string, offset: number, scopeId: string) => {
|
||||
const fn = fnsById[fnId];
|
||||
const scope = scopeLookup[scopeId];
|
||||
return bind(fn, offset, scope);
|
||||
};
|
||||
|
||||
Object.defineProperty(window, hydrateVar, {
|
||||
get() {
|
||||
@ -40,13 +45,15 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
|
||||
}
|
||||
|
||||
function hydrate(
|
||||
scopes: Record<string, Scope>,
|
||||
scopesFn: (b, s, ...rest: unknown[]) => Record<string, Scope>,
|
||||
calls: Array<string | number>
|
||||
) {
|
||||
if (doc.readyState !== "loading") {
|
||||
walker.currentNode = doc;
|
||||
}
|
||||
|
||||
const scopes = scopesFn(bindFunction, scopeLookup);
|
||||
|
||||
/**
|
||||
* Loop over all the new hydration scopes and see if a previous walk
|
||||
* had to create a dummy scope to store Nodes of interest.
|
||||
@ -54,13 +61,13 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
|
||||
*/
|
||||
for (const scopeId in scopes) {
|
||||
const scope = scopes[scopeId];
|
||||
const storedScope = scopeLookup.get(scope[ScopeOffsets.ID]);
|
||||
const storedScope = scopeLookup[scopeId];
|
||||
|
||||
if (storedScope !== scope) {
|
||||
if (storedScope) {
|
||||
Object.assign(scope, storedScope);
|
||||
}
|
||||
scopeLookup.set(scope[ScopeOffsets.ID], scope);
|
||||
scopeLookup[scopeId] = scope;
|
||||
if (currentScope === storedScope) {
|
||||
currentScope = scope;
|
||||
}
|
||||
@ -91,10 +98,10 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
|
||||
if (currentScope) {
|
||||
stack.push(currentScope[ScopeOffsets.ID] as string, currentOffset);
|
||||
}
|
||||
currentScope = scopeLookup.get(data)!;
|
||||
currentScope = scopeLookup[data]!;
|
||||
currentOffset = 0;
|
||||
if (!currentScope) {
|
||||
scopeLookup.set(data, (currentScope = [data] as unknown as Scope));
|
||||
scopeLookup[data] = currentScope = [data] as unknown as Scope;
|
||||
}
|
||||
currentScope[ScopeOffsets.START_NODE] = currentNode;
|
||||
} else if (token === HydrateSymbols.SCOPE_END) {
|
||||
@ -106,7 +113,7 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
|
||||
}
|
||||
currentScope[ScopeOffsets.END_NODE] = currentNode;
|
||||
currentOffset = stack.pop() as number;
|
||||
currentScope = scopeLookup.get(stack.pop() as string)!;
|
||||
currentScope = scopeLookup[stack.pop() as string]!;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
} else if ("MARKO_DEBUG") {
|
||||
throw new Error("MALFORMED MARKER: " + nodeValue);
|
||||
@ -118,7 +125,7 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
|
||||
runWithScope(
|
||||
fnsById[calls[i]]!,
|
||||
calls[i + 1] as number,
|
||||
scopeLookup.get(calls[i + 2] as string)
|
||||
scopeLookup[calls[i + 2]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,9 +68,11 @@ export function getOwnerScope(ownerLevel = 1) {
|
||||
return scope;
|
||||
}
|
||||
|
||||
export function bind(fn: (...args: unknown[]) => unknown) {
|
||||
const boundScope = currentScope;
|
||||
const boundOffset = currentOffset;
|
||||
export function bind(
|
||||
fn: (...args: unknown[]) => unknown,
|
||||
boundOffset = currentOffset,
|
||||
boundScope = currentScope
|
||||
) {
|
||||
return fn.length
|
||||
? (...args: unknown[]) => runWithScope(fn, boundOffset, boundScope, args)
|
||||
: () => runWithScope(fn, boundOffset, boundScope);
|
||||
|
||||
@ -21,4 +21,6 @@ export {
|
||||
writeScope,
|
||||
} from "./writer";
|
||||
|
||||
export { register } from "./serializer";
|
||||
|
||||
export { pushContext, popContext, getInContext } from "../common/context";
|
||||
|
||||
428
packages/runtime/src/html/serializer.ts
Normal file
428
packages/runtime/src/html/serializer.ts
Normal file
@ -0,0 +1,428 @@
|
||||
/* eslint "@typescript-eslint/ban-types": ["error", { "types": { "object": false }, "extendDefaults": true }] */
|
||||
|
||||
const { hasOwnProperty } = Object.prototype;
|
||||
const PARAM_BIND = "b";
|
||||
const PARAM_SCOPE = "s";
|
||||
const REF_START_CHARS = "hjkmoquxzABCDEFGHIJKLNPQRTUVWXYZ$_"; // Avoids chars that could evaluate to a reserved word.
|
||||
const REF_START_CHARS_LEN = REF_START_CHARS.length;
|
||||
const REF_CHARS =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_";
|
||||
const REF_CHARS_LEN = REF_CHARS.length;
|
||||
const SYMBOL_FN_ID = Symbol("FN_ID");
|
||||
const SYMBOL_SCOPE_ID = Symbol("SCOPE_ID");
|
||||
const SYMBOL_OFFSET = Symbol("OFFSET");
|
||||
|
||||
type SerializableFn = ((...args: unknown[]) => unknown) & {
|
||||
[SYMBOL_FN_ID]: string;
|
||||
[SYMBOL_SCOPE_ID]: string;
|
||||
[SYMBOL_OFFSET]: number;
|
||||
};
|
||||
|
||||
export function register(
|
||||
fn: (...args: unknown[]) => unknown,
|
||||
fnId: string,
|
||||
scopeId: string,
|
||||
offset: number
|
||||
): SerializableFn {
|
||||
(fn as SerializableFn)[SYMBOL_FN_ID] = fnId;
|
||||
(fn as SerializableFn)[SYMBOL_SCOPE_ID] = scopeId;
|
||||
(fn as SerializableFn)[SYMBOL_OFFSET] = offset;
|
||||
return fn as SerializableFn;
|
||||
}
|
||||
|
||||
export function stringify(root: unknown) {
|
||||
return new Serializer().stringify(root);
|
||||
}
|
||||
export class Serializer {
|
||||
// TODO: hoist these back out?
|
||||
STACK: object[] = [];
|
||||
BUFFER: string[] = [""];
|
||||
ASSIGNMENTS: Map<string, string> = new Map();
|
||||
INDEX_OR_REF: WeakMap<object, number | string> = new WeakMap();
|
||||
REF_COUNT = 0;
|
||||
// These stay
|
||||
PARENTS: WeakMap<object, object> = new WeakMap();
|
||||
KEYS: WeakMap<object, number | string> = new WeakMap();
|
||||
|
||||
constructor() {
|
||||
this.BUFFER.pop();
|
||||
}
|
||||
|
||||
stringify(root: unknown) {
|
||||
if (this.writeProp(root, "", undefined)) {
|
||||
const { BUFFER, REF_COUNT, ASSIGNMENTS, INDEX_OR_REF } = this;
|
||||
|
||||
let result = BUFFER[0];
|
||||
|
||||
for (let i = 1, len = BUFFER.length; i < len; i++) {
|
||||
result += BUFFER[i];
|
||||
}
|
||||
|
||||
if (REF_COUNT) {
|
||||
if (ASSIGNMENTS.size) {
|
||||
let ref = INDEX_OR_REF.get(root as object);
|
||||
|
||||
if (typeof ref === "number") {
|
||||
ref = toRefParam(this.REF_COUNT++);
|
||||
result = ref + "=" + result;
|
||||
}
|
||||
|
||||
for (const [assignmentRef, assignments] of ASSIGNMENTS) {
|
||||
result += "," + assignments + assignmentRef;
|
||||
}
|
||||
|
||||
result += "," + ref;
|
||||
this.ASSIGNMENTS = new Map();
|
||||
}
|
||||
|
||||
result =
|
||||
"(" +
|
||||
PARAM_BIND +
|
||||
"," +
|
||||
PARAM_SCOPE +
|
||||
"," +
|
||||
this.refParamsString() +
|
||||
")=>(" +
|
||||
result +
|
||||
")";
|
||||
} else if (root && (root as object).constructor === Object) {
|
||||
result = "(" + PARAM_BIND + "," + PARAM_SCOPE + ")=>(" + result + ")";
|
||||
}
|
||||
|
||||
BUFFER.length = 0;
|
||||
this.INDEX_OR_REF = new WeakMap();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return "void 0";
|
||||
}
|
||||
|
||||
writeProp(
|
||||
cur: unknown,
|
||||
accessor: string | number,
|
||||
parent: object | undefined
|
||||
): boolean {
|
||||
const { BUFFER } = this;
|
||||
switch (typeof cur) {
|
||||
case "string":
|
||||
BUFFER.push(quote(cur, 0));
|
||||
break;
|
||||
|
||||
case "number":
|
||||
BUFFER.push(cur + "");
|
||||
break;
|
||||
|
||||
case "boolean":
|
||||
BUFFER.push(cur ? "!0" : "!1");
|
||||
break;
|
||||
|
||||
case "object":
|
||||
if (cur === null) {
|
||||
BUFFER.push("null");
|
||||
} else {
|
||||
const ref = this.getRef(cur, accessor, parent);
|
||||
|
||||
switch (ref) {
|
||||
case true:
|
||||
return false;
|
||||
case false:
|
||||
switch (cur.constructor) {
|
||||
case Object:
|
||||
this.writeObject(cur as Record<string, unknown>);
|
||||
break;
|
||||
|
||||
case Array:
|
||||
this.writeArray(cur as unknown[]);
|
||||
break;
|
||||
|
||||
case Date:
|
||||
BUFFER.push(
|
||||
'new Date("' + (cur as Date).toISOString() + '")'
|
||||
);
|
||||
break;
|
||||
|
||||
case RegExp:
|
||||
BUFFER.push(cur + "");
|
||||
break;
|
||||
|
||||
case Map:
|
||||
BUFFER.push("new Map(");
|
||||
this.writeArray(
|
||||
Array.from(cur as Map<unknown, unknown> | Set<unknown>)
|
||||
);
|
||||
BUFFER.push(")");
|
||||
break;
|
||||
|
||||
case Set:
|
||||
BUFFER.push("new Set(");
|
||||
this.writeArray(
|
||||
Array.from(cur as Map<unknown, unknown> | Set<unknown>)
|
||||
);
|
||||
BUFFER.push(")");
|
||||
break;
|
||||
|
||||
case undefined:
|
||||
BUFFER.push("Object.assign(Object.create(null),");
|
||||
this.writeObject(cur as Record<string, unknown>);
|
||||
BUFFER.push("))");
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BUFFER.push(ref);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "function": {
|
||||
return this.writeFunction(cur as SerializableFn);
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
writeFunction(fn: SerializableFn) {
|
||||
const {
|
||||
[SYMBOL_FN_ID]: fnId,
|
||||
[SYMBOL_SCOPE_ID]: scopeId,
|
||||
[SYMBOL_OFFSET]: offset,
|
||||
} = fn;
|
||||
if (fnId && scopeId && offset != null) {
|
||||
// ASSERT: fnId and scopeId don't need `quote` escaping
|
||||
this.BUFFER.push(`${PARAM_BIND}("${fnId}",${offset},"${scopeId}")`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
writeObject(obj: Record<string, unknown>) {
|
||||
const { STACK, BUFFER } = this;
|
||||
|
||||
let sep = "{";
|
||||
STACK.push(obj);
|
||||
|
||||
for (const key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) {
|
||||
const val = obj[key];
|
||||
const escapedKey = toObjectKey(key);
|
||||
BUFFER.push(sep + escapedKey + ":");
|
||||
if (this.writeProp(val, escapedKey, obj)) {
|
||||
sep = ",";
|
||||
} else {
|
||||
BUFFER.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sep === "{") {
|
||||
BUFFER.push("{}");
|
||||
} else {
|
||||
BUFFER.push("}");
|
||||
}
|
||||
|
||||
STACK.pop();
|
||||
}
|
||||
|
||||
writeArray(arr: unknown[]) {
|
||||
const { STACK, BUFFER } = this;
|
||||
|
||||
BUFFER.push("[");
|
||||
STACK.push(arr);
|
||||
|
||||
this.writeProp(arr[0], 0, arr);
|
||||
|
||||
for (let i = 1, len = arr.length; i < len; i++) {
|
||||
BUFFER.push(",");
|
||||
this.writeProp(arr[i], i, arr);
|
||||
}
|
||||
|
||||
STACK.pop();
|
||||
BUFFER.push("]");
|
||||
}
|
||||
|
||||
getRef(cur: object, accessor: string | number, parent: object | undefined) {
|
||||
const { STACK, BUFFER, INDEX_OR_REF, ASSIGNMENTS, PARENTS, KEYS } = this;
|
||||
|
||||
let ref = INDEX_OR_REF.get(cur);
|
||||
|
||||
if (ref === undefined) {
|
||||
INDEX_OR_REF.set(cur, BUFFER.length);
|
||||
|
||||
let knownParent = PARENTS.get(cur);
|
||||
|
||||
if (knownParent === undefined) {
|
||||
PARENTS.set(cur, parent!);
|
||||
KEYS.set(cur, accessor);
|
||||
return false;
|
||||
} else {
|
||||
let ref = "";
|
||||
while (knownParent) {
|
||||
ref = toPropertyAccess(KEYS.get(cur)!) + ref;
|
||||
knownParent = PARENTS.get((cur = knownParent));
|
||||
}
|
||||
return PARAM_SCOPE + ref;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof ref === "number") {
|
||||
ref = this.insertAndGetRef(cur, ref);
|
||||
}
|
||||
|
||||
if (STACK.includes(cur)) {
|
||||
const parent = STACK[STACK.length - 1];
|
||||
let parentRef = INDEX_OR_REF.get(parent) as string | number;
|
||||
|
||||
if (typeof parentRef === "number") {
|
||||
parentRef = this.insertAndGetRef(parent, parentRef);
|
||||
}
|
||||
|
||||
ASSIGNMENTS.set(
|
||||
ref,
|
||||
(ASSIGNMENTS.get(ref) || "") + toAssignment(parentRef, accessor) + "="
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
insertAndGetRef(obj: object, pos: number) {
|
||||
const ref = toRefParam(this.REF_COUNT++);
|
||||
this.INDEX_OR_REF.set(obj, ref);
|
||||
if (pos) {
|
||||
this.BUFFER[pos - 1] += ref + "=";
|
||||
} else {
|
||||
this.BUFFER[pos] = ref + "=" + this.BUFFER[pos];
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
refParamsString() {
|
||||
let result = REF_START_CHARS[0];
|
||||
|
||||
for (let i = 1; i < this.REF_COUNT; i++) {
|
||||
result += "," + toRefParam(i);
|
||||
}
|
||||
|
||||
this.REF_COUNT = 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function toObjectKey(name: string) {
|
||||
const invalidIdentifierPos = getInvalidIdentifierPos(name);
|
||||
return invalidIdentifierPos === -1 ? name : quote(name, invalidIdentifierPos);
|
||||
}
|
||||
|
||||
function toAssignment(parent: string, key: string | number) {
|
||||
return parent + toPropertyAccess(key);
|
||||
}
|
||||
|
||||
function toPropertyAccess(key: string | number) {
|
||||
return typeof key === "number" || key[0] === '"'
|
||||
? "[" + key + "]"
|
||||
: "." + key;
|
||||
}
|
||||
|
||||
function getInvalidIdentifierPos(name: string) {
|
||||
let char = name[0];
|
||||
if (
|
||||
!(
|
||||
(char >= "a" && char <= "z") ||
|
||||
(char >= "A" && char <= "Z") ||
|
||||
char === "$" ||
|
||||
char === "_"
|
||||
)
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (let i = 1, len = name.length; i < len; i++) {
|
||||
char = name[i];
|
||||
if (
|
||||
!(
|
||||
(char >= "a" && char <= "z") ||
|
||||
(char >= "A" && char <= "Z") ||
|
||||
(char >= "0" && char <= "9") ||
|
||||
char === "$" ||
|
||||
char === "_"
|
||||
)
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Creates a JavaScript double quoted string and escapes all characters not listed as DoubleStringCharacters on
|
||||
// Also includes "<" to escape "</script>" and "\" to avoid invalid escapes in the output.
|
||||
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
|
||||
function quote(str: string, startPos: number): string {
|
||||
let result = "";
|
||||
let lastPos = 0;
|
||||
|
||||
for (let i = startPos, len = str.length; i < len; i++) {
|
||||
let replacement: string;
|
||||
switch (str[i]) {
|
||||
case '"':
|
||||
replacement = '\\"';
|
||||
break;
|
||||
case "\\":
|
||||
replacement = "\\\\";
|
||||
break;
|
||||
case "<":
|
||||
replacement = "\\x3C";
|
||||
break;
|
||||
case "\n":
|
||||
replacement = "\\n";
|
||||
break;
|
||||
case "\r":
|
||||
replacement = "\\r";
|
||||
break;
|
||||
case "\u2028":
|
||||
replacement = "\\u2028";
|
||||
break;
|
||||
case "\u2029":
|
||||
replacement = "\\u2029";
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
result += str.slice(lastPos, i) + replacement;
|
||||
lastPos = i + 1;
|
||||
}
|
||||
|
||||
if (lastPos === startPos) {
|
||||
result = str;
|
||||
} else {
|
||||
result += str.slice(lastPos);
|
||||
}
|
||||
|
||||
return '"' + result + '"';
|
||||
}
|
||||
|
||||
function toRefParam(index: number) {
|
||||
let mod = index % REF_START_CHARS_LEN;
|
||||
let ref = REF_START_CHARS[mod];
|
||||
index = (index - mod) / REF_START_CHARS_LEN;
|
||||
|
||||
while (index > 0) {
|
||||
mod = index % REF_CHARS_LEN;
|
||||
ref += REF_CHARS[mod];
|
||||
index = (index - mod) / REF_CHARS_LEN;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
@ -2,6 +2,7 @@ import type { Writable } from "stream";
|
||||
import { Context, setContext } from "../common/context";
|
||||
import { Renderer, Scope, ScopeOffsets, HydrateSymbols } from "../common/types";
|
||||
import reorderRuntime from "./reorder-runtime";
|
||||
import { Serializer } from "./serializer";
|
||||
|
||||
const runtimeId = "M";
|
||||
const reorderRuntimeString = String(reorderRuntime).replace(
|
||||
@ -18,7 +19,7 @@ let $_promises: Array<Promise<unknown> & { isPlaceholder?: true }> | null =
|
||||
|
||||
const uids: WeakMap<MaybeFlushable, number> = new WeakMap();
|
||||
const runtimeFlushed: WeakSet<MaybeFlushable> = new WeakSet();
|
||||
const hydrateFlushed: WeakSet<MaybeFlushable> = new WeakSet();
|
||||
const streamSerializers: WeakMap<MaybeFlushable, Serializer> = new WeakMap();
|
||||
|
||||
export function nextId() {
|
||||
const id = uids.get($_stream!)! + 1 || 0;
|
||||
@ -286,11 +287,18 @@ export function markReplaceEnd(id: number) {
|
||||
|
||||
function flushToStream() {
|
||||
if ($_buffer!.calls || $_buffer!.scopes) {
|
||||
let isFirstFlush;
|
||||
let serializer = streamSerializers.get($_stream!);
|
||||
if ((isFirstFlush = !serializer)) {
|
||||
streamSerializers.set($_stream!, (serializer = new Serializer()));
|
||||
}
|
||||
$_buffer!.content += `<script>${
|
||||
hydrateFlushed.has($_stream!)
|
||||
? runtimeId + HydrateSymbols.VAR_HYDRATE
|
||||
: `(${runtimeId + HydrateSymbols.VAR_HYDRATE}=[])`
|
||||
}.push(${JSON.stringify($_buffer!.scopes)},[${$_buffer!.calls}])</script>`;
|
||||
isFirstFlush
|
||||
? `(${runtimeId + HydrateSymbols.VAR_HYDRATE}=[])`
|
||||
: runtimeId + HydrateSymbols.VAR_HYDRATE
|
||||
}.push(${serializer.stringify($_buffer!.scopes)},[${
|
||||
$_buffer!.calls
|
||||
}])</script>`;
|
||||
}
|
||||
$_stream!.write($_buffer!.content);
|
||||
if ($_stream!.flush) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user