marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
237 lines (202 loc) • 7.8 kB
JavaScript
var copyProps = require("raptor-util/copyProps");
var beginComponent = require("@internal/components-beginComponent");
var endComponent = require("@internal/components-endComponent");
var registry = require("@internal/components-registry");
var componentsUtil = require("@internal/components-util");
var componentLookup = componentsUtil.___componentLookup;
var ComponentsContext = require("./ComponentsContext");
var getComponentsContext = ComponentsContext.___getComponentsContext;
var isServer = componentsUtil.___isServer === true;
var COMPONENT_BEGIN_ASYNC_ADDED_KEY = "$wa";
function resolveComponentKey(key, parentComponentDef) {
if (key[0] === "#") {
return key.substring(1);
} else {
return parentComponentDef.id + "-" + parentComponentDef.___nextKey(key);
}
}
function trackAsyncComponents(out) {
if (out.isSync() || out.global[COMPONENT_BEGIN_ASYNC_ADDED_KEY]) {
return;
}
out.on("beginAsync", handleBeginAsync);
out.on("beginDetachedAsync", handleBeginDetachedAsync);
out.global[COMPONENT_BEGIN_ASYNC_ADDED_KEY] = true;
}
function handleBeginAsync(event) {
var parentOut = event.parentOut;
var asyncOut = event.out;
var componentsContext = parentOut.___components;
if (componentsContext !== undefined) {
// We are going to start a nested ComponentsContext
asyncOut.___components = new ComponentsContext(asyncOut, componentsContext);
}
// Carry along the component arguments
asyncOut.c(
parentOut.___assignedComponentDef,
parentOut.___assignedKey,
parentOut.___assignedCustomEvents,
);
}
function handleBeginDetachedAsync(event) {
var asyncOut = event.out;
handleBeginAsync(event);
asyncOut.on("beginAsync", handleBeginAsync);
asyncOut.on("beginDetachedAsync", handleBeginDetachedAsync);
}
function createRendererFunc(
templateRenderFunc,
componentProps,
renderingLogic,
) {
var onInput = renderingLogic && renderingLogic.onInput;
var typeName = componentProps.t;
var isSplit = componentProps.s === true;
var isImplicitComponent = componentProps.i === true;
var shouldApplySplitMixins = renderingLogic && isSplit;
// eslint-disable-next-line no-constant-condition
if ("MARKO_DEBUG") {
if (!componentProps.d) {
throw new Error(
"Component was compiled in a different NODE_ENV than the Marko runtime is using.",
);
}
} else if (componentProps.d) {
throw new Error("Runtime/NODE_ENV Mismatch");
}
return function renderer(input, out) {
trackAsyncComponents(out);
var componentsContext = getComponentsContext(out);
var globalComponentsContext = componentsContext.___globalContext;
var component = globalComponentsContext.___rerenderComponent;
var isRerender = component !== undefined;
var id;
var isExisting;
var customEvents;
var parentComponentDef = componentsContext.___componentDef;
var ownerComponentDef = out.___assignedComponentDef;
var ownerComponentId = ownerComponentDef && ownerComponentDef.id;
var key = out.___assignedKey;
if (component) {
// If component is provided then we are currently rendering
// the top-level UI component as part of a re-render
id = component.id; // We will use the ID of the component being re-rendered
isExisting = true; // This is a re-render so we know the component is already in the DOM
globalComponentsContext.___rerenderComponent = null;
} else {
// Otherwise, we are rendering a nested UI component. We will need
// to match up the UI component with the component already in the
// DOM (if any) so we will need to resolve the component ID from
// the assigned key. We also need to handle any custom event bindings
// that were provided.
if (parentComponentDef) {
// console.log('componentArgs:', componentArgs);
customEvents = out.___assignedCustomEvents;
if (key != null) {
id = resolveComponentKey(key.toString(), parentComponentDef);
} else {
id = parentComponentDef.___nextComponentId();
}
} else {
id = globalComponentsContext.___nextComponentId();
}
}
if (isServer) {
// If we are rendering on the server then things are simplier since
// we don't need to match up the UI component with a previously
// rendered component already mounted to the DOM. We also create
// a lightweight ServerComponent
component = registry.___createComponent(
renderingLogic,
id,
input,
out,
typeName,
customEvents,
ownerComponentId,
);
// This is the final input after running the lifecycle methods.
// We will be passing the input to the template for the `input` param
input = component.___updatedInput;
} else {
if (!component) {
if (
isRerender &&
(component = componentLookup[id]) &&
component.___type !== typeName
) {
// Destroy the existing component since
component.destroy();
component = undefined;
}
if (component) {
isExisting = true;
} else {
isExisting = false;
// We need to create a new instance of the component
component = registry.___createComponent(typeName, id);
if (shouldApplySplitMixins === true) {
shouldApplySplitMixins = false;
var renderingLogicProps =
typeof renderingLogic == "function"
? renderingLogic.prototype
: renderingLogic;
copyProps(renderingLogicProps, component.constructor.prototype);
}
}
// Set this flag to prevent the component from being queued for update
// based on the new input. The component is about to be rerendered
// so we don't want to queue it up as a result of calling `setInput()`
component.___updateQueued = true;
if (customEvents) {
component.___setCustomEvents(customEvents, ownerComponentId);
}
if (isExisting === false) {
component.___emitCreate(input, out);
}
input = component.___setInput(input, onInput, out);
if (isExisting === true) {
if (
component.___isDirty === false ||
component.shouldUpdate(input, component.___state) === false
) {
// We put a placeholder element in the output stream to ensure that the existing
// DOM node is matched up correctly when using morphdom. We flag the VElement
// node to track that it is a preserve marker
out.___preserveComponent(component);
globalComponentsContext.___renderedComponentsById[id] = true;
component.___reset(); // The component is no longer dirty so reset internal flags
return;
}
}
}
component.___global = out.global;
component.___emitRender(out);
}
var componentDef = beginComponent(
componentsContext,
component,
key,
ownerComponentDef,
isSplit,
isImplicitComponent,
);
componentDef.___isExisting = isExisting;
// Render the template associated with the component using the final template
// data that we constructed
templateRenderFunc(
input,
out,
componentDef,
component,
component.___rawState,
out.global,
);
endComponent(out, componentDef);
componentsContext.___componentDef = parentComponentDef;
};
}
module.exports = createRendererFunc;
// exports used by the legacy renderer
createRendererFunc.___resolveComponentKey = resolveComponentKey;
createRendererFunc.___trackAsyncComponents = trackAsyncComponents;