Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve serialization across multiple writes #1542

Merged
merged 2 commits into from
Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 5 additions & 10 deletions packages/marko/src/core-tags/components/body-transformer.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
"use strict";

module.exports = function transform(el, context) {
let componentGlobalsNode = context.createNodeForEl("component-globals");
el.prependChild(componentGlobalsNode);

let initComponentsNode = context.createNodeForEl("init-components");
el.appendChild(initComponentsNode);

// Make <await-reorderer> optional. Automatically insert it before the
// body tag.
let awaitReorderer = context.createNodeForEl("await-reorderer");
el.appendChild(awaitReorderer);
if (context.outputType === "html") {
el.appendChild(context.createNodeForEl("init-components"));
el.appendChild(context.createNodeForEl("await-reorderer"));
el.appendChild(context.createNodeForEl("_preferred-script-location"));
}
};

This file was deleted.

45 changes: 0 additions & 45 deletions packages/marko/src/core-tags/components/component-globals-tag.js

This file was deleted.

15 changes: 3 additions & 12 deletions packages/marko/src/core-tags/components/init-components-tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,10 @@ function addInitScript(writer) {
writer.script(getInitComponentsCode(out, componentDefs));
}

function forceScriptTagAtThisPoint(out) {
const writer = out.writer;
const htmlSoFar = writer.toString();
writer.clear();
writer.write(htmlSoFar);
}

