@dark-engine/platform-server
Version:
Dark renderer for server
313 lines (312 loc) • 10 kB
JavaScript
import {
REF_ATTR,
ATTR_BLACK_LIST,
CREATE_EFFECT_TAG,
UPDATE_EFFECT_TAG,
DELETE_EFFECT_TAG,
MOVE_MASK,
FLUSH_MASK,
detectIsUndefined,
detectIsBoolean,
detectIsObject,
NodeType,
detectIsTagVirtualNode,
detectIsTextVirtualNode,
detectIsPlainVirtualNode,
getFiberWithElement,
collectElements,
walk,
applyRef,
detectIsHydration,
} from '@dark-engine/core';
import {
detectIsSvgElement,
detectIsVoidElement,
illegal,
removeContent,
setInnerHTML,
detectIsBrowser,
} from '../utils';
import { delegateEvent, detectIsEvent, getEventName } from '../events';
import {
INPUT_TAG,
TEXTAREA_TAG,
STYLE_ATTR,
CLASS_ATTR,
CLASS_NAME_ATTR,
VALUE_ATTR,
AS_ATTR,
EXCLUDE_ATTR_MARK,
DANGER_HTML_ATTR,
DASH_MARK,
PREVENT,
} from '../constants';
let moves = [];
let patches = [];
let trackUpdate = null;
function createNativeElement(vNode) {
switch (vNode.type) {
case NodeType.TAG:
const name = vNode.name;
const tag = detectIsSvgElement(name)
? document.createElementNS('http://www.w3.org/2000/svg', name)
: document.createElement(name);
return tag;
case NodeType.TEXT:
return document.createTextNode(vNode.value);
case NodeType.COMMENT:
return document.createComment(vNode.value);
}
}
function setObjectStyle(element, style) {
for (const key in style) {
m.setStyleProp.call(element.style, key, String(style[key]));
}
}
function addAttributes(element, vNode, isHydration) {
const tagElement = element;
for (let attrName in vNode.attrs) {
const attrValue = vNode.attrs[attrName];
const attribute = performAttribute(tagElement, attrName, attrValue);
if (attribute === null) {
continue;
} else {
attrName = attribute;
}
if (detectIsEvent(attrName)) {
delegateEvent(tagElement, getEventName(attrName), attrValue);
} else if (!isHydration && !detectIsUndefined(attrValue) && !ATTR_BLACK_LIST[attrName]) {
!patchAttributes(tagElement, vNode.name, attrName, attrValue) &&
m.setAttribute.call(tagElement, attrName, attrValue);
}
}
}
function updateAttributes(element, prevVNode, nextVNode) {
const attrNames = getAttributeNames(prevVNode, nextVNode);
const tagElement = element;
const isInput = nextVNode.name === INPUT_TAG;
for (let attrName of attrNames) {
const prevAttrValue = prevVNode.attrs[attrName];
const nextAttrValue = nextVNode.attrs[attrName];
const attribute = performAttribute(tagElement, attrName, nextAttrValue, prevAttrValue);
if (attribute === null) {
continue;
} else {
attrName = attribute;
}
if (!detectIsUndefined(nextAttrValue)) {
if (detectIsEvent(attrName)) {
prevAttrValue !== nextAttrValue && delegateEvent(tagElement, getEventName(attrName), nextAttrValue);
} else if (!ATTR_BLACK_LIST[attrName] && (isInput || prevAttrValue !== nextAttrValue)) {
!patchAttributes(tagElement, nextVNode.name, attrName, nextAttrValue) &&
m.setAttribute.call(tagElement, attrName, nextAttrValue);
}
} else {
m.removeAttribute.call(tagElement, attrName);
}
}
}
function performAttribute(tagElement, attrName, nextAttrValue, prevAttrValue) {
if (attrName[0] === EXCLUDE_ATTR_MARK) return null;
if (attrName === DANGER_HTML_ATTR) {
setInnerHTML(tagElement, String(nextAttrValue));
return null;
}
if (attrName === REF_ATTR) {
applyRef(nextAttrValue, tagElement);
return null;
}
if ((attrName === CLASS_ATTR || attrName === CLASS_NAME_ATTR) && nextAttrValue !== prevAttrValue) {
if (nextAttrValue) {
m.setAttribute.call(tagElement, CLASS_ATTR, nextAttrValue);
} else {
m.removeAttribute.call(tagElement, CLASS_ATTR);
}
return null;
}
if (attrName === STYLE_ATTR && nextAttrValue && nextAttrValue !== prevAttrValue && detectIsObject(nextAttrValue)) {
setObjectStyle(tagElement, nextAttrValue);
return null;
}
if (attrName === PREVENT) {
tagElement[PREVENT] = true;
return null;
}
if (attrName === AS_ATTR) {
attrName = attrName.slice(1, AS_ATTR.length);
}
return attrName;
}
function getAttributeNames(prevVNode, nextVNode) {
const attrNames = new Set();
const prevAttrs = Object.keys(prevVNode.attrs);
const nextAttrs = Object.keys(nextVNode.attrs);
const size = Math.max(prevAttrs.length, nextAttrs.length);
for (let i = 0; i < size; i++) {
attrNames.add(prevAttrs[i] || nextAttrs[i]);
}
return attrNames;
}
const ATTR_TRANSFORM_MAP = {
readonly: 'readOnly',
};
function patchAttributes(element, tagName, attrName, attrValue) {
const fn = specialCasesMap[tagName];
const $attrName = ATTR_TRANSFORM_MAP[attrName] || attrName;
let stop = fn ? fn(element, attrName, attrValue) : false;
if (canSetProperty(element, $attrName)) {
element[$attrName] = attrValue;
}
if (!stop && detectIsBoolean(attrValue)) {
stop = !$attrName.includes(DASH_MARK);
}
return stop;
}
function canSetProperty(element, key) {
const prototype = Object.getPrototypeOf(element);
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
return Boolean(descriptor?.set);
}
const specialCasesMap = {
[INPUT_TAG]: (element, attrName, attrValue) => {
if (attrName === VALUE_ATTR) {
patches.push(() => {
detectIsBoolean(attrValue) ? (element.checked = attrValue) : (element.value = String(attrValue));
});
}
return false;
},
[TEXTAREA_TAG]: (element, attrName, attrValue) => {
if (attrName === VALUE_ATTR) {
element.innerText = String(attrValue);
return true;
}
return false;
},
};
function commitCreation(fiber) {
const parent = getFiberWithElement(fiber.parent);
const parentElement = parent.el;
const childNodes = parentElement.childNodes;
const isHydration = detectIsHydration();
if (isHydration) {
let nativeElement = childNodes[fiber.eidx];
if (nativeElement instanceof DocumentType) {
nativeElement = nativeElement.nextSibling;
}
if (
detectIsTextVirtualNode(fiber.inst) &&
nativeElement instanceof Text &&
fiber.inst.value.length !== nativeElement.length
) {
nativeElement.splitText(fiber.inst.value.length);
}
if (fiber.el.nodeName !== nativeElement.nodeName) {
illegal('Inconsistent element for hydration!');
}
fiber.el = nativeElement;
} else {
if (detectIsTagVirtualNode(parent.inst) && parent.inst.attrs[DANGER_HTML_ATTR]) {
illegal(`The element with danger content can't have a children!`);
}
if (childNodes.length === 0 || fiber.eidx > childNodes.length - 1) {
!detectIsVoidElement(parent.inst.name) && m.appendElement.call(parentElement, fiber.el);
} else {
m.insertElement.call(parentElement, fiber.el, parentElement.childNodes[fiber.eidx]);
}
}
detectIsTagVirtualNode(fiber.inst) && addAttributes(fiber.el, fiber.inst, isHydration);
fiber.el.parentElement?.[PREVENT] && (fiber.el[PREVENT] = true);
}
function commitUpdate(fiber) {
const element = fiber.el;
const prevInst = fiber.alt.inst;
const nextInst = fiber.inst;
detectIsPlainVirtualNode(nextInst)
? prevInst.value !== nextInst.value && (element.nodeValue = nextInst.value)
: updateAttributes(element, prevInst, nextInst);
}
function commitDeletion(fiber) {
const parent = getFiberWithElement(fiber.parent);
if (fiber.mask & FLUSH_MASK) {
parent.el.children.length > 0 && removeContent(parent.el);
} else {
walk(fiber, onWalkInCommitDeletion(parent.el));
}
}
const onWalkInCommitDeletion = parentElement => (fiber, skip) => {
if (fiber.el) {
!fiber.hook?.getIsPortal() && m.removeElement.call(parentElement, fiber.el);
return skip();
}
};
function move(fiber) {
const sourceNodes = collectElements(fiber, x => x.el);
const sourceNode = sourceNodes[0];
const parentElement = sourceNode.parentElement;
const sourceFragment = new DocumentFragment();
const elementIdx = fiber.eidx;
const move = () => {
for (let i = 1; i < sourceNodes.length; i++) {
m.removeElement.call(parentElement, parentElement.childNodes[elementIdx + 1]);
}
m.replaceElement.call(parentElement, sourceFragment, parentElement.childNodes[elementIdx]);
};
let idx = 0;
for (const node of sourceNodes) {
m.insertElement.call(parentElement, document.createComment(`${elementIdx}:${idx}`), node);
m.appendElement.call(sourceFragment, node);
idx++;
}
moves.push(move);
}
function commit(fiber) {
switch (fiber.tag) {
case CREATE_EFFECT_TAG:
if (!fiber.el || fiber.hook?.getIsPortal()) return;
trackUpdate && trackUpdate(fiber.el);
commitCreation(fiber);
break;
case UPDATE_EFFECT_TAG:
fiber.mask & MOVE_MASK && (move(fiber), (fiber.mask &= ~MOVE_MASK));
if (!fiber.el || fiber.hook?.getIsPortal()) return;
trackUpdate && trackUpdate(fiber.el);
commitUpdate(fiber);
break;
case DELETE_EFFECT_TAG:
commitDeletion(fiber);
break;
default:
break;
}
}
function finishCommit() {
moves.forEach(x => x());
patches.forEach(x => x());
moves = [];
patches = [];
}
function setup() {
if (!detectIsBrowser()) return {};
const np = Node.prototype;
const ep = Element.prototype;
return {
appendElement: np.appendChild,
insertElement: np.insertBefore,
replaceElement: np.replaceChild,
removeElement: np.removeChild,
hasAttribute: ep.hasAttribute,
setAttribute: ep.setAttribute,
removeAttribute: ep.removeAttribute,
setStyleProp: CSSStyleDeclaration.prototype.setProperty,
};
}
const m = setup();
const setTrackUpdate = fn => (trackUpdate = fn);
const toggle = (element, isVisible) => {
isVisible
? m.hasAttribute.call(element, STYLE_ATTR) && m.removeAttribute.call(element, STYLE_ATTR)
: m.setStyleProp.call(element.style, 'display', 'none', 'important');
};
export { createNativeElement, commit, finishCommit, setTrackUpdate, toggle };
//# sourceMappingURL=dom.js.map