marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
310 lines (257 loc) • 8.71 kB
JavaScript
;
require("../../../runtime/html/marko-namespace");
var warp10 = require("warp10");
var w10ToJSON = require("../../../runtime/helpers/serialize-noop").___toJSON;
var getRenderId = require("../../../runtime/html/get-render-id");
var safeJSONRegExp = /<\/|\u2028|\u2029/g;
var IGNORE_GLOBAL_TYPES = new Set(["undefined", "function", "symbol"]);
var DEFAULT_RUNTIME_ID = "M";
var FLAG_WILL_RERENDER_IN_BROWSER = 1;
var FLAG_HAS_RENDER_BODY = 2;
var FLAG_IS_LEGACY = 4;
var FLAG_OLD_HYDRATE_NO_CREATE = 8;
function safeJSONReplacer(match) {
if (match === "</") {
return "\\u003C/";
} else {
return "\\u" + match.charCodeAt(0).toString(16);
}
}
function isNotEmpty(obj) {
var keys = Object.keys(obj);
for (var i = keys.length; i--; ) {
if (obj[keys[i]] !== undefined) {
return true;
}
}
return false;
}
function safeStringify(data) {
return JSON.stringify(warp10.stringifyPrepare(data)).replace(
safeJSONRegExp,
safeJSONReplacer,
);
}
function getSerializedGlobals($global) {
let serializedGlobalsLookup = $global.serializedGlobals;
if (serializedGlobalsLookup) {
let serializedGlobals;
let keys = Object.keys(serializedGlobalsLookup);
for (let i = keys.length; i--; ) {
let key = keys[i];
if (serializedGlobalsLookup[key]) {
let value = $global[key];
if (!IGNORE_GLOBAL_TYPES.has(typeof value)) {
if (serializedGlobals === undefined) {
serializedGlobals = {};
}
serializedGlobals[key] = value;
}
}
}
return serializedGlobals;
}
}
function addComponentsFromContext(componentsContext, componentsToHydrate) {
var components = componentsContext.___components;
var len = components.length;
for (var i = 0; i < len; i++) {
var componentDef = components[i];
var id = componentDef.id;
var component = componentDef.___component;
var flags = componentDef.___flags;
var isLegacy = componentDef.___isLegacy;
var state = component.state;
var input = component.input || 0;
var typeName = component.typeName;
var customEvents = component.___customEvents;
var scope = component.___scope || undefined;
var bubblingDomEvents = component.___bubblingDomEvents;
var needsState;
var serializedProps;
var renderBody;
if (isLegacy) {
flags |= FLAG_IS_LEGACY;
renderBody = component.___widgetBody;
if (component.widgetConfig && isNotEmpty(component.widgetConfig)) {
serializedProps = component.widgetConfig;
}
needsState = true;
} else {
if (
!(flags & FLAG_WILL_RERENDER_IN_BROWSER) ||
flags & FLAG_OLD_HYDRATE_NO_CREATE
) {
component.___state = undefined; // We don't use `delete` to avoid V8 deoptimization
component.___input = undefined; // We don't use `delete` to avoid V8 deoptimization
component.typeName = undefined;
component.id = undefined;
component.___customEvents = undefined;
component.___scope = undefined;
component.___bubblingDomEvents = undefined;
component.___bubblingDomEventsExtraArgsCount = undefined;
component.___updatedInput = undefined;
component.___updateQueued = undefined;
needsState = true;
if (isNotEmpty(component)) {
serializedProps = component;
}
} else {
renderBody = input.renderBody;
}
}
var undefinedPropNames = undefined;
if (needsState && state) {
// Update state properties with an `undefined` value to have a `null`
// value so that the property name will be serialized down to the browser.
// This ensures that we add the proper getter/setter for the state property.
const stateKeys = Object.keys(state);
for (let i = stateKeys.length; i--; ) {
const stateKey = stateKeys[i];
if (state[stateKey] === undefined) {
if (undefinedPropNames) {
undefinedPropNames.push(stateKey);
} else {
undefinedPropNames = [stateKey];
}
}
}
}
if (typeof renderBody === "function" && renderBody.toJSON === w10ToJSON) {
flags |= FLAG_HAS_RENDER_BODY;
renderBody = undefined;
if (input) input.renderBody = undefined;
}
var extra = {
b: bubblingDomEvents,
d: componentDef.___domEvents,
e: customEvents,
f: flags || undefined,
p: customEvents && scope, // Only serialize scope if we need to attach custom events
s: needsState && state,
u: undefinedPropNames,
w: serializedProps,
r: renderBody,
};
var parts = [id, typeName];
var hasExtra = isNotEmpty(extra);
if (input) {
parts.push(input);
if (hasExtra) {
parts.push(extra);
}
} else if (hasExtra) {
parts.push(0, extra); // empty input;
}
componentsToHydrate.push(parts);
}
components.length = 0;
// Also add any components from nested contexts
var nestedContexts = componentsContext.___nestedContexts;
if (nestedContexts !== undefined) {
nestedContexts.forEach(function (nestedContext) {
addComponentsFromContext(nestedContext, componentsToHydrate);
});
}
}
function getInitComponentsData($global, componentDefs) {
const len = componentDefs.length;
const isLast = $global.___isLastFlush;
const didSerializeComponents = $global.___didSerializeComponents;
const prefix = getRenderId($global);
if (len === 0) {
if (isLast && didSerializeComponents) {
return { p: prefix, l: 1 };
}
return;
}
const TYPE_INDEX = 1;
const typesLookup =
$global.___typesLookup || ($global.___typesLookup = new Map());
let newTypes;
for (let i = 0; i < len; i++) {
const componentDef = componentDefs[i];
const typeName = componentDef[TYPE_INDEX];
let typeIndex = typesLookup.get(typeName);
if (typeIndex === undefined) {
typeIndex = typesLookup.size;
typesLookup.set(typeName, typeIndex);
if (newTypes) {
newTypes.push(typeName);
} else {
newTypes = [typeName];
}
}
componentDef[TYPE_INDEX] = typeIndex;
}
let serializedGlobals;
if (!didSerializeComponents) {
$global.___didSerializeComponents = true;
serializedGlobals = getSerializedGlobals($global);
}
return {
p: prefix,
l: isLast && 1,
g: serializedGlobals,
w: componentDefs,
t: newTypes,
};
}
function getInitComponentsDataFromOut(out) {
const componentsContext = out.___components;
if (componentsContext === null) {
return;
}
const $global = out.global;
const runtimeId = $global.runtimeId;
const componentsToHydrate = [];
addComponentsFromContext(componentsContext, componentsToHydrate);
$global.___isLastFlush = true;
const data = getInitComponentsData($global, componentsToHydrate);
$global.___isLastFlush = undefined;
if (runtimeId !== DEFAULT_RUNTIME_ID && data) {
data.r = runtimeId;
}
return data;
}
function writeInitComponentsCode(out) {
out.script(exports.___getInitComponentsCode(out));
}
exports.___getInitComponentsCode = function getInitComponentsCode(
out,
componentDefs,
) {
const $global = out.global;
return getInitComponentsCodeFromData(
$global,
arguments.length === 2
? getInitComponentsData($global, componentDefs)
: getInitComponentsDataFromOut(out),
);
};
function getInitComponentsCodeFromData($global, initComponentsData) {
if (initComponentsData === undefined) {
return "";
}
const runtimeId = $global.runtimeId;
const componentGlobalKey =
runtimeId === DEFAULT_RUNTIME_ID ? "MC" : runtimeId + "_C";
return `$${componentGlobalKey}=(window.$${componentGlobalKey}||[]).concat(${safeStringify(
initComponentsData,
)})`;
}
exports.___getInitComponentsCodeForDefs = function getInitComponentsCodeForDefs($global, defs) {
return getInitComponentsCodeFromData($global, getInitComponentsData($global, defs));
}
exports.___addComponentsFromContext = addComponentsFromContext;
exports.writeInitComponentsCode = writeInitComponentsCode;
/**
* Returns an object that can be sent to the browser using JSON.stringify. The parsed object should be
* passed to require('marko-components').initComponents(...);
*
* @param {ComponentsContext|AsyncWriter} componentsContext A ComponentsContext or an AsyncWriter
* @return {Object} An object with information about the rendered components that can be serialized to JSON. The object should be treated as opaque
*/
exports.getRenderedComponents = function (out) {
return warp10.stringifyPrepare(getInitComponentsDataFromOut(out));
};