Skip to content

Commit

Permalink
trying to add the render callback (#45) but not sure if this is the…
Browse files Browse the repository at this point in the history
… right way - and had to change `defer` to `setTimeout` for now, as I'm not sure if `requestAnimationFrame` even works with `jsdom` in the first place?
  • Loading branch information
mindplay-dk committed Sep 26, 2019
1 parent 9ac263c commit 997024c
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 222 deletions.
376 changes: 189 additions & 187 deletions src/reconciler.js
Original file line number Diff line number Diff line change
@@ -1,187 +1,189 @@
import { createElement, updateElement } from './dom'
import { resetCursor } from './hooks'
import { defer, hashfy, merge } from './util'

const options = {}
const FPS = 1000 / 60
export const [HOST, HOOK, ROOT, SVG, PLACE, UPDATE, DELETE] = [0, 1, 2, 3, 4, 5, 6]

let updateQueue = []
let nextWork = null
let pendingCommit = null
let currentFiber = null

function render (vnode, node) {
let rootFiber = {
tag: ROOT,
node,
props: { children: vnode }
}
scheduleWork(rootFiber)
}

function scheduleWork (fiber) {
updateQueue.push(fiber)
defer(workLoop)
}

function workLoop (startTime = 0) {
if (startTime && performance.now() - startTime > FPS) {
defer(workLoop)
} else if (!nextWork && updateQueue.length > 0) {
nextWork = updateQueue.shift()
defer(workLoop)
} else {
const nextTime = performance.now()
nextWork = performWork(nextWork)
if (nextWork) {
workLoop(nextTime)
} else {
options.commitWork
? options.commitWork(pendingCommit)
: commitWork(pendingCommit)
}
}
}

function performWork (WIP) {
WIP.tag == HOOK ? updateHOOK(WIP) : updateHost(WIP)
if (WIP.child) return WIP.child
while (WIP) {
completeWork(WIP)
if (WIP.sibling) return WIP.sibling
WIP = WIP.parent
}
}

function updateHost (WIP) {
if (!options.end && !WIP.node) {
WIP.parentNode = getParentNode(WIP)
if (WIP.type === 'svg') WIP.tag = SVG
WIP.node = createElement(WIP)
}
let p = WIP.parentNode || {}
WIP.insertPoint = p.lastFiber || null
p.lastFiber = WIP
WIP.node.lastFiber = null
reconcileChildren(WIP, WIP.props.children)
}

function getParentNode (fiber) {
if (fiber.parent) {
return fiber.parent.tag === HOOK
? fiber.parent.parent.node
: fiber.parent.node
} else {
return fiber.node
}
}

function updateHOOK (WIP) {
WIP.props = WIP.props || {}
WIP.state = WIP.state || {}
currentFiber = WIP
resetCursor()
reconcileChildren(WIP, WIP.type(WIP.props))
currentFiber.patches = WIP.patches
}

function reconcileChildren (WIP, children) {
const oldFibers = WIP.kids
const newFibers = (WIP.kids = hashfy(children, WIP.kids))
let reused = {}

for (const k in oldFibers) {
let newFiber = newFibers[k]
let oldFiber = oldFibers[k]

if (newFiber && newFiber.type === oldFiber.type) {
reused[k] = oldFiber
} else {
oldFiber.patchTag = DELETE
WIP.patches.push(oldFiber)
}
}

let prevFiber = null
let alternate = null

for (const k in newFibers) {
let newFiber = newFibers[k]
let oldFiber = reused[k]

if (oldFiber) {
alternate = createFiber(oldFiber, { patchTag: UPDATE })
if (!options.end) newFiber.patchTag = UPDATE
newFiber = merge(alternate, newFiber)
newFiber.alternate = alternate
if (newFiber.key) newFiber.patchTag = PLACE
} else {
newFiber = createFiber(newFiber, { patchTag: PLACE })
}

newFibers[k] = newFiber
newFiber.parent = WIP

if (prevFiber) {
prevFiber.sibling = newFiber
} else {
if (WIP.tag === SVG) newFiber.tag = SVG
WIP.child = newFiber
}
prevFiber = newFiber
}
if (prevFiber) prevFiber.sibling = null
}

function createFiber (vnode, data) {
data.tag = typeof vnode.type === 'function' ? HOOK : HOST
return merge(vnode, data)
}

function completeWork (fiber) {
if (!options.end && fiber.parent) {
fiber.parent.patches = (fiber.parent.patches || []).concat(
fiber.patches || [],
fiber.patchTag ? [fiber] : []
)
} else {
pendingCommit = fiber
}
}

function commitWork (WIP) {
WIP.patches.forEach(p => {
commit(p)
const e = p.effects
if (e) for (const k in e) e[k]()
})
nextWork = null
pendingCommit = null
}
function commit (fiber) {
fiber.parent.patches = fiber.patches = []
let parent = fiber.parentNode || fiber.parent.node
let dom = fiber.node || fiber.child.node
switch (fiber.patchTag) {
case UPDATE:
updateElement(dom, fiber.alternate.props, fiber.props)
break
case DELETE:
parent.removeChild(dom)
break
default:
let point = fiber.insertPoint ? fiber.insertPoint.node : null
let after = point ? point.nextSibling : parent.firstChild
if (fiber.tag === HOOK || after === dom) return
if (after === null && dom === parent.lastChild) return
parent.insertBefore(dom, after)
break
}
}

function getWIP () {
return currentFiber || null
}

export { render, scheduleWork, getWIP, options }
import { createElement, updateElement } from './dom'
import { resetCursor } from './hooks'
import { defer, hashfy, merge } from './util'

const options = {}
const FPS = 1000 / 60
export const [HOST, HOOK, ROOT, SVG, PLACE, UPDATE, DELETE] = [0, 1, 2, 3, 4, 5, 6]

let updateQueue = []
let nextWork = null
let pendingCommit = null
let currentFiber = null

function render (vnode, node, done) {
let rootFiber = {
tag: ROOT,
node,
props: { children: vnode },
done
}
scheduleWork(rootFiber)
}

function scheduleWork (fiber) {
updateQueue.push(fiber)
defer(workLoop)
}

function workLoop (startTime = 0) {
if (startTime && performance.now() - startTime > FPS) {
defer(workLoop)
} else if (!nextWork && updateQueue.length > 0) {
nextWork = updateQueue.shift()
defer(workLoop)
} else {
const nextTime = performance.now()
nextWork = performWork(nextWork)
if (nextWork) {
workLoop(nextTime)
} else {
options.commitWork
? options.commitWork(pendingCommit)
: commitWork(pendingCommit)
}
}
}

function performWork (WIP) {
WIP.tag == HOOK ? updateHOOK(WIP) : updateHost(WIP)
if (WIP.child) return WIP.child
while (WIP) {
completeWork(WIP)
if (WIP.sibling) return WIP.sibling
WIP = WIP.parent
}
}

function updateHost (WIP) {
if (!options.end && !WIP.node) {
WIP.parentNode = getParentNode(WIP)
if (WIP.type === 'svg') WIP.tag = SVG
WIP.node = createElement(WIP)
}
let p = WIP.parentNode || {}
WIP.insertPoint = p.lastFiber || null
p.lastFiber = WIP
WIP.node.lastFiber = null
reconcileChildren(WIP, WIP.props.children)
}

function getParentNode (fiber) {
if (fiber.parent) {
return fiber.parent.tag === HOOK
? fiber.parent.parent.node
: fiber.parent.node
} else {
return fiber.node
}
}

function updateHOOK (WIP) {
WIP.props = WIP.props || {}
WIP.state = WIP.state || {}
currentFiber = WIP
resetCursor()
reconcileChildren(WIP, WIP.type(WIP.props))
currentFiber.patches = WIP.patches
}

function reconcileChildren (WIP, children) {
const oldFibers = WIP.kids
const newFibers = (WIP.kids = hashfy(children, WIP.kids))
let reused = {}

for (const k in oldFibers) {
let newFiber = newFibers[k]
let oldFiber = oldFibers[k]

if (newFiber && newFiber.type === oldFiber.type) {
reused[k] = oldFiber
} else {
oldFiber.patchTag = DELETE
WIP.patches.push(oldFiber)
}
}

let prevFiber = null
let alternate = null

for (const k in newFibers) {
let newFiber = newFibers[k]
let oldFiber = reused[k]

if (oldFiber) {
alternate = createFiber(oldFiber, { patchTag: UPDATE })
if (!options.end) newFiber.patchTag = UPDATE
newFiber = merge(alternate, newFiber)
newFiber.alternate = alternate
if (newFiber.key) newFiber.patchTag = PLACE
} else {
newFiber = createFiber(newFiber, { patchTag: PLACE })
}

newFibers[k] = newFiber
newFiber.parent = WIP

if (prevFiber) {
prevFiber.sibling = newFiber
} else {
if (WIP.tag === SVG) newFiber.tag = SVG
WIP.child = newFiber
}
prevFiber = newFiber
}
if (prevFiber) prevFiber.sibling = null
}

function createFiber (vnode, data) {
data.tag = typeof vnode.type === 'function' ? HOOK : HOST
return merge(vnode, data)
}

function completeWork (fiber) {
if (!options.end && fiber.parent) {
fiber.parent.patches = (fiber.parent.patches || []).concat(
fiber.patches || [],
fiber.patchTag ? [fiber] : []
)
} else {
pendingCommit = fiber
}
}

function commitWork (WIP) {
WIP.patches.forEach(p => {
commit(p)
const e = p.effects
if (e) for (const k in e) e[k]()
})
nextWork = null
pendingCommit = null
}
function commit (fiber) {
fiber.parent.patches = fiber.patches = []
let parent = fiber.parentNode || fiber.parent.node
let dom = fiber.node || fiber.child.node
switch (fiber.patchTag) {
case UPDATE:
updateElement(dom, fiber.alternate.props, fiber.props)
break
case DELETE:
parent.removeChild(dom)
break
default:
let point = fiber.insertPoint ? fiber.insertPoint.node : null
let after = point ? point.nextSibling : parent.firstChild
if (fiber.tag === HOOK || after === dom) return
if (after === null && dom === parent.lastChild) return
parent.insertBefore(dom, after)
break
}
fiber.parent.done && fiber.parent.done()
}

function getWIP () {
return currentFiber || null
}

export { render, scheduleWork, getWIP, options }
9 changes: 3 additions & 6 deletions src/reconciler.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ import { h } from "./h";

const testRender = jsx => new Promise(resolve => {
let target = document.createElement("div")
let dummy = document.createElement("div")

document.body.appendChild(target)
document.body.appendChild(dummy)

render(jsx, target)
render({ type: () => resolve(target.innerHTML) }, dummy)
render(jsx, target, () => resolve(target.innerHTML))
})

test('render HTML elements', done => {
testRender(<div/>).then(html => {
expect(html).toBe("<div></div>")
testRender(<div>test</div>).then(html => {
expect(html).toBe("<div>test</div>")

done()
})
Expand Down
Loading

0 comments on commit 997024c

Please sign in to comment.