Skip to content

Commit

Permalink
feat(select): rewrite DOM.select with snabbdom-selector
Browse files Browse the repository at this point in the history
Make use of snabbdom-selector to replace the previous use of
document.querySelector(). With this change, the performance test,
is running ~15-20FPS faster than prior. Adding features and fixing
bugs had led to performance degradation. Currently, on my computer
(@TylorS), the performance test is running entirely without a single
janky frame to be seen anywhere.

BREAKING CHANGE:
  Before:
    DOM.select(selector) used document.querySelector() under the hood
    for ease of use and for it's substanstially more robust css selector
    engine.

  After:
    DOM.selector(selector) now uses snabbdom-selector to match css selectors
    from the virtual DOM tree for the speed of avoiding the baggage of the DOM.

References #4
  • Loading branch information
TylorS committed Dec 16, 2015
1 parent 2c37bfb commit 8b231e4
Showing 1 changed file with 34 additions and 55 deletions.
89 changes: 34 additions & 55 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import most from 'most'
import hold from '@most/hold'
import snabbdom from 'snabbdom'
import h from 'snabbdom/h'
const {
Expand All @@ -14,15 +15,11 @@ const {
style, sub, sup, table, tbody, td, textarea, tfoot, th,
thead, title, tr, u, ul, video,
} = require(`hyperscript-helpers`)(h)
let matchesSelector
try {
matchesSelector = require(`matches-selector`)
} catch (e) {
matchesSelector = () => {}
}
import fastMap from 'fast.js/array/map'
import reduce from 'fast.js/array/reduce'
import matchesSelector from 'snabbdom-selector'
import filter from 'fast.js/array/filter'
import reduce from 'fast.js/array/reduce'
import concat from 'fast.js/array/concat'
import fastMap from 'fast.js/array/map'

import {getDomElement} from './utils'
import fromEvent from './fromEvent'
Expand Down Expand Up @@ -53,7 +50,8 @@ const makeIsStrictlyInRootScope =
return matched && namespace.indexOf(`.${c}`) === -1
}

for (let el = leaf.parentElement; el !== null; el = el.parentElement) {
for (let el = leaf.elm.parentElement;
el !== null; el = el.parentElement) {
if (rootList.indexOf(el) >= 0) {
return true
}
Expand Down Expand Up @@ -81,6 +79,23 @@ const makeEventsSelector =
}).switch().multicast()
}

const mapToElement = element => Array.isArray(element) ?
fastMap(element, el => el.elm) :
element.elm

function makeFindBySelector(selector, namespace) {
return function findBySelector(rootElem) {
const matches = Array.isArray(rootElem) ?
reduce(rootElem, (m, el) =>
concat(matchesSelector(`${namespace.join(` `)}selector`, el), m),
[]) : matchesSelector(selector, rootElem)
return filter(
matches,
makeIsStrictlyInRootScope(matches, namespace)
)
}
}

// Use function not 'const = x => {}' for this.namespace below
function makeElementSelector(rootElem$) {
return function DOMSelect(selector) {
Expand All @@ -93,38 +108,12 @@ function makeElementSelector(rootElem$) {
const element$ =
selector.trim() === `:root` ?
rootElem$ :
rootElem$.map(
x => {
const array = Array.isArray(x) ? x : [x]
return filter(
reduce(
fastMap(
array,
element => {
if (matchesSelector(element, scopedSelector)) {
return [element]
} else {
let nodeList = element.querySelectorAll(scopedSelector)
const nodeListArray = new Array(nodeList.length)
for (let j = 0; j < nodeListArray.length; j++) {
nodeListArray[j] = nodeList[j]
}
return nodeListArray
}
}
),
(prev, curr) => prev.concat(curr),
[]
),
makeIsStrictlyInRootScope(array, namespace)
)
}
)
rootElem$.map(makeFindBySelector(scopedSelector, namespace))
return {
observable: element$,
observable: element$.map(mapToElement),
namespace: namespace.concat(selector),
select: makeElementSelector(element$),
events: makeEventsSelector(element$),
events: makeEventsSelector(element$.map(mapToElement)),
isolateSource,
isolateSink,
}
Expand Down Expand Up @@ -154,24 +143,14 @@ const makeDOMDriver =
validateDOMDriverInput(view$)

const rootElem$ =
most.create(
add =>
view$
.flatMap(parseTree)
.reduce(
(prevView, newView) => {
patch(prevView, newView)
add(newView.elm)
return newView
}
, rootElem
)
)
rootElem$.drain()

view$
.map(parseTree)
.switch()
.scan((oldVnode, vnode) => patch(oldVnode, vnode), rootElem)
.skip(1) // emits rootElem first
return {
namespace: [],
select: makeElementSelector(rootElem$),
select: makeElementSelector(hold(rootElem$)),
isolateSink,
isolateSource,
}
Expand All @@ -193,5 +172,5 @@ export {
ol, optgroup, option, p, param, pre, q, rp, rt, ruby, s,
samp, script, section, select, small, source, span, strong,
style, sub, sup, table, tbody, td, textarea, tfoot, th,
thead, title, tr, u, ul, video
thead, title, tr, u, ul, video,
}

0 comments on commit 8b231e4

Please sign in to comment.