Giter VIP home page Giter VIP logo

nanomorph's Introduction

nanomorph stability

npm version build status downloads js-standard-style

Hyper fast diffing algorithm for real DOM nodes ⚑

Usage

var morph = require('nanomorph')
var html = require('nanohtml')

var tree = html`<div>hello people</div>`
document.body.appendChild(tree)
// document.body === <body><div>hello people</div></body>

morph(tree, html`<div>nanananana-na-no</div>`)
// document.body === <body><div>nanananana-na-no</div></body>

morph(tree, html`<div>teeny, tiny, tin bottle</div>`)
// document.body === <body><div>teeny, tiny, tin bottle</div></body>

Clearing Input Values

To remove values from inputs, there's a few options:

html`<input class="beep" value=${null}>` // set the value to null
html`<input class="beep">`               // omit property all together

Reordering Lists

It's common to work with lists of elements on the DOM. Adding, removing or reordering elements in a list can be rather expensive. To optimize this you can add an id attribute to a DOM node. When reordering nodes it will compare nodes with the same ID against each other, resulting in far fewer re-renders. This is especially potent when coupled with DOM node caching.

var el = html`
  <section>
    <div id="first">hello</div>
    <div id="second">world</div>
  </section>
`

Caching DOM elements

Sometimes we want to tell the algorithm to not evaluate certain nodes (and its children). This can be because we're sure they haven't changed, or perhaps because another piece of code is managing that part of the DOM tree. To achieve this nanomorph evaluates the .isSameNode() method on nodes to determine if they should be updated or not.

var el = html`<div>node</div>`

// tell nanomorph to not compare the DOM tree if they're both divs
el.isSameNode = function (target) {
  return (target && target.nodeName && target.nodeName === 'DIV')
}

Prevent Morphing Particular Elements

There are situations where two elements should never be morphed, but replaced. nanomorph automatically does this for elements with different tag names. But if we're implementing a custom component system, for example, components of different types should probably be treated as if they had different tagsβ€”even if they both render a <div> at their top level.

Nodes can have an optional data-nanomorph-component-id attribute. nanomorph will only ever morph nodes if they both have the same value in this attribute. If the values differ, the old node is replaced with the new one.

var el = html`<div data-nanomorph-component-id="a">hello</div>`
var el2 = html`<div data-nanomorph-component-id="b">goodbye</div>`

assert.equal(nanomorph(el, el2), el2)

nanomorph doesn't have an opinion on the values of the data-nanomorph-component-id attribute, so we can decide the meaning we give it on a case by case basis. There could be a unique ID for every type of component, or a unique ID for every instance of a component, or any other meaning.

FAQ

How is this different from morphdom?

It's quite similar actually; the API of this library is completely compatible with morphdom and we've borrowed a fair few bits. The main difference is that we copy event handlers like onclick, don't support browsers that are over a decade old, and don't provide custom behavior by removing all hooks. This way we can guarantee a consistent, out-of-the box experience for all your diffing needs.

Why doesn't this work in Node?

Node has no concept of a DOM - server side rendering is basically fancy string concatenation. If you want to combine HTML strings in Node, check out hyperstream.

This library seems cool, I'd like to build my own!

Nanomorph was optimized for simplicity, but different situations might require different tradeoffs. So in order to allow folks to build their own implementation we expose our test suite as a function you can call. So regardless if you're doing it to solve a problem, or just for fun: you can use the same tests we use for your own implementation. Yay! ✨

API

tree = nanomorph(oldTree, newTree)

Diff a tree of HTML elements against another tree of HTML elements and create a patched result that can be applied on the DOM.

⚠️ nanomorph will modify the newTree and it should be discarded after use

Installation

$ npm install nanomorph

See Also

Similar Packages

Further Reading

Authors

License

MIT

nanomorph's People

Contributors

aishikaty avatar bcomnes avatar cedricraison avatar finnp avatar goto-bus-stop avatar greghuc avatar him2him2 avatar juliangruber avatar kristoferjoseph avatar lenanor avatar mantoni avatar moszeed avatar nicknikolov avatar reminyborg avatar rreusser avatar schtauffen avatar sethvincent avatar shannonmoeller avatar tornqvist avatar yoshuawuyts avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nanomorph's Issues

