UNPKG

marko

Version:

UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.

587 lines (447 loc) • 14.1 kB
"use strict"; // 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;