module.exports = function render(input, out) {
const outGlobal = out.global;
if (outGlobal[INIT_COMPONENTS_KEY] === undefined) {
outGlobal[INIT_COMPONENTS_KEY] = true;
const $global = out.global;
if ($global[INIT_COMPONENTS_KEY] === undefined) {
$global[INIT_COMPONENTS_KEY] = true;

out.on("await:finish", addComponentsFromOut);
out.on("___toString", addInitScript);
Expand All @@ -40,7 +33,6 @@ module.exports = function render(input, out) {
// Generate initialization code for any of the UI components that were
// rendered synchronously
addComponentsFromOut(out);
forceScriptTagAtThisPoint(out);
} else {
// Generate initialization code for any of the UI components that were
// rendered asynchronously, but were outside an `<await>` tag
Expand All @@ -54,7 +46,6 @@ module.exports = function render(input, out) {
}
// Write out all of the component init code from the main out
addComponentsFromOut(rootOut, asyncOut);
forceScriptTagAtThisPoint(asyncOut);
asyncOut.end();
next();
});
Expand Down
9 changes: 4 additions & 5 deletions packages/marko/src/core-tags/components/marko.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,15 @@
"code-generator": "./component-tag.js",
"autocomplete": []
},
"<component-globals>": {
"renderer": "./component-globals-tag.js",
"no-output": true,
"autocomplete": []
},
"<init-components>": {
"renderer": "./init-components-tag.js",
"no-output": true,
"@immediate": "boolean"
},
"<_preferred-script-location>": {
"renderer": "./preferred-script-location-tag.js",
"no-output": true
},
"<_preserve>": {
"renderer": "./preserve-tag.js",
"@cid": "string",
Expand Down
1 change: 0 additions & 1 deletion packages/marko/src/core-tags/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"browser": {
"./component-globals-tag.js": "./component-globals-tag-browser.js",
"./getRequirePath.js": "./getRequirePath-browser.js",
"./init-components-tag.js": "./init-components-tag-browser.js",
"./preserve-tag.js": "./preserve-tag-browser.js"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use strict";

function forceScriptTagAtThisPoint(out) {
const writer = out.writer;

out.global.___isLastFlush = true;
const htmlSoFar = writer.toString();
out.global.___isLastFlush = undefined;

writer.clear();
writer.write(htmlSoFar);
}

module.exports = function render(input, out) {
if (out.isSync() === true) {
forceScriptTagAtThisPoint(out);
} else {
const asyncOut = out.beginAsync({ last: true, timeout: -1 });
out.onLast(function(next) {
forceScriptTagAtThisPoint(asyncOut);
asyncOut.end();
next();
});
}
};
8 changes: 5 additions & 3 deletions packages/marko/src/core-tags/core/await/reorderer-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ module.exports = function(input, out) {

var global = out.global;

out.flush();

// We have already invoked an <await-reorderer. We do not need to do this
// We have already invoked an <await-reorderer>. We do not need to do this
// work again.
if (global.__awaitReordererInvoked) {
return;
}

global.__awaitReordererInvoked = true;

if (out.global.___clientReorderContext) {
out.flush();
}

var asyncOut = out.beginAsync({
last: true,
timeout: -1,
Expand Down
7 changes: 7 additions & 0 deletions packages/marko/src/core-tags/migrate/component-globals-tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = function render(elNode, context) {
context.deprecate(
"The <component-globals> tag is deprecated. This functionality has been added to the '<init-components>' tag (which is automatically inserted at the end of the <body> tag), you can safely remove the '<component-globals>' tag."
);

elNode.detach();
};
4 changes: 4 additions & 0 deletions packages/marko/src/core-tags/migrate/marko.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,5 +221,9 @@
"<class>": {
"migrator": "./class-tag",
"open-tag-only": true
},
"<component-globals>": {
"migrator": "./component-globals-tag.js",
"deprecated": true
}
}
133 changes: 95 additions & 38 deletions packages/marko/src/runtime/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

var warp10 = require("warp10");
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;
Expand All @@ -26,9 +28,33 @@ function isNotEmpty(obj) {

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;
}
}
}

function safeJSON(json) {
return json.replace(safeJSONRegExp, safeJSONReplacer);
return serializedGlobals;
}
}

function addComponentsFromContext(componentsContext, componentsToHydrate) {
Expand Down Expand Up @@ -139,41 +165,82 @@ function addComponentsFromContext(componentsContext, componentsToHydrate) {
}
}

function getInitComponentsData(componentDefs, runtimeId) {
let len;
if ((len = componentDefs.length) === 0) {
function getInitComponentsData(out, componentDefs) {
const len = componentDefs.length;
const $global = out.global;
const isLast = $global.___isLastFlush;
const didSerializeComponents = $global.___didSerializeComponents;
const prefix = $global.componentIdPrefix || $global.widgetIdPrefix;

if (len === 0) {
if (isLast && didSerializeComponents) {
return { p: prefix, l: 1 };
}

return;
}

const typesLookup = {};
const componentTypes = [];
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[typeName];
let typeIndex = typesLookup.get(typeName);

if (typeIndex === undefined) {
typeIndex = componentTypes.length;
componentTypes.push(typeName);
typesLookup[typeName] = typeIndex;
typeIndex = typesLookup.size;
typesLookup.set(typeName, typeIndex);

if (newTypes) {
newTypes.push(typeName);
} else {
newTypes = [typeName];
}
}

componentDef[TYPE_INDEX] = typeIndex;
}
return { r: runtimeId, w: componentDefs, t: componentTypes };

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) {
var componentsContext = out.___components;
const componentsContext = out.___components;

if (componentsContext === null) {
return;
}

var componentsToHydrate = [];

const $global = out.global;
const runtimeId = $global.runtimeId;
const componentsToHydrate = [];
addComponentsFromContext(componentsContext, componentsToHydrate);

return getInitComponentsData(componentsToHydrate, out.global.runtimeId);
$global.___isLastFlush = true;
const data = getInitComponentsData(out, componentsToHydrate);
$global.___isLastFlush = undefined;

if (runtimeId !== DEFAULT_RUNTIME_ID) {
data.r = runtimeId;
}

return data;
}

function writeInitComponentsCode(out) {
Expand All @@ -184,31 +251,22 @@ exports.___getInitComponentsCode = function getInitComponentsCode(
out,
componentDefs
) {
var runtimeId = out.global.runtimeId;
var initComponentsData;

if (arguments.length === 2) {
initComponentsData = getInitComponentsData(componentDefs, runtimeId);
} else {
initComponentsData = getInitComponentsDataFromOut(out);
}
const initComponentsData =
arguments.length === 2
? getInitComponentsData(out, componentDefs)
: getInitComponentsDataFromOut(out);

if (initComponentsData === undefined) {
return "";
}

var componentGlobalKey =
"$" + (runtimeId === "M" ? "components" : runtimeId + "_components");

return (
componentGlobalKey +
"=(window." +
componentGlobalKey +
"||[]).concat(" +
safeJSON(warp10.stringify(initComponentsData)) +
")||" +
componentGlobalKey
);
const runtimeId = out.global.runtimeId;
const componentGlobalKey =
runtimeId === DEFAULT_RUNTIME_ID ? "MC" : runtimeId + "_C";

return `$${componentGlobalKey}=(window.$${componentGlobalKey}||[]).concat(${safeStringify(
initComponentsData
)})`;
};

exports.___addComponentsFromContext = addComponentsFromContext;
Expand All @@ -222,6 +280,5 @@ exports.writeInitComponentsCode = writeInitComponentsCode;
* @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) {
var initComponentsData = getInitComponentsDataFromOut(out);
return warp10.stringifyPrepare(initComponentsData);
return warp10.stringifyPrepare(getInitComponentsDataFromOut(out));
};