@dark-engine/core
Version:
The lightweight and powerful UI rendering engine without dependencies and written in TypeScript (Browser, Node.js, Android, iOS, Windows, Linux, macOS)
196 lines (195 loc) • 6.09 kB
JavaScript
import {
DELETE_EFFECT_TAG,
UPDATE_EFFECT_TAG,
SKIP_EFFECT_TAG,
EFFECT_HOST_MASK,
ATOM_HOST_MASK,
MOVE_MASK,
HOOK_DELIMETER,
} from '../constants';
import { getElementKey, hasChildrenProp } from '../view';
import { Fiber } from '../fiber';
import { createIndexKey } from '../utils';
import { detectIsMemo } from '../memo';
function walk(fiber, onWalk) {
let shouldDeep = true;
let shouldStop = false;
const skip = () => (shouldDeep = false);
const stop = () => (shouldStop = true);
const stack = [fiber];
while (stack.length !== 0) {
const unit = stack.pop();
onWalk(unit, skip, stop);
if (shouldStop) break;
unit !== fiber && unit.next && stack.push(unit.next);
shouldDeep && unit.child && stack.push(unit.child);
shouldDeep = true;
}
}
function collectElements(fiber, transform) {
const elements = [];
walk(fiber, onWalkInCollectElements(elements, transform));
return elements;
}
function onWalkInCollectElements(elements, transform) {
return (fiber, skip) => {
if (fiber.el) {
!fiber.hook?.getIsPortal() && elements.push(transform(fiber));
return skip();
}
};
}
function getFiberWithElement(fiber) {
let $fiber = fiber;
while ($fiber) {
if ($fiber.el) return $fiber;
$fiber = $fiber.parent;
}
return $fiber;
}
function detectIsFiberAlive(fiber) {
let $fiber = fiber;
while ($fiber) {
if ($fiber.tag === DELETE_EFFECT_TAG) return false;
$fiber = $fiber.parent;
}
return Boolean(fiber);
}
function getSuspense(fiber, isPending) {
let suspense = fiber;
while (suspense) {
if (suspense.hook?.getIsSuspense() && (isPending ? suspense.hook.getIsPending() : true)) return suspense;
suspense = suspense.parent;
}
return null;
}
function resolveSuspense(fiber) {
return getSuspense(fiber, true) || getSuspense(fiber, false) || null;
}
function resolveBoundary(fiber) {
let boundary = fiber;
while (boundary) {
if (boundary.hook?.getIsBoundary()) return boundary;
boundary = boundary.parent;
}
return null;
}
function createHookLoc(rootId, idx, hook) {
const fiber = hook.owner;
let $fiber = fiber;
let loc = `${fiber.idx}${HOOK_DELIMETER}${idx}`;
while ($fiber) {
$fiber = $fiber.parent;
$fiber && (loc = `${$fiber.idx}.${loc}`);
}
loc = `[${rootId}]${loc}`;
return loc;
}
const createLoc = (rootId, idx, hook) => () => createHookLoc(rootId, idx, hook);
function detectIsStableMemoTree(fiber, $scope) {
if (!hasChildrenProp(fiber.inst)) return;
const store = $scope.getReconciler().get(fiber.id);
const children = fiber.inst.children;
for (let i = 0; i < children.length; i++) {
const inst = children[i];
const key = getElementKey(inst);
if (key === null) return false;
const alt = store.map[key];
if (!alt) return false;
const pc = alt.inst;
const nc = inst;
const isStable =
detectIsMemo(nc) && detectIsMemo(pc) && nc.type === pc.type && !nc.shouldUpdate(pc.props, nc.props);
if (!isStable) return false;
}
return true;
}
function tryOptStaticSlot(fiber, alt, $scope) {
const store = $scope.getReconciler().get(fiber.id);
const inst = fiber.inst;
alt.el && (fiber.el = alt.el);
for (let i = 0; i < inst.children.length; i++) {
buildChildNode(inst.children, fiber, store.map, i, fiber.eidx);
}
fiber.cc = inst.children.length;
$scope.setMountDeep(false);
}
function tryOptMemoSlot(fiber, alt, $scope) {
const store = $scope.getReconciler().get(fiber.id);
const hasMove = Boolean(store.move);
const hasRemove = Boolean(store.remove);
const hasInsert = Boolean(store.insert);
const hasReplace = Boolean(store.replace);
const canOptimize = ((hasMove && !hasRemove) || (hasRemove && !hasMove)) && !hasInsert && !hasReplace;
if (!canOptimize || !detectIsStableMemoTree(fiber, $scope)) return;
hasMove && tryOptMov(fiber, alt, $scope);
hasRemove && buildChildNodes(fiber, alt, $scope);
}
function tryOptMov(fiber, alt, $scope) {
const store = $scope.getReconciler().get(fiber.id);
buildChildNodes(fiber, alt, $scope, (fiber, key) => {
if (!store.move[key]) return;
fiber.alt = new Fiber().mutate(fiber);
fiber.tag = UPDATE_EFFECT_TAG;
fiber.mask |= MOVE_MASK;
$scope.addCandidate(fiber);
});
}
function buildChildNodes(fiber, alt, $scope, onNode) {
const store = $scope.getReconciler().get(fiber.id);
const inst = fiber.inst;
const children = inst.children;
alt.el && (fiber.el = alt.el);
for (let i = 0; i < children.length; i++) {
const key = getKey(children[i], i);
const $fiber = store.map[key];
buildChildNode(children, fiber, store.map, i, fiber.eidx);
onNode && onNode($fiber, key);
}
fiber.cc = children.length;
$scope.setMountDeep(false);
}
function buildChildNode(children, parent, altMap, idx, startEidx) {
const prevIdx = idx - 1;
const nextIdx = idx + 1;
const key = getKey(children[idx], idx);
const prevKey = getKey(children[prevIdx], prevIdx);
const nextKey = getKey(children[nextIdx], nextIdx);
const fiber = altMap[key];
const left = altMap[prevKey];
const right = altMap[nextKey];
const isFirst = idx === 0;
const isLast = idx === children.length - 1;
isFirst && (parent.child = fiber);
fiber.alt = null;
fiber.parent = parent;
fiber.tag = SKIP_EFFECT_TAG;
fiber.idx = idx;
left ? (fiber.eidx = left.eidx + (left.el ? 1 : left.cec)) : (fiber.eidx = startEidx);
right && (fiber.next = right);
isLast && (fiber.next = null);
notifyParents(fiber);
}
function getKey(inst, idx) {
const key = getElementKey(inst);
return key !== null ? key : createIndexKey(idx);
}
function notifyParents(fiber, alt = fiber) {
fiber.increment(alt.el ? 1 : alt.cec);
alt.mask & EFFECT_HOST_MASK && fiber.markHost(EFFECT_HOST_MASK);
alt.mask & ATOM_HOST_MASK && fiber.markHost(ATOM_HOST_MASK);
}
export {
walk,
collectElements,
getFiberWithElement,
detectIsFiberAlive,
resolveSuspense,
resolveBoundary,
createHookLoc,
createLoc,
tryOptStaticSlot,
tryOptMemoSlot,
notifyParents,
};
//# sourceMappingURL=walk.js.map