Add TypeScript typings?

This seems to work for me, but I'm a TypeScript beginner and am probably doing a lot of things wrong:

// Modified from https://github.com/patrick-steele-idem/morphdom/pull/113/commits/0b6b2e87b5f1b193c40c4c64f25938846ccd4039
declare module "nanomorph" {
  interface NanomorphOptions {
    getNodeKey?: (node: Node) => any;
    onBeforeNodeAdded?: (node: Node) => Node;
    onNodeAdded?: (node: Node) => Node;
    onBeforeElUpdated?: (fromEl: HTMLElement, toEl: HTMLElement) => boolean;
    onElUpdated?: (el: HTMLElement) => void;
    onBeforeNodeDiscarded?: (node: Node) => boolean;
    onNodeDiscarded?: (node: Node) => void;
    onBeforeElChildrenUpdated?: (
      fromEl: HTMLElement,
      toEl: HTMLElement
    ) => boolean;
    childrenOnly?: boolean;
  }

  namespace nanomorph {

  }

  function nanomorph(
    fromNode: Node,
    toNode: Node | string,
    options?: NanomorphOptions
  ): void;

  export default nanomorph; // Added "default" here.
}

Update skipping every other element

It seems like nanomorph element iteration is skipping every other element somewhere. I (blindly) tried adding ids, but the result is unchanged.

Version: [email protected] with [email protected]

Expected behavior:

  1. Start with a root node with zero children.
  2. run nanomorph(new, old) where new tree has ten children
  3. tree now contains ten children

Observed behavior: DOM is updated, but only contains elements 0, 2, 4, 6, 8

Example: http://codepen.io/rsreusser/pen/KazMMa?editors=0010

Code to reproduce:

const makeHtml = (n) => bel`
  <ul>${
    new Array(n).fill(0).map((d, i) => bel`<li>${i}</li>`)
  }</ul> 
`;
const tree = makeHtml(0);
document.body.appendChild(tree);
nanomorph(makeHtml(10), tree);

Thoughts: Aside from a bug, the next most likely issue seems to me that I'm misusing the API. (right after that is that this is a very experimental library πŸ˜„ )

Random thought: This isn't that thing that happens when you mutate a list as you're iterating over it, is it? Some things solve that by letting each operation return an updated loop index. Just a random guess without proper debugging.

Check values before morph

As suggested by @yoshuawuyts in #choo, more checks could be added to nanomorph to prevent unnecessary repainting.

This would likely result in extra lookups, but depending on whether repaints are computationally more expensive, it could be worth it.

GIF for demonstration:
dom-update1

Event Failures

I created tests for the events listed in https://github.com/choojs/nanomorph/blob/master/lib/events.js#L32

They are tested in the as follows:

  1. test if the event is parsed from html
  2. test if events are removed in morph
  3. test of events are copied in morph

The following events fail to parse from HTML attribute. I believe this is a problem in Bel. But I am not sure where this problem originates.

The first two are listed as problematic and fail

Note: these event may not work as expected in Chrome, Safari and Opera 15+ using the JavaScript HTML DOM syntax. However, it should work as an HTML attribute and by using the addEventListener() method (See syntax examples below).

onfocusin
https://www.w3schools.com/jsref/event_onfocusin.asp

onfocusout
https://www.w3schools.com/jsref/event_onfocusout.asp

These events are listed as Editor's Draft(Non-stable version), but should work in chrome. They also fail.

ontouchcancel
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ontouchcancel
https://developer.mozilla.org/en-US/docs/Web/Events/touchcancel

ontouchend
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ontouchend
https://developer.mozilla.org/en-US/docs/Web/Events/touchend

ontouchmove
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ontouchmove
https://developer.mozilla.org/en-US/docs/Web/Events/touchmove

ontouchstart
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ontouchstart
https://developer.mozilla.org/en-US/docs/Web/Events/touchstart

