marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
587 lines (447 loc) • 14.1 kB
JavaScript
; // eslint-disable-next-line no-constant-binary-expression
var setImmediate = require("@internal/set-immediate")._l_;
var warp10Finalize = require("warp10/finalize");
var defineComponent = require("../../../runtime/components/defineComponent");
var eventDelegation = require("../../../runtime/components/event-delegation");
var createFragmentNode =
require("../../../runtime/vdom/morphdom/fragment")._m_;
var ComponentDef = require("../../../runtime/components/ComponentDef");
var domData = require("../../../runtime/components/dom-data");
var componentsUtil = require("@internal/components-util");
var req = require("@internal/require");
var componentLookup = componentsUtil._n_;
var addComponentRootToKeyedElements =
componentsUtil._o_;
var keysByDOMNode = domData._p_;
var keyedElementsByComponentId = domData._q_;
var componentsByDOMNode = domData._r_;
var serverComponentRootNodes = {};
var serverRenderedMeta = {};
var win = window;
win.Marko = {
Component: function () {}
};
var DEFAULT_RUNTIME_ID = "M";
var FLAG_WILL_RERENDER_IN_BROWSER = 1;
// var FLAG_HAS_RENDER_BODY = 2;
var registered = {};
var loaded = {};
var componentTypes = {};
var deferredDefs;
var pendingDefs;
function register(type, def) {
var pendingForType;
if (pendingDefs) {
pendingForType = pendingDefs[type];
}
registered[type] = def;
delete loaded[type];
delete componentTypes[type];
if (pendingForType) {
delete pendingDefs[type];
setImmediate(function () {
pendingForType.forEach(function (args) {
tryHydrateComponent(args[0], args[1], args[2], args[3])();
});
});
}
return type;
}
function addPendingDef(def, type, meta, host, runtimeId) {
if (!pendingDefs) {
pendingDefs = {};
}
(pendingDefs[type] = pendingDefs[type] || []).push([
def,
meta,
host,
runtimeId]
);
}
function load(typeName, isLegacy) {
var target = loaded[typeName];
if (!target) {
target = registered[typeName];
if (target) {
target = target();
} else if (isLegacy) {
target = exports._i_.load(typeName);
} else {
target = req(typeName);
// eslint-disable-next-line no-constant-condition
}
if (!target) {
throw Error("Component not found: " + typeName);
}
loaded[typeName] = target;
}
return target;
}
function getComponentClass(typeName, isLegacy) {
var ComponentClass = componentTypes[typeName];
if (ComponentClass) {
return ComponentClass;
}
ComponentClass = load(typeName, isLegacy);
ComponentClass = ComponentClass.Component || ComponentClass;
if (!ComponentClass.y_) {
ComponentClass = defineComponent(ComponentClass, ComponentClass.renderer);
}
// Make the component "type" accessible on each component instance
ComponentClass.prototype._s_ = typeName;
// eslint-disable-next-line no-constant-condition
componentTypes[typeName] = ComponentClass;
return ComponentClass;
}
function createComponent(typeName, id, isLegacy) {
var ComponentClass = getComponentClass(typeName, isLegacy);
return new ComponentClass(id);
}
function indexServerComponentBoundaries(node, runtimeId, stack) {
var componentId;
var ownerId;
var ownerComponent;
var keyedElements;
var nextSibling;
var runtimeLength = runtimeId.length;
stack = stack || [];
node = node.firstChild;
while (node) {
nextSibling = node.nextSibling;
if (node.nodeType === 8) {
// Comment node
var commentValue = node.nodeValue;
if (commentValue.slice(0, runtimeLength) === runtimeId) {
var firstChar = commentValue[runtimeLength];
if (firstChar === "^" || firstChar === "#") {
stack.push(node);
} else if (firstChar === "/") {
var endNode = node;
var startNode = stack.pop();
var rootNode;
if (startNode.parentNode === endNode.parentNode) {
rootNode = createFragmentNode(startNode.nextSibling, endNode);
} else {
rootNode = createFragmentNode(
endNode.parentNode.firstChild,
endNode
);
}
componentId = startNode.nodeValue.substring(runtimeLength + 1);
firstChar = startNode.nodeValue[runtimeLength];
if (firstChar === "^") {
var parts = componentId.split(/ /g);
var key = parts[2];
ownerId = parts[1];
componentId = parts[0];
if (ownerComponent = componentLookup[ownerId]) {
keyedElements = ownerComponent.L_;
} else {
keyedElements =
keyedElementsByComponentId[ownerId] || (
keyedElementsByComponentId[ownerId] = {});
}
addComponentRootToKeyedElements(
keyedElements,
key,
rootNode,
componentId
);
}
serverComponentRootNodes[componentId] = rootNode;
startNode.parentNode.removeChild(startNode);
endNode.parentNode.removeChild(endNode);
}
}
} else if (node.nodeType === 1) {
// HTML element node
var markoKey = node.getAttribute("data-marko-key");
var markoProps = componentsUtil._t_(node);
if (markoKey) {
var separatorIndex = markoKey.indexOf(" ");
ownerId = markoKey.substring(separatorIndex + 1);
markoKey = markoKey.substring(0, separatorIndex);
if (ownerComponent = componentLookup[ownerId]) {
keyedElements = ownerComponent.L_;
} else {
keyedElements =
keyedElementsByComponentId[ownerId] || (
keyedElementsByComponentId[ownerId] = {});
}
keysByDOMNode.set(node, markoKey);
keyedElements[markoKey] = node;
}
if (markoProps) {
Object.keys(markoProps).forEach(function (key) {
if (key.slice(0, 2) === "on") {
eventDelegation._u_(key.slice(2));
}
});
}
indexServerComponentBoundaries(node, runtimeId, stack);
}
node = nextSibling;
}
}
function invokeComponentEventHandler(component, targetMethodName, args) {
var method = component[targetMethodName];
if (!method) {
throw Error("Method not found: " + targetMethodName);
}
method.apply(component, args);
}
function addEventListenerHelper(el, eventType, isOnce, listener) {
var eventListener = listener;
if (isOnce) {
eventListener = function (event) {
listener(event);
el.removeEventListener(eventType, eventListener);
};
}
el.addEventListener(eventType, eventListener, false);
return function remove() {
el.removeEventListener(eventType, eventListener);
};
}
function addDOMEventListeners(
component,
el,
eventType,
targetMethodName,
isOnce,
extraArgs,
handles)
{
var removeListener = addEventListenerHelper(
el,
eventType,
isOnce,
function (event) {
var args = [event, el];
if (extraArgs) {
args = extraArgs.concat(args);
}
invokeComponentEventHandler(component, targetMethodName, args);
}
);
handles.push(removeListener);
}
function initComponent(componentDef, host) {
var component = componentDef.s_;
if (!component || !component.y_) {
return; // legacy
}
component._v_();
component.C_ = host;
var isExisting = componentDef._w_;
if (isExisting) {
component._x_();
}
var domEvents = componentDef._d_;
if (domEvents) {
var eventListenerHandles = [];
domEvents.forEach(function (domEventArgs) {
// The event mapping is for a direct DOM event (not a custom event and not for bubblign dom events)
var eventType = domEventArgs[0];
var targetMethodName = domEventArgs[1];
var eventEl = component.L_[domEventArgs[2]];
var isOnce = domEventArgs[3];
var extraArgs = domEventArgs[4];
addDOMEventListeners(
component,
eventEl,
eventType,
targetMethodName,
isOnce,
extraArgs,
eventListenerHandles
);
});
if (eventListenerHandles.length) {
component._y_ = eventListenerHandles;
}
}
if (component._z_) {
component._A_();
} else {
component._z_ = true;
component._B_();
}
}
/**
* This method is used to initialized components associated with UI components
* rendered in the browser. While rendering UI components a "components context"
* is added to the rendering context to keep up with which components are rendered.
* When ready, the components can then be initialized by walking the component tree
* in the components context (nested components are initialized before ancestor components).
* @param {Array<marko-components/lib/ComponentDef>} componentDefs An array of ComponentDef instances
*/
function initClientRendered(componentDefs, host) {
if (!host) host = document;
// Ensure that event handlers to handle delegating events are
// always attached before initializing any components
eventDelegation._C_(host);
var len = componentDefs.length;
var componentDef;
var i;
for (i = len; i--;) {
componentDef = componentDefs[i];
trackComponent(componentDef);
}
for (i = len; i--;) {
componentDef = componentDefs[i];
initComponent(componentDef, host);
}
}
/**
* This method initializes all components that were rendered on the server by iterating over all
* of the component IDs.
*/
function initServerRendered(renderedComponents, host) {
var type = typeof renderedComponents;
var globalKey = "$";
var runtimeId;
if (type !== "object") {
if (type === "string") {
runtimeId = renderedComponents;
globalKey += runtimeId + "_C";
} else {
globalKey += (runtimeId = DEFAULT_RUNTIME_ID) + "C";
}
renderedComponents = win[globalKey];
// eslint-disable-next-line no-constant-condition
var fakeArray = win[globalKey] = {
r: runtimeId,
concat: initServerRendered
};
// eslint-disable-next-line no-constant-condition
if (renderedComponents && renderedComponents.forEach) {
renderedComponents.forEach(function (renderedComponent) {
fakeArray.concat(renderedComponent);
});
}
return fakeArray;
}
var isFromSerializedGlobals = this.concat === initServerRendered;
renderedComponents = warp10Finalize(renderedComponents);
if (isFromSerializedGlobals) {
runtimeId = this.r;
host = document;
} else {
runtimeId = renderedComponents.r || DEFAULT_RUNTIME_ID;
if (!host) host = document;
// eslint-disable-next-line no-constant-condition
}
// eslint-disable-next-line no-constant-condition
var prefix = renderedComponents.p || "s";
var meta = serverRenderedMeta[prefix];
var isLast = renderedComponents.l;
if (meta) {
if (isLast) {
delete serverRenderedMeta[prefix];
}
} else {
meta = {};
if (!isLast) {
serverRenderedMeta[prefix] = meta;
}
}
// Ensure that event handlers to handle delegating events are
// always attached before initializing any components
indexServerComponentBoundaries(host, runtimeId);
eventDelegation._C_(host);
if (!meta._D_) {
meta._D_ = Object.assign({
renderId: prefix,
runtimeId: runtimeId,
componentIdPrefix: prefix
}, renderedComponents.g);
}
if (renderedComponents.t) {
meta._E_ = meta._E_ ?
meta._E_.concat(renderedComponents.t) :
renderedComponents.t;
}
// hydrate components top down (leaf nodes last)
// and return an array of functions to mount these components
(renderedComponents.w || []).
map(function (componentDef) {
var typeName = meta._E_[componentDef[1]];
return registered[typeName] ||
req.e(typeName) ?
tryHydrateComponent(componentDef, meta, host, runtimeId) :
addPendingDef(componentDef, typeName, meta, host, runtimeId);
}).
reverse().
forEach(tryInvoke);
return this;
}
function tryHydrateComponent(rawDef, meta, host, runtimeId) {
var componentDef = ComponentDef._F_(
rawDef,
meta._E_,
meta._D_,
exports
);
var mount = hydrateComponentAndGetMount(componentDef, host);
if (!mount) {
// hydrateComponentAndGetMount will return false if there is not rootNode
// for the component. If this is the case, we'll wait until the
// DOM has fully loaded to attempt to init the component again.
if (deferredDefs) {
deferredDefs.push(componentDef);
} else {
deferredDefs = [componentDef];
document.addEventListener("DOMContentLoaded", function () {
indexServerComponentBoundaries(host, runtimeId);
deferredDefs.
map(function (componentDef) {
return hydrateComponentAndGetMount(componentDef, host);
}).
reverse().
forEach(tryInvoke);
deferredDefs.length = 0;
});
}
}
return mount;
}
function hydrateComponentAndGetMount(componentDef, host) {
var componentId = componentDef.id;
var component = componentDef.s_;
var rootNode = serverComponentRootNodes[componentId];
var renderResult;
if (rootNode) {
delete serverComponentRootNodes[componentId];
component._G_ = rootNode;
componentsByDOMNode.set(rootNode, component);
if (componentDef.u_ & FLAG_WILL_RERENDER_IN_BROWSER) {
component.C_ = host;
renderResult = component._H_(component.P_, true);
trackComponent(componentDef);
return function mount() {
renderResult.afterInsert(host);
};
} else {
trackComponent(componentDef);
}
return function mount() {
initComponent(componentDef, host);
};
}
}
function trackComponent(componentDef) {
var component = componentDef.s_;
if (component) {
componentLookup[component.id] = component;
}
}
function tryInvoke(fn) {
if (fn) fn();
}
exports.r = register;
exports._I_ = createComponent;
exports._J_ = getComponentClass;
exports.V_ = win.$initComponents = initServerRendered;
require("../../../runtime/components/ComponentsContext")._K_ =
initClientRendered;