UNPKG

@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)

463 lines (462 loc) 15.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.detectIsBusy = exports.createUpdate = exports.workLoop = exports.Fiber = void 0; const platform_1 = require('../platform'); const constants_1 = require('../constants'); const utils_1 = require('../utils'); const scope_1 = require('../scope'); const component_1 = require('../component'); const fiber_1 = require('../fiber'); Object.defineProperty(exports, 'Fiber', { enumerable: true, get: function () { return fiber_1.Fiber; }, }); const view_1 = require('../view'); const memo_1 = require('../memo'); const walk_1 = require('../walk'); const scheduler_1 = require('../scheduler'); const fragment_1 = require('../fragment'); const unmount_1 = require('../unmount'); const batch_1 = require('../batch'); function workLoop(isAsync) { const $scope = (0, scope_1.$$scope)(); const wipFiber = $scope.getWorkInProgress(); const isStream = $scope.getIsStream(); const emitter = $scope.getEmitter(); let unit = $scope.getNextUnitOfWork(); let shouldYield = false; try { while (unit && !shouldYield) { const isDeepWalking = $scope.getMountDeep(); unit = performUnitOfWork(unit, wipFiber, isDeepWalking, isStream, emitter, $scope); shouldYield = isAsync && scheduler_1.scheduler.shouldYield(); $scope.setUnitOfWork(unit); if (shouldYield && scheduler_1.scheduler.detectIsTransition() && scheduler_1.scheduler.hasNewTask()) { fork($scope); return false; } } if (!unit && wipFiber) { commit($scope); } } catch (error) { if ((0, utils_1.detectIsPromise)(error)) { return error; } else { const emitter = $scope.getEmitter(); $scope.keepRoot(); emitter.emit('error', (0, utils_1.createError)(error)); if (!isAsync) { throw error; } else { (0, utils_1.logError)(error); } return false; } } return Boolean(unit); } exports.workLoop = workLoop; function performUnitOfWork(fiber, wipFiber, isDeepWalking, isStream, emitter, $scope) { fiber.hook && (fiber.hook.idx = 0); if (isDeepWalking) { const children = fiber.inst.children; if (children && children.length > 0) { const child = mountChild(fiber, $scope); isStream && emitter.emit('chunk', child); return child; } } let parent = fiber.parent; while (parent && fiber !== wipFiber) { const next = mountSibling(fiber, $scope); if (isStream) { emitter.emit('chunk', fiber); next && emitter.emit('chunk', next); } if (next) return next; fiber = parent; parent = fiber.parent; } return null; } function mountChild(parent, $scope) { $scope.navToChild(); const children = parent.inst?.children || null; const inst = children ? setupInstance(children, 0) : null; const fiber = createFiber(getAlternate(parent, inst, 0, $scope), inst, 0); fiber.hook = parent.child?.hook || fiber.hook; fiber.parent = parent; parent.child = fiber; fiber.eidx = parent.el ? 0 : parent.eidx; share(fiber, parent, inst, $scope); return fiber; } function mountSibling(left, $scope) { $scope.navToSibling(); const idx = $scope.getMountIndex(); const children = left.parent.inst.children; const inst = children ? setupInstance(children, idx) : null; if (!inst) { $scope.navToParent(); $scope.setMountDeep(false); return null; } $scope.setMountDeep(true); const fiber = createFiber(getAlternate(left, inst, idx, $scope), inst, idx); fiber.hook = left.next?.hook || fiber.hook; fiber.parent = left.parent; left.next = fiber; fiber.eidx = left.eidx + (left.el ? (left.hook?.getIsPortal() ? 0 : 1) : left.cec); share(fiber, left, inst, $scope); return fiber; } function setupInstance(children, idx) { if (!children || idx >= children.length) return null; const child = children[idx]; let inst = null; children[idx] = (0, utils_1.detectIsArray)(child) ? (0, fragment_1.Fragment)({ slot: child }) : (0, utils_1.detectIsTextBased)(child) ? (0, view_1.Text)(child) : child || supportConditional(child); inst = children[idx]; return inst; } function share(fiber, prev, inst, $scope) { const { alt } = fiber; const shouldMount = alt && (0, memo_1.detectIsMemo)(inst) ? shouldUpdate(fiber, inst, $scope) : true; $scope.setCursor(fiber); fiber.inst = inst; if (alt && alt.mask & constants_1.MOVE_MASK) { fiber.mask |= constants_1.MOVE_MASK; alt.mask &= ~constants_1.MOVE_MASK; } fiber.hook && (fiber.hook.owner = fiber); if (shouldMount) { fiber.inst = mount(fiber, prev, $scope); alt && $scope.getReconciler().reconcile(fiber, alt, $scope); setup(fiber, alt); } else if (fiber.mask & constants_1.MOVE_MASK) { fiber.tag = constants_1.UPDATE_EFFECT_TAG; } $scope.addCandidate(fiber); } function createFiber(alt, next, idx) { const prev = alt ? alt.inst : null; const fiber = new fiber_1.Fiber(idx, (0, fiber_1.getHook)(alt, prev, next)); fiber.alt = alt || null; return fiber; } function getAlternate(fiber, inst, idx, $scope) { const isChild = idx === 0; const parent = isChild ? fiber : fiber.parent; if (!fiber.hook?.getIsWip() && parent.tag === constants_1.CREATE_EFFECT_TAG) return null; const parentId = isChild ? fiber.id : fiber.parent.id; const key = (0, view_1.getElementKey)(inst); const store = $scope.getReconciler().get(parentId); let alt = null; if (key !== null && store) { const isMove = store.move && Boolean(store.move[key]); const isStable = store.stable && Boolean(store.stable[key]); if (isMove || isStable) { alt = store.map[key]; isMove && (alt.mask |= constants_1.MOVE_MASK); } } else { if (fiber.alt) { alt = isChild ? fiber.alt.child : fiber.alt.next; } else { alt = store ? store.map[(0, utils_1.createIndexKey)(idx)] || null : null; } } return alt; } function setup(fiber, alt) { const inst = fiber.inst; let isUpdate = false; fiber.parent.tag === constants_1.CREATE_EFFECT_TAG && (fiber.tag = fiber.parent.tag); isUpdate = alt && fiber.tag !== constants_1.CREATE_EFFECT_TAG && (0, view_1.detectAreSameInstanceTypes)(alt.inst, inst) && (0, view_1.getElementKey)(alt.inst) === (0, view_1.getElementKey)(inst); fiber.tag = isUpdate ? constants_1.UPDATE_EFFECT_TAG : constants_1.CREATE_EFFECT_TAG; if (!fiber.el) { if (isUpdate && alt.el) { fiber.el = alt.el; } else if ((0, view_1.detectIsVirtualNode)(fiber.inst)) { fiber.el = platform_1.platform.createElement(fiber.inst); } } fiber.el && !fiber.hook?.getIsPortal() && fiber.increment(); } function shouldUpdate(fiber, inst, $scope) { if (process.env.NODE_ENV !== 'production') { if ($scope.getIsHot()) return true; } const alt = fiber.alt; const pc = alt.inst; const nc = inst; if (nc.type !== pc.type || nc.shouldUpdate(pc.props, nc.props)) return true; $scope.setMountDeep(false); fiber.tag = constants_1.SKIP_EFFECT_TAG; fiber.child = alt.child; fiber.child.parent = fiber; fiber.hook = alt.hook; fiber.cc = alt.cc; fiber.cec = alt.cec; alt.el && (fiber.el = alt.el); const diff = fiber.eidx - alt.eidx; const deep = diff !== 0; deep && (0, walk_1.walk)(fiber.child, onWalkInShouldUpdate(diff)); (0, walk_1.notifyParents)(fiber, alt); return false; } const onWalkInShouldUpdate = diff => ($fiber, skip) => { $fiber.eidx += diff; if ($fiber.el) return skip(); }; function mount(fiber, prev, $scope) { let inst = fiber.inst; const isComponent = (0, component_1.detectIsComponent)(inst); const component = inst; if (isComponent) { try { let result = component.type(component.props); if ((0, utils_1.detectIsArray)(result)) { !(0, fragment_1.detectIsFragment)(component) && (result = (0, fragment_1.Fragment)({ slot: result })); } else if ((0, utils_1.detectIsTextBased)(result)) { result = (0, view_1.Text)(result); } component.children = result; } catch (err) { const isSSR = (0, platform_1.detectIsSSR)(); if ((0, utils_1.detectIsPromise)(err)) { const promise = err; const reset = createReset(fiber, prev, $scope); const boundary = (0, walk_1.resolveBoundary)(fiber); if (!isSSR) { const suspense = (0, walk_1.resolveSuspense)(fiber); if (suspense || boundary) { $scope.getAwaiter().add(suspense, boundary, promise); } else { reset(); throw promise; } } else { reset(); throw promise; } } else { component.children = []; !isSSR && fiber.setError(err); } } } else if ((0, view_1.detectIsVirtualNodeFactory)(inst)) { inst = inst(); } if ((0, view_1.hasChildrenProp)(inst)) { inst.children = (0, utils_1.detectIsArray)(inst.children) ? inst.children : [inst.children]; isComponent && component.children.length === 0 && component.children.push((0, view_1.createReplacer)()); fiber.cc = inst.children.length; } return inst; } const createReset = (fiber, prev, $scope) => () => { if (prev) { fiber.hook.owner = null; fiber.hook.idx = 0; $scope.navToPrev(); $scope.setUnitOfWork(prev); fiber_1.Fiber.setNextId(prev.id); } else { fiber.id = fiber_1.Fiber.incrementId(); fiber.cec = fiber.alt.cec; } }; function supportConditional(inst) { return (0, utils_1.detectIsFalsy)(inst) ? (0, view_1.createReplacer)() : inst; } function commit($scope) { const isStream = $scope.getIsStream(); if (process.env.NODE_ENV !== 'production') { process.env.NODE_ENV === 'development' && $scope.setIsHot(false); } if (isStream) { platform_1.platform.finishCommit(); cleanup($scope); } else { const wip = $scope.getWorkInProgress(); const deletions = $scope.getDeletions(); const candidates = $scope.getCandidates(); const isUpdate = $scope.getIsUpdate(); const awaiter = $scope.getAwaiter(); const unmounts = []; const inst = wip.inst; for (const fiber of deletions) { const canAsync = fiber.mask & constants_1.ATOM_HOST_MASK && !(fiber.mask & constants_1.EFFECT_HOST_MASK); canAsync ? unmounts.push(fiber) : (0, unmount_1.unmountFiber)(fiber); fiber.tag = constants_1.DELETE_EFFECT_TAG; platform_1.platform.commit(fiber); } isUpdate && sync(wip); $scope.runInsertionEffects(); for (const fiber of candidates) { const item = fiber.inst; fiber.tag !== constants_1.SKIP_EFFECT_TAG && platform_1.platform.commit(fiber); fiber.alt = null; item.children && (item.children = null); } wip.alt = null; wip.hook?.setIsWip(false); inst.children = null; platform_1.platform.finishCommit(); $scope.runLayoutEffects(); $scope.runAsyncEffects(); awaiter.resolve(); unmounts.length > 0 && setTimeout(onUnmount(unmounts)); cleanup($scope); } } const onUnmount = fibers => () => fibers.forEach(unmount_1.unmountFiber); function cleanup($scope, fromFork = false) { $scope.cleanup(); !fromFork && $scope.getEmitter().emit('finish'); $scope.runAfterCommit(); } function sync(fiber) { const diff = fiber.cec - fiber.alt.cec; if (diff === 0) return; const parent = (0, walk_1.getFiberWithElement)(fiber.parent); const scope = { isRight: false }; fiber.hook.setIsWip(false); fiber.increment(diff); (0, walk_1.walk)(parent.child, onWalkInSync(diff, fiber, scope)); } const onWalkInSync = (diff, fiber, scope) => ($fiber, skip) => { if ($fiber === fiber) { scope.isRight = true; return skip(); } $fiber.el && skip(); scope.isRight && ($fiber.eidx += diff); }; function fork($scope) { const $fork = $scope.fork(); const wip = $scope.getWorkInProgress(); const onRestore = createOnRestore($fork, wip.child); const { alt } = wip; wip.child = alt.child; wip.cc = alt.cc; wip.cec = alt.cec; wip.hook?.setIsWip(false); wip.alt = null; wip.hook.idx = 0; wip.hook.owner = wip; $scope.runInsertionEffects(); $scope.applyCancels(); cleanup($scope, true); scheduler_1.scheduler.retain(onRestore); } const createOnRestore = ($fork, child) => options => { const { fiber: wip, setValue, resetValue } = options; const $scope = (0, scope_1.$$scope)(); (0, utils_1.detectIsFunction)(setValue) && setValue(); (0, utils_1.detectIsFunction)(resetValue) && $fork.addCancel(resetValue); wip.alt = new fiber_1.Fiber().mutate(wip); wip.tag = constants_1.UPDATE_EFFECT_TAG; wip.child = child; wip.hook?.setIsWip(true); child.parent = wip; $fork.setRoot($scope.getRoot()); $fork.setWorkInProgress(wip); (0, scope_1.replaceScope)($fork); }; function createCallback(options) { const { rootId, hook, isTransition, tools = $tools } = options; const callback = onRestore => { (0, scope_1.setRootId)(rootId); const isRetain = (0, utils_1.detectIsFunction)(onRestore); const { shouldUpdate, setValue, resetValue } = tools(); const $scope = (0, scope_1.$$scope)(); const owner = hook.owner; const fiber = owner.alt || owner; const isBroken = !fiber.tag; if (isBroken || !shouldUpdate() || !(0, walk_1.detectIsFiberAlive)(fiber) || isRetain) { isRetain && onRestore({ fiber, setValue, resetValue }); return; } (0, utils_1.detectIsFunction)(setValue) && setValue(); (0, utils_1.detectIsFunction)(resetValue) && isTransition && $scope.addCancel(resetValue); fiber.alt = null; fiber.alt = new fiber_1.Fiber().mutate(fiber); fiber.tag = constants_1.UPDATE_EFFECT_TAG; fiber.cc = 0; fiber.cec = 0; fiber.child = null; fiber.hook.setIsWip(true); hook.idx = 0; hook.owner = fiber; $scope.setIsUpdate(true); $scope.resetMount(); $scope.setWorkInProgress(fiber); $scope.setCursor(fiber); fiber.inst = mount(fiber, null, $scope); $scope.setUnitOfWork(fiber); }; return callback; } function createUpdate(rootId, hook) { const { idx } = hook; const update = tools => { const $scope = (0, scope_1.$$scope)(); if ($scope.getIsInsertionEffect()) return; const hasTools = (0, utils_1.detectIsFunction)(tools); const isTransition = $scope.getIsTransition(); const isBatch = $scope.getIsBatch(); const isEvent = $scope.getIsEvent(); const priority = isTransition ? constants_1.TaskPriority.LOW : isEvent ? constants_1.TaskPriority.HIGH : constants_1.TaskPriority.NORMAL; const forceAsync = isTransition; const onTransitionEnd = isTransition ? $scope.getOnTransitionEnd() : null; const callback = createCallback({ rootId, hook, isTransition, tools: hasTools ? tools : undefined, }); const loc = (0, walk_1.createLoc)(rootId, idx, hook); const options = { priority, forceAsync, isTransition, loc, onTransitionEnd, }; if (isBatch) { (0, batch_1.addBatch)( hook, () => scheduler_1.scheduler.schedule(callback, options), () => hasTools && tools().setValue(), ); } else { scheduler_1.scheduler.schedule(callback, options); } }; return update; } exports.createUpdate = createUpdate; const $tools = () => ({ shouldUpdate: utils_1.trueFn, setValue: null, resetValue: null, }); const detectIsBusy = () => Boolean((0, scope_1.$$scope)()?.getWorkInProgress()); exports.detectIsBusy = detectIsBusy; //# sourceMappingURL=workloop.js.map