This event should only work on the body tag, but also seems to fail.
onunload
https://www.w3schools.com/jsref/event_onunload.asp
Should only work on the body tag. Testing on the body tag fails.

I will submit a pull request that contains the event tests.

Export `updateChildren()` or `childrenOnly` option

I'd like to patch just the children of two nodes (specifically a custom element and a document fragment). I think this would be possible with nanomorph if you exposed the updateChildren() function or provide a childrenOnly option like morphdom.

annotate source code

Hi
Do you think it's a good idea to annotate the source for educational purposes? Just like how the-super-tiny-compiler does. I really like the idea of a tiny module demonstrating hot but hard technologies. If not this module maybe you could make another one for educational purposes. It would also reduce the barrier of contributing your choo module I believe. Thanks!

idea: abstract-dom-morph

If this library becomes a thing we would probably want to keep parity with the nanomorph diffing algorithm or (yo-yo if they're not keen). It would be cool if we could create a shared testing library that defines all behavior we expect from a diffing library so we can make sure our implementations adhere to this. I would imagine the API would be along the lines of:

const abstract = require('abstract-dom-morph/test')
const nanomorph = require('nanomorph')

abstract(nanomorph)

This could help with a possible migration of choo to a tinier diffing implementation and help foster the creation of new DOM-only diffing algorithms. Thoughts?

cc/ @kristoferjoseph

See Also

can't set 'onchange' event in select tag

I just ran into this but I'm not sure if it is my mistake or an issue.
When setting something like

function mainView (state, emit) {
  return html`
    <body>
      <select onchange="${onchange}">
        ${state.opts.map((opt, i) => {
          return html`<option value="${i}">${opt}</option>`
        })}
      </select>
      <button onclick=${onclick}>Run</button>
    </body>
  `

  function onclick () {
    console.log('click!')
  }
  function onchange() {
    console.log('change!')
  }
}

The click event is normally attached to the button, but the change event isn't attached to the select tag.
I saw in comments that some elements need speacial treatment to set events, but there is no special treatment for select tags, even tho there is in morphdom. Is there something missing? or am I doing something wrong?

[optimization] child node reordering

So @developit pointed out another optimization we could do is check node reordering. Often times lists get updated; items get appended or removed. Given that lists, forms and links are the staple elements of HTML this is probably worth optimizing for.

The new node comparison algorithm would then become:

  1. check if node is same, if yes do nothing
  2. compare the full array of child elements with equality checks, apply the re-order algorithm
  3. insert new children where reordering didn’t apply

I reckon this should speed up the reordering case by quite a bunch. Help welcome! ✨

Comments are un-commented

It's late at night, so I'll keep this short.
I'm making a framework that uses nanomorph heavily. I've just been playing with it, and I realized that comments are uncommented, meaning the <!-- and --> are removed. This is a bit annoying when debugging my app. Not urgent, but just letting you know it's there.

API: old vs new order

Was there any particular reason to use the order newTree, oldTree in the API? Maybe I'm just used to morphdom/yo-yo but I would have expected oldTree, newTree since you're morphing the old tree to become the new tree, no?

implement Meyers diffing algorithm

So we have a slight diffing problem β€” not only are we fairly slow at updating, reordering nodes is also suboptimal. This means that some reorders can be surprising at the worst case, and in a worst case reorders simply take longer than they should.

In computer science this is known as "diffing", and is a rather well-studied problem. Probably the best known algo for this is the Mayers Diffing algorithm. It'd be neat if we could make use of this for nanomorph.

references

Input focus lost when sibling is added to parent node.

This is an edge case i discovered while using nanomorph "in anger".

What is the best way to test issues like these?

// initial state
<form>
  <label for=stuff>
    <input autofocus="autofocus" name="stuff">
  </label>
</form>

// Updated state based on input interaction
<form>
  <h1>YOU DID A THING!</h1>
  <label for=stuff>
    <input  autofocus="autofocus" name="stuff">
  </label>
</form>

Copy input values?

Do we think we should copy input values ala yo-yo?
Always felt weird to me that morphdom doesn't handle this case.

usage without require/module manager

I haven't modified my project to use any kind of module manager yet. So, is this possible to use nanomorph by using regular script tags only?

what would be required if this cant be done through npm?

I use *-umd files from morpdom.

Replace events.js to be more interop

Something like this

let events = []

if (typeof window !== 'undefined' && typeof document !== 'undefined') {
  // eslint-disable-next-line
  for (const key in document) {
    const isEvent = document[key] == null || typeof document[key] === 'function'
    if (key.startsWith('on') && isEvent) {
      events.push(key)
    }
  }
} else {
  events = [
    'onreadystatechange',
    'onpointerlockchange',
    'onpointerlockerror',
    'onbeforecopy',
    'onbeforecut',
    'onbeforepaste',
    'oncopy',
    'oncut',
    'onpaste',
    'onsearch',
    'onselectionchange',
    'onselectstart',
    'onvisibilitychange',
    'onabort',
    'onblur',
    'oncancel',
    'oncanplay',
    'oncanplaythrough',
    'onchange',
    'onclick',
    'onclose',
    'oncontextmenu',
    'oncuechange',
    'ondblclick',
    'ondrag',
    'ondragend',
    'ondragenter',
    'ondragleave',
    'ondragover',
    'ondragstart',
    'ondrop',
    'ondurationchange',
    'onemptied',
    'onended',
    'onerror',
    'onfocus',
    'oninput',
    'oninvalid',
    'onkeydown',
    'onkeypress',
    'onkeyup',
    'onload',
    'onloadeddata',
    'onloadedmetadata',
    'onloadstart',
    'onmousedown',
    'onmouseenter',
    'onmouseleave',
    'onmousemove',
    'onmouseout',
    'onmouseover',
    'onmouseup',
    'onmousewheel',
    'onpause',
    'onplay',
    'onplaying',
    'onprogress',
    'onratechange',
    'onreset',
    'onresize',
    'onscroll',
    'onseeked',
    'onseeking',
    'onselect',
    'onstalled',
    'onsubmit',
    'onsuspend',
    'ontimeupdate',
    'ontoggle',
    'onvolumechange',
    'onwaiting',
    'onwheel',
    'onauxclick',
    'ongotpointercapture',
    'onlostpointercapture',
    'onpointerdown',
    'onpointermove',
    'onpointerup',
    'onpointercancel',
    'onpointerover',
    'onpointerout',
    'onpointerenter',
    'onpointerleave',
    'onwebkitfullscreenchange',
    'onwebkitfullscreenerror',
    'onsecuritypolicyviolation',
    'onformdata',
    'onfullscreenchange',
    'onfullscreenerror',
    'onfreeze',
    'onresume'
  ]
}

module.exports = events

Also, currently, there are only 45 event names, but as of today it seems they are 95. As, probably no so common or used, but why not. Also it will decrease the bundle sizes, and will use this list only on server.

Another Alternative.

@yoshuawuyts I have also tried to create this exact thing for use in a framework I have been building. It's located here, perhaps it could provide some inspiration since it is a slightly different approach and it also addresses node ordering with data-key and id attributes. It also supports namespaces in a pretty clean way.

Anyways this is a non issue but thought I would shoot this your way.
Feel free to close.

Input type=search not working

When typing into an input search input box, the input is cleared every time, which is a behavioral difference to morphdom.

document.createTreeWalker

Have you considered to use document.createTreeWalker or document.createNodeIterator for DOM traversal?
I like the API but it is hardly ever used and I just wonder why.

questions before switching from morphdom

I'm planing to switch from morhdom due to the size of this library. I have read the FAQs already.

I wanted to know, if there is a way to find out if nanomorph supports a browser or not?

Does nanomorph gracefully degrade on unsupported browsers?

benchmark

It'd be neat to have a benchmark to test our diffing needs β€” perf is a feature, and we should be able to catch regressions.

Given this would need to run in the browser, we'd probably need some tooling around this. I was thinking it'd be cool to run a headless Chrome instance through puppeteer, and use the nanobench module to perform the benches. Would be neat if we could run it as a standalone CLI thing, with similar output to TAP.

But by all means β€” feel free to pick up this issue, and run with it. Probably worth picking up similar benches as in choojs/choo#492.

Hope this makes sense. Thanks!

The tests

Heya there! πŸ‘‹

I'm deeply digging into tests, because i'm experimenting to implement virtual dom diffing, where nanomorph guided me at most. Diffing vdom, seems pretty smaller and easy than that here.

I believe my implementation looks good, but it's just a toy for the moment. All nanomorph tests pass, except the last one for nested without id.

But while digging into test/diff.js i found one test which have opening tag, but the closing tag is missing a slash / - see it here test/diff.js#L357-L365. Is it intentional or is typo?

Also, the title isn't very clear to me. What should be the result? I think it should be <section><div></div><section>, but the title says me that it expect to be something like <section><div>'hello'</div><section> which not make sense to me to be correct result?

One more thing is to include the test suite (that file) in the npm package, so others can tests against it.

Better usage example?

Hello, I'm trying to use nanomorph on the entire <body>. I'm getting an HTML string via ajax (see inject). The goal is to update the DOM body by updating only the changed nodes (instead of destroying/replacing everything). What am I missing here?

var inject = "string of HTML, e.g. $('body').html()";
var tree = morph(inject, $('body').html());
console.log(tree); // undefined

Thanks in advance

Is this project dead?

Open issues and PRs with no updates to master in 9 months. Wondering if I should switch back to morphdom.

nanomorph mutates the new DOM node tree

When using appendChild to add children from the new tree to the original it removes them from the original.
Should we add a warning about this in the docs?

ref morphdom writes:

NOTE: This module will modify both the original and target DOM node tree during the transformation. It is assumed that the target DOM node tree will be discarded after the original DOM node tree is morphed.

test attribute copying

We're testing SVG namespaces and events, but not regular ol' attributes. I've been running into issues on the server with this, so we should probably test this. Would also help enable #37 as we need to read and write attributes.

Specifically it'd be nice if tests could hit el.setAttribute() and el.getAttribute(). Thanks!

Clean up the test suite

  • we have 400 tests or so
  • they're kinda nested and weird
  • we're gonna be adding a bunch more tests, and it'll only get worse
  • it's hard to only run a part of the suite

So we should clean up our tests a little before we refactor & add more tests :D

patching stateful DOM (input, textarea, form fields)

Hi there,

I love the direct DOM diffing, and I first experimented wit morphdom. I encountered one problem connected to what in React's terminology are controlled components. In short, when you diff <input value='sth'> vs <input value='something'>, the stateful part of the DOM is lost (field focus, cursor position, etc.).

I was wondering if that is something that you guys dealt with in nanomorph?

rename update-dom

right now the update-dom file allows creating an update loop from a single element that prevents the tree from being dismounted when the type of root node is changed. This removes a lot of boilerplate when building applications. I think the file is rather unfortunately named though, so I reckon we should rename it.

  • require('nanomorph/loop') (my preference)
  • require('nanomorph/update')

thoughts? cc/ @kristoferjoseph

copy over events

Whenever we morph elements we should probs be copying over the events. From yo-yo:

// update-events.js
module.exports = [
  // attribute events (can be set with attributes)
  'onclick',
  'ondblclick',
  'onmousedown',
  'onmouseup',
  'onmouseover',
  'onmousemove',
  'onmouseout',
  'ondragstart',
  'ondrag',
  'ondragenter',
  'ondragleave',
  'ondragover',
  'ondrop',
  'ondragend',
  'onkeydown',
  'onkeypress',
  'onkeyup',
  'onunload',
  'onabort',
  'onerror',
  'onresize',
  'onscroll',
  'onselect',
  'onchange',
  'onsubmit',
  'onreset',
  'onfocus',
  'onblur',
  'oninput',
  // other common events
  'oncontextmenu',
  'onfocusin',
  'onfocusout'
]
// copy.js
  function copier (f, t) {
    // copy events:
    var events = opts.events || defaultEvents
    for (var i = 0; i < events.length; i++) {
      var ev = events[i]
      if (t[ev]) { // if new element has a whitelisted attribute
        f[ev] = t[ev] // update existing element
      } else if (f[ev]) { // if existing element has it and new one doesnt
        f[ev] = undefined // remove it from existing element
      }
    }
    // copy values for form elements
    if ((f.nodeName === 'INPUT' && f.type !== 'file') || f.nodeName === 'TEXTAREA' || f.nodeName === 'SELECT') {
      if (t.getAttribute('value') === null) t.value = f.value
    }
  }

morph input types with file on 'em

Believe we're not touching the file field which means morphing might go wrong if the whole view is replaced. Bit of an edge case, but something to think about.

A better diffing algorithm

We could turn the DOM into a Merkle tree, but because each Node is already unique (hurray for objects), the tree itself is already a tree of hashes. The goal of this is to provide functionality similar to React's .shouldComponentUpdate() but without explicitly having to create manual checks, so that any tree of valid DOM nodes can be parsed and diffed efficiently (hurray for the DOM as the lowest common denominator!). A few rules should suffice I reckon:

rules for creating new trees

  1. When a node in the new tree is updated, traverse the tree upwards and set an incremental "edited=" label; this acts as a signal if that part of the tree should be traversed for further updates
  2. Thunk as many components as possible. When creating nodes if oldState === newState return the existing node so [object Object] === [object Object] will return true.

rules for diffing trees

  1. If a node in the new tree !== node in the old tree, update the node in the old tree
  2. If a node in the new tree has a later "edited=" label than the new tree, or no label at all, update the node in the old tree - don't forget to copy over the "edited" label. Then continue traversing the tree

Trees are generally only a few layers of nesting, with loads of elements living in parallel. Traversing upwards in a tree should be generally inexpensive, so the "edited=" label can be applied with little cost. Because JS Objects are unique, and the label provides guarantees about the state of the child nodes, we can stop traversing nodes early and essentially gain the result of React's .shouldComponentUpdate().

caveats

  • no idea how this works with widgets; if widgets don't rely on external state this should be sweet. If they do - I'm not sure
  • this might not work with webworker diffing, as when stringifying properties on the nodes might not be transferred (plain properties, not DOM node attributes). But if we're sending regular objects it might work - but then we get the problem of how to label nodes, as an incremental clock may not work - shared state between workers is introduced and... yeah, that would def make it more complicated hah
  • walking svg nodes is hard. Not specifically related to this algorithm, but just want to throw it out there. I've got not idea how to walk svg haha

So yeah, that's the idea. morphdom can already be improved in size and speed a bit by removing the event hooks; but with this we might be able to take things a bit further. Thanks! ✨

selected attribute is not updated properly

The repo where this bug happens is https://github.com/cideM/tinyfinancial on the details branch (https://github.com/cideM/tinyfinancial/blob/details/src/client/views/Details/index.js). But it requires MongoDB and one would have to remove the facebook authentication. But maybe I'll be able to explain my issue through code examples as well.

I have a controlled <select> and onchange a choo state variable is updated, the view function is rerendered and the newly selected <option> should now be selected. The actual behavior is very confusing though:

When the function renders the first time the DOM looks like this

<select value="2017">
   <option value="2017" selected="selected">2017</option>
   <option value="2016">2016</option>
</select>

If the user select 2016, the DOM looks like this

<select value="2016">
  <option value="2017" selected="">2017</option>
  <option value="2016" selected="selected">2016</option>
</select>

Note the leftover selected="".

Now here are a few console.log statements that I put in updateAttribute of morphdom

function updateAttribute (newNode, oldNode, name) {
  console.log(newNode)
  console.log(newNode.selected)
  console.log(oldNode)
  console.log(oldNode.selected)

When the user chooses 2016, the log statements evaluate to this:
<option value="2017">2017</option>
true why? :[
<option value="2017" selected="">2017</option>
false Seems okay I guess

and
<option value="2016" selected="selected">2016</option>
false O_o
<option value="2016" selected="selected">2016</option>
false Γ“_Γ²

It's late and it's been confusing, so maybe this is totally expected, but I don't it. The booleans seem off though. My intuition says something is mutating the nodes in between other actions but that doesn't sound right. And that getAttribute and properties are not always the same is known but that it would cause such a bug also sounds odd. Lastly, maybe I am just making a grave mistake here somewhere?

I was actually able to fix this by changing updateAttributes to

function updateAttribute (newNode, oldNode, name) {
  if (newNode.getAttribute(name) !== oldNode.getAttribute(name)) {
    oldNode.setAttribute(name, newNode.getAttribute(name))
    if (newNode.getAttribute(name)) {
      oldNode.setAttribute(name, '')
    } else {
      oldNode.removeAttribute(name, '')
    }
  }
}

Note that I replaced the calls to node.selected with node.getAttribute('selected'). I didn't fork and run tests and I didn't manually check anything else because I really need to sleep now. But please let me know
a) if my bug is known and there's a workaround I couldn't find
b) my "discoveries" are expected behavior and I just don't it

Avoid using .childNodes and .attributes

If possible you should try and avoid using childNodes and attributes. Both are computed functions that are very expensive to use.

For example, rather than using childNode, use firstChild and loop through via nextSibling as DOM nodes are based on a two-way linked list – not a graph/array structure.

Also, I did some tests recently and its far more effective to create a lightweight "vdom" object upon inspecting and calculating what exists in a real DOM node and storing this in a WeakMap based on the DOM node itself being the key. You'll never get greater performance from accessing the "vdom" object vs DOM node properties as they are aren't monomorphic and can't be properly optimised by JavaScript engines. So rather than getting an O(1) from doing dom.nodeType, you're likely to get an O(log n) or worse depending on the JavaScript engine's optimisation path.

Recommended way to cache <img> elements

Hey there! I'm wondering if there are any recommended ways to cache elements. Some of my render loops happen in a requestAnimationFrame(), so I'd like to prevent tons of network requests for the same images.

➿ ➿ ➿

I tried declaring my icons once, outside of my render function. E.g.:

let someIcon = html`<img src="icon.png">`

let renderButton = () => {
  return html`
    <button>${ someIcon } Click me </button>
  `
}

let renderAll = () => {
  morph(oldButton, renderButton())
}

This prevents additional network requests, but it still causes unnecessary changes to the DOM because the cached img node can only live in one tree at a time. So when morph does a comparison, the img exists in one tree and not the other, and it does a replacement.

Now that I'm writing this, maybe what I need is exactly two copies of each image. One for the real DOM, and one for the comparison DOM?

Would love to hear if/how other people have handled this sort of thing!

invert argument order

morphdom has tree, newTree - we got newTree, tree - this is like super confusing; we should probably change

Updating input

Hi Yosh,

sometimes I want to do

var newInput = html`<input></input>`
newInput.value = 42
return newInput

rather than

var newInput = html`<input value=${value}></input>`
return newInput

then diff this thing somewhere else

But since you have this if-chain here https://github.com/yoshuawuyts/nanomorph/blob/32635c37f4272e1e225398e76e540de314a7b701/lib/morph.js#L118

it never really updates my value as written in the first case because I added the property but the attribute doesn't exist (I think?) :(
The way morphdom handles this is it checks these things independently: https://github.com/patrick-steele-idem/morphdom/blob/43b808008bd39b571a1dd32d49811c7c22bd2021/src/specialElHandlers.js#L32

I don't know if this should be considered a bug but it would be nice if my first case would be possible to do as sometimes elements get large and it's visually nice to set some stuff from outside.

Does this make sense? 😁 Sorry for putting more things on your plate, would have just sent PR but you might have had your reasons for doing it like this.

edit by yoshuawuyts syntax highlighting

Do you plan to keep event copying?

Hey!

I am working with morphdom to build a backbone.js view extension. Because all events are discarded each time and I am morphing an element which is many views combined... I have to get a bit creative. I am writing some extra stuff to map all the events defined in each view to a central object to redistribute them after each render.

I was just wondering if you plan to keep that addition?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.