Giter VIP home page Giter VIP logo

diffhtml's Introduction

<±/> diffHTML

Build Status Coverage Status

Latest version: 1.0.0-beta.30

diffHTML is an extremely lightweight and optimized HTML parser and Virtual DOM specifically designed for modern web UIs. These interfaces can be applications, games, data visualizations, or anything that you may want to render in a web browser or Node.

The core package works like a library, where you can import just one function and have a fully reactive VDOM rendering engine. When you opt into more functions and use the companion packages you get a framework for structuring your ideas.

Features

  • Parses real HTML and supports JSX & Tagged Templates.
  • Memory efficient VDOM rendering that utilizes object pooling.
  • Powerful middleware extends diffHTML with additional features.
  • React-like Components which can be rendered as Web Components.
  • A lite build which has a smaller size, meant for optimizing production code.

Works great with legacy and modern browsers, Node.js, Deno, and with whatever your favorite JavaScript runtime is.

Packages

The following list of modules are nested in the /packages folder. They form the foundation of the diffHTML ecosystem.

  • diffhtml

    npm install diffhtml

    The core public API for creating user interfaces. Contains a standard build which includes everything, and a smaller optimized build that excludes the HTML parser and performance metrics, which is useful for those who want to minimize the filesize.

  • diffhtml-components

    npm install diffhtml-components

    Provides constructors and middleware for rendering stateful/stateless components seamlessly. The API will be very familiar to anyone who has used React as the class methods and structure are the same.

  • diffhtml-rust-parser

    npm install diffhtml-rust-parser

    Coming soon

    An alternative parser written in Rust and compiled to WASM, providing a wrapper around the tl HTML parsing library which is then converted into a compatible diffHTML VDOM structure.

  • babel-plugin-transform-diffhtml

    npm install babel-plugin-transform-diffhtml

    Transforms your input into function calls. This eliminates the need for runtime parsing. This is similar to how React compiles down JSX.

  • diffhtml-middleware-linter

    npm install diffhtml-middleware-linter

    This module will run various linting rules on your input to ensure you are writing valid/well-formed HTML. This was inspired by and uses rules from the HTMLHint project.

  • diffhtml-middleware-logger

    npm install diffhtml-middleware-logger

    Logs out diffHTML state from the start and end of every render transaction.

  • diffhtml-middleware-synthetic-events

    npm install diffhtml-middleware-synthetic-events

    Changes the event binding from inline event handlers like onclick = fn to use addEventListener. Events are attached to the body element and coordinated to children through delegation.

  • diffhtml-middleware-service-worker

    npm install diffhtml-middleware-service-worker

    Helps with the creation of a service worker for PWAs, available as a convenience to make development more friendlier.

  • diffhtml-website

    The source for the www.diffhtml.org website.

diffhtml's People

Contributors

cetinsert avatar delano-infodatek avatar dependabot[bot] avatar f avatar gitter-badger avatar gnarf avatar jmas avatar justjake avatar kltan avatar maadhattah avatar odinho avatar rzhornyk avatar tbranyen avatar uzitech 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  avatar  avatar  avatar  avatar

diffhtml's Issues

data-name parameters are lost after patch

When I do :

element.diffHTML = '<div data-name="js-element"></div>';
'<div data-name="js-element"></div>'

element
    .querySelector('div')
    .getAttribute('data-name');
undefined

In results markup of element there is no data-name.

Linting errorz

You may already know this, but atm you're only linting files in the root directory of the project. Linting everything via **/*.js yields a number of errors.

I'm working on resolving the easy ones, like semicolon problemz, but there will be other issues that might require some more thought.

I'm opening this issue as a placeholder for resolving those problems. Don't worry about it now unless you're super curious; I'll compile a more in-depth analysis once I make a PR based on the conversation @ #37 (comment)

Logo help wanted

Just putting it out there if anyone has logo design experience, I'm trying to do something that speaks open and standards, while also looking nice.

I've been thinking about using <±/> as a way to represent a diff and HTML. It's really self describing, but doesn't look all that great on its own. I'd like to see something with more emphasis and intentioned layout.

Reuse the Uint16Array

Since we're already sending the offset position, we could trivially reuse the Uint16Array instead of creating a new one every time. The only time a new one would need to be created is if the newHTML is larger than the current size of the existing Uint16Array.

Re-executing js scripts

Hello @tbranyen ;

Thanks for your great work diffhtml again. I'm using it in AsciidocFX editor. Editing HTML and CSS is ok but I need to re-execute JavaScript codes after they changed. How can we do that with diffhtml ?

THanks

Support key attribute

Reference implementation: https://github.com/tbranyen/diffhtml/pull/62

A problem with the current diffHTML implementation exists when removing items in a list. The patches reconciliation algorithm is naive and fast. It simply checks if the original node length is larger than the new length. That indicates something was removed.

Since knowing which row was removed between...

<div>
  <ul>
    <li>Hello</li>
    <li>Hello</li>
    <li>Hello</li>
  </ul>
</div>

...and...

<div>
  <ul>
    <li>Hello</li>
    <li>Hello</li>
  </ul>
</div>

... is impossible to know with just strings, we simply remove the last item and then reconcile changes over the child set. This could be very ill-performing with large lists that need to shift changes down.

I'd like to propose the very-necessary key attribute as influenced and made mainstream by React. The reasoning being that it would allow smart awareness of element siblings. This will ensure that the correct element is removed when the lists change.

An example VDOM representation of the structure:

{
  "nodeName": "div",
  "nodeValue": null,
  "key": null,
  "attributes": [],

  "childNodes": [{
    "nodeName": "ul",
    "nodeValue": null,
    "key": null,
    "attributes": [],

    "childNodes": [{
      "nodeName": "li",
      "nodeValue": null,
      "key": "unique_1",

      "attributes": [{
        "name": "key",
        "value": "unique_1"
      }], 

      "childNodes": [{
        "nodeName": "#text",
        "nodeValue": "Hello"
      }]
    }, {
      "nodeName": "li",
      "nodeValue": null,
      "key": "unique_2",

      "attributes": [{
        "name": "key",
        "value": "unique_2"
      }], 

      "childNodes": [{
        "nodeName": "#text",
        "nodeValue": "Hello"
      }]
    }, {
      "nodeName": "li",
      "nodeValue": null,
      "key": "unique_3",

      "attributes": [{
        "name": "key",
        "value": "unique_3"
      }], 

      "childNodes": [{
        "nodeName": "#text",
        "nodeValue": "Hello"
      }]
    }]
  }]
}

Without this attribute present, if you wanted to animate out an element when it was removed you'd have to track the index of the removed item and then fetch the reference from the DOM and animate that element. Essentially you need to ignore the passed detached transition element, since it is always going to be the last item.

Example:

function DivComponent(tagName) {
  const element = document.createElement(tagName)
  const state = {
    toBeRemoved: null
  }

  document.addTransitionState('detached', oldEl => {
    // We can't use the provided element since it will always be the last element;
    // so use the element clicked on to be removed. We still want to be sure an
    // li was removed though
    if (!element.contains(oldEl) || !element.matches('li') || !state.toBeRemoved) {
      return
    }

    return state.toBeRemoved.animate(/* Some fancy animation stuff */).then(() => {
      state.toBeRemoved = null
    })
  })

  return function update(props) {
    element.onclick = ev => {
      if (!ev.target.matches('li')) { return }

      state.toBeRemoved = ev.target

      const index = [...ev.target.parentNode.children].indexOf(ev.target)
      const newItems = [...props.items]
      newItems.splice(index, 1)

      update({ items: newItems })
    })

    element.diffInnerHTML = `<ul>
      ${props.items.map(item => `<li>${item}</li>`).join('\n')}
    </ul>`

    return element
  }
}

const props = { items: ['Hello', 'Hello', 'Hello'] }
const update = DivComponent()

document.body.appendChild(update(props))

This isn't ideal, you want to just remove the correct element, why do you need to introduce state for this?

Instead the element provided by the detached transition hook would be correct, so long as you ensure a unique and matching/binding key attribute. This cannot simply be the index value, otherwise you would overlap. Therefore I've introduced an id property to the passed in props.

function DivComponent(tagName) {
  const element = document.createElement(tagName)

  document.addTransitionState('detached', oldEl => {
    // We can now trust that the correct LI will be passed to be removed
    if (!element.contains(oldEl) || !element.matches('li')) {
      return
    }

    return oldEl.animate(/* Some fancy animation stuff */)
  })

  return function update(props) {
    element.onclick = ev => {
      if (!ev.target.matches('li')) { return }

      const index = [...ev.target.parentNode.children].indexOf(ev.target)
      const newItems = [...props.items]
      newItems.splice(index, 1)

      update({ items: newItems })
    })

    element.diffInnerHTML = `<ul>
      ${props.items.map(item => `<li key="${item.id}">${item.text}</li>`).join('\n')}
    </ul>`

    return element
  }
}

const props = {
  items: [{
    id: 0,
    text: 'Hello'
  }, {
    id: 1,
    text: 'Hello'
  }, {
    id: 2,
    text: 'Hello'
  }]
}
const update = DivComponent()

document.body.appendChild(update(props))

Sync issues with Worker

Currently experiencing some issues with syncing to the Worker, will need to write tests that hit all transformations.

Spit out validation errors.

Just hit a major issue where parsing got all messed up when I had:

<h4>Some header</h5>

This is such a common mistake that it'd be great to spit out warnings. While this is inconsistent with innerHTML/outerHTML, this is also an experimental API that may help with best practices around well formed markup.

Updates to gh-pages

You prob. already know this, but the gh-pages site seems a lil outdated. I can update it if you give me a list of changes. It seems like the entire "About" section is out of date as of #10.

Things that need updating:

  • No dependencies on virtual-dom, and, in fact, no dependency on anything
  • Optional prollyfill, rather than that being the default
  • A description of the algorithm currently employed

I'm still diving into the details of the lib, so I don't think I could write out how the current algo works. Anyway, figured I'd raise the issue.

need benchmark .

I have tried a similiar test like diffhtml. But i find that performance is even worse than innerHTML.

What about diffhtml

Barebones Custom Elements API pollyfill

To make this library even more useful, it'd be great to provide a Custom Elements pollyfill that can support the following:

var MyElement = Object.create(HTMLElement.prototype, {
  createdCallback: {
    value: function() {
      console.log('The element instance', this);
    }
  },

  attachedCallback: {
    value: function() {
      console.log('The element is now in the DOM');
    }
  },

  detachedCallback: {
    value: function() {
      console.log('The element has left the DOM');
    }
  },

  attributeChangedCallback: {
    value: function(name, previousValue, value) {
      if (previousValue == null) {
        console.log('New attribute with name: ', name, ' and value: ', value);
      } else if (value == null) {
        console.log('Removed attribute with name: ', name,
          ' with the value: ', previousValue);
      } else {
        console.log('Changed attribute with name: ', name,
          ' with the value: ', previousValue, 'to the value: ', value);
      }
    }
  }
});

document.registerElement('my-element', { prototype: MyElement });

Allow using buffers + extra thoughts

After having a look at React i also thought that something like an 'upsertHTML' would be far better and i'm really happy to see things moving that way.

One reason why i think the diffHTML approach is better is that building a string containing HTML and let browsers make the diff allows to avoid garbage collection cycles.

I think an optimal way to build that string would be to user buffers. Since the HTML contents of an application rarely vary in size, we could avoid garbage collection and have really impressive performances.

Another great thing with ArrayBuffers is that they are "transferable objects" https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)

It means you can:

  • compute the UI into a WebWorker and fill an ArrayBuffer with the new HTML
  • transfer it to the main thread and render changes to the DOM.

Using 2 buffers, you could perform the HTML diff from the first while the next HTML to render would compute in the web worker to fill the second buffer. At each rendering cycle, you would just have to exchange buffers and render/compute again.

That's why i think diffHTML should accept array buffers containing encoded strings.

I assume you used virtual-dom to provide a quick implementation but i think that in the end, an HTML parser combined to a tree walker applying changes on the fly would be a better fit for implementing diffHTML.

Hope it helps.

Track Custom Element rewrite changes

Reference: WICG/webcomponents#405

Since the Custom Element API is still in flux, I've only implemented the API that matches current browsers. I'll be starting on an opt-in to try out the new API. I'm thinking something like diff.enableExperimental() which matches enableProllyfill() except brings in the new APIs and will change with the leading browser implementation.

[Question] Hook to allow for Custom Element rendering

Currently it's not possible for a Custom Element to render itself and maintain markup through re-renders. That does not mean there is no merit in how they are designed. Currently they are very effective at enforcing progressive over existing markup. Although this makes it difficult to align with existing projects like React, Ember, Backbone, and Angular that allow "Component" instances to maintain their own rendering.

Consider the following Custom Element definition:

class MyCustomElement extends HTMLElement {
  attachedCallback() {
    this.render();
  }

  render() {
    this.diffOuterHTML = `
      <my-custom-element>
        <h1>Rendered!</h1>
      </my-custom-element>
    `;
  }
}

document.registerElement('my-custom-element', MyCustomElement);

If this code was executed, anytime a <my-custom-element/> is attached to the DOM, the callback will render an <h1/> inside itself.

Okay so that part is easy to understand and makes sense, but what happens if you're re-rendering the container for this custom-element, most likely your template looks like:

<main role="main">
  <my-custom-element></my-custom-element>
</main>

So the first time this is rendered into the DOM, the <my-custom-element/> is rendered and displays correctly, but the next render will compare the dynamic contents of the Custom Element to the empty container present in the page.

My recommendation is that this remain the default and expected behavior. However, I think we should allow a hook into the Virtual DOM process so that we can emulate the render flow behavior of something like React.

I think it could work as simple as this. If a Custom Element instance is detected, we check if it has a render method. If that method exists, we call it and abort diffing the element. This would mean that if no render method was attached, it would behave exactly as before and empty.

One of the major edge cases I can see right now is that if a Custom Element is using a Web Worker (async render) compared to a container rendering via UI (sync render), that the renderComplete will not be accurate (since the component will still be rendering). So the fix here is basically pushing a new Promise bound to the Custom Element's renderComplete event and always treating it as potentially async.

[Pinned] Roadmaps

The current state of diffHTML is experimental teetering on stable. However, I anticipate some major additions in the future.

Version 1.0 (Current roadmap):

  • More robust testing
    • Including reliability tests, ie: swapping out entire pages with various sites and calculating the accuracy of the changes
      • I've started on this by scraping the Alexa top 500. I will cycle through each and compare the diffHTML generated VTree to a DOMParser result.
  • Plenty of examples and documentation
  • Chrome Dev Tools extension
  • More performance optimizations
    • Like decoding entities via #43
    • Improvements to memory management (specifically pooling and GC) Done!
    • Parsing HTML, it's currently taking up a majority of the average "patchNode" (diff / patch) cycle
  • The API will most likely not change at all. I really don't like the idea of changing things that are
    "good enough" in favor of "eternal consistency".
    • The API is now tenatively locked for 1.0 release!
  • Tagged template string helper that can be used to build complex encapsulated components, will be very similar to the React/JSX declarative/mixed markup style. Done!
  • Will start publishing builds that exclude the parser.
  • Node.js implementation? Since this does not rely on the real DOM at all, make the APIs "global" with an implementation for the server.
    • Decided it's not worth the effort, you can use libraries like stringdom or parse5 to handle this.
  • Lightweight middleware system
  • Mono-repo w/ Lerna.

Version 2.0 (Future roadmap):

  • Hot Module Replacement
  • Web Worker Middleware
  • Babel plugin to compile down the tagged template string into a memoized pre-parsed vtree function that eliminates the need for the parser code in the frontend.
    • Using the diffHTML parser this should be smart enough to track dynamic/static content.
      • Almost there, only issue now is tracing back up the AST to ensure static is truly static

Hardware concurrency

It'd be great to have diffhtml coordinate around navigator.hardwareConcurrency if available. We'll have to experiment if this knowledge of available cores will aid thread management.

Use ES5 in npm package

The bundled dist/diffhtml.js has some problem using in Webpack:

webpack: bundle is now INVALID.
Hash: 206a823a10edb9d87368
Version: webpack 1.13.1
Time: 118ms
                               Asset      Size  Chunks             Chunk Names
                           bundle.js    339 kB       0  [emitted]  main
0.58c44d06e33087563e03.hot-update.js   3.17 kB       0  [emitted]  main
58c44d06e33087563e03.hot-update.json  36 bytes          [emitted]
chunk    {0} bundle.js, 0.58c44d06e33087563e03.hot-update.js (main) 297 kB [rendered]
   [77] ./src/hud.js 3.01 kB {0} [built]
     + 82 hidden modules

WARNING in ./~/diffhtml/dist/diffhtml.js
Critical dependencies:
1:477-484 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
 @ ./~/diffhtml/dist/diffhtml.js 1:477-484
webpack: bundle is now VALID.

Could you release the compiled ES5 to npm please? Adding a gulpfile.js and prepublish will make it.

Support `is` attribute

This attribute allows inheritance in the markup by extending an existing element with a custom element.

Alternative to Custom Elements

An alternative to Custom Elements for managing a component, could be to follow the approach taken by: bel and yo-yo

list.js

const bel = require('bel')
const diffElement = require('diffhtml').element

module.exports = function createList() {
  var root = document.createElement('ul')

  function render(items) {
    diffElement(root, bel`<ul>
      ${items.map(item => bel`<li>${item}</li>`)}
    </ul>`)

    return root
  }

  return data => render(data)
}

index.js

const createList = require('./list')

function makeList(items=[]) {
  // Create a new list "component".
  const updateList = createList()

  // Append it to the DOM after rendering the first time.
  document.body.appendChild(
    updateList(items)
  )

  // Subsequent re-renders.
  return (items=[]) => updateList(items)
}

const items = [
  'Test 0',
  'Test 1',
  'Test 2',
]

const updateList = makeList(items)

// Illustrate updating.
setInterval(() => {
  items.push('Test ' + items.length)
  updateList(items)
}, 1000)

Performance problems around memory locking

In order to avoid accidentally free'ing pooled memory, we lock/protect virtual dom objects. This comes at a cost though since we need to constant check lengths and use indexOf which isn't very fast on super large arrays. Since we're dealing with potentially thousands of objects, we need to figure out a more efficient way to handle object pooling and clean up.

`enableWorker` option does not seem to do anything

I am using enableWorker: true option as per the documentation. It looks like it is not doing anything. Or Am I using it in wrong way?

This is how I am using now:

diff.innerHTML(this.element, div.innerHTML, {enableWorker: true})

Thanks,
Prathap

Implement render buffer

This will be a simple array. [current, next] At the end of the render loop this array will be shifted. This will allow for asynchronous transitions, since we'll be able to effectively block the next render until the transition is complete.

ES2015 updates

Migrating the conversation from #37

tl;dr

@tbranyen was finding it difficult to get some test-related features working with ES2015. Namely, src maps, cvg reports, and browser tests.

I've spent far too much time in the past year thinking about using ES2015 with popular testing libraries. Much of that work exists in a usable form over in this repo: generator-babel-boilerplate

Basically all that I'm doing is porting this lib over to use that boilerplate, or ideas from that boilerplate, with changes where necessary.

I'm opening a separate issue in case I've got more questions, and to keep it separate from that PR, which I've closed.

Todo:

  • Add linting back before test and coverage gulp tasks
  • Update package.json tasks
  • Remove unused devDeps
  • Split test-browser into one that builds app for phantomJS testin' and one for in-browser testin'

Changes

  • Prollyfill no longer patches window to add TransitionStateError. This makes testing harder, most developers think it is bad manners to patch window, and it is unnecessary as TransitionStateError is available under diff.TransitionStateError

[Question] Browser support / Sauce Labs?

I'm curious to know what the target browser support is. I don't see it on the site atm.

Also, could be worthwhile to run the tests in SauceLabs. Whatcha think?

[Question] Usage with template strings

I've been thinking about the following way to create elements:

function el (str) {
  var element = document.createElement('div')
  // ... Do some processing of the template first ...
  diff.outerHTML(element, str.raw[0])
  // ... customize/check the element before returning ...
  return element
}

module.exports = function (r) {
  return el`<svg viewBox="0 0 100 100">
    <circle cx="${r/2}" cy="${r/2}" r="${r}" />
  </svg>`
}

// ... later ...

var circle = require('./circle.js')(50)
document.body.appendChild(circle)

Which won't work because the element doesn't have a parentNode. So I'm about to try and parse out the outer element and then diff.innerHTML(element, innerProcessedStuff) instead. That way I can create elements and insert them later.

But I wanted to get your thoughts on this approach and any advice you might have using with template strings. Thanks for the awesome lib Tim!

License? Forking, etc

Hey, great concept. I'm an environmentalist though, and don't quite like pollution. Is it okay if I fork this into just a function instead of a method on a browser prototype?

Freeing memory off Worker

Currently if a worker is not used, objects and arrays are never free'd. Ensure that when elements are removed that their uuid, childNodes, and attributes are recursively free'd.

We also need to free all parser objects and arrays allocated as well.

Remove UUID

The UUID concept was introduced as a way to tie descriptor (vTree) objects to DOM nodes. Since Web Workers could produce objects as well I thought UUID would be a good bridge. Since I've removed Web Workers, it's entirely possible to simply utilize the object reference itself as a lookup.

This is an internal concept that if removed will keep the lib backwards compatible.

Configure `npm test` to not have git side effects

I cloned the lib, ran npm i, then ran npm test to make sure that the tests passed locally. They did, but it also built dist/diffhtml.js, which git recognized as a change.

I usually prefer to make it so npm test has no side effects, which makes it easier for folks to contribute to the library – they can make changes to the src, run the tests, then PR without needing to undo the change to the build. This assumes that ya don't want people PRing the built file, which is usually something I only do when it's release time.

Typically, I accomplish this by building to a tmp directory for the tests that is gitignored. Lmk if this is something you're interested in, and I can make a PR for it.

Transitions API

I've been thinking about a simplistic API for handling transitions. I think
the following will be sufficient for working with animation libraries, CSS
animations, and vanilla JavaScript.

I think the API should be similar in style to addEventListener and
removeEventListener, as you may want to break up your transition code and not
have one giant function. I think the methods: addTransitionState and
removeTransitionState will be sufficient and obvious.

Element.prototype.addTransitionState requires two arguments, a string state
and a callback function to invoke on every element matching the state. The
valid states are: attached, detached, replaced,
attributeChanged, and textChanged. It should be invoked globally on
the exposed diff object. The callbacks will be invoked whenever the states are
matched from the DOM changes. You may optionally return a Promise to have
diffhtml wait until the transition is complete.

Examples:

Synchronous operation on all elements being attached to the DOM:
// Register the state callback to add a class to any attached element nodes.
document.body.addTransitionState('attached', function(newElement) {
  // Filter to only Element nodes, avoids trying to set a class on a text node.
  if (newElement.nodeType === 1) {
    newElement.classList.add('attached');
  }
});

// Diff in some markup.
document.body.diffInnerHTML = '<p></p>';

// Inspect the classList.
console.log(document.body.querySelector('p').classList.contains('attached'));
// => true
Filtering down to a specific element:

Since the callback is invoked for every single element attached, you may want to
reduce down to only elements that match a specific selector. Luckily the DOM
provides us a method: matches that is relatively well supported although if you want good
backwards compatibility you could filter with jQuery as well (shown below).

// Register the state callback to add a class to specific attached element nodes.
document.body.addTransitionState('attached', function(newElement) {
  // Filter to only paragraph tags using modern DOM API.
  if (newElement.matches('p')) {
    newElement.classList.add('attached');
  }
});

// Diff in some markup.
document.body.diffInnerHTML = '<p></p><span></span>';

// Inspect the span classList.
console.log(document.body.querySelector('span').classList.contains('attached'));
// => false

// Inspect the p classList.
console.log(document.body.querySelector('p').classList.contains('attached'));
// => true
Using a library like jQuery:

Since jQuery has great browser compatibility, it's an excellent choice if you
need to support legacy systems using this API. jQuery also provides some handy
JavaScript animation utilities that can be represented as Promises.

// Register the state callback to add a class to specific attached element nodes.
document.body.addTransitionState('attached', function(newElement) {
  var $newElement = $(newElement);

  // Filter to only paragraph tags using more compatible jQuery.
  if ($newElement.is('p')) {
    $newElement.addClass('attached');
  }
});

// Diff in some markup.
document.body.diffInnerHTML = '<p></p><span></span>';

// Inspect the span classList.
console.log(document.body.querySelector('span').classList.contains('attached'));
// => false

// Inspect the p classList.
console.log(document.body.querySelector('p').classList.contains('attached'));
// => true

Since jQuery can do animations and represent them as Promises, we can do
asynchronous animations simply:

// Register the state callback to fade in all paragraph tags.
document.body.addTransitionState('attached', function(newElement) {
  var $newElement = $(newElement);

  // Filter to only paragraph tags using more compatible jQuery.
  if ($newElement.is('p')) {
    return $newElement.fadeIn().promise();
  }
});

// Diff in some markup.
document.body.diffInnerHTML = '<p></p><span></span>';

// => p will fade in and then diffhtml will complete the render cycle

Now this works great, but what if you remove the element during the animation
process? Chances are you want to stop the current animation and then fadeOut.
This is where the detached comes in handy.

To rewrite the above example with a clean stop and resume, you could do:

// Register the attached state callback to fade in any paragrah tags.
document.body.addTransitionState('attached', function(newElement) {
  var $newElement = $(newElement);

  // Filter to only paragraph tags using more compatible jQuery.
  if ($newElement.is('p')) {
    $newElement.stop().fadeIn();
  }
});

// Register the detached state callback to fade out any paragrah tags.
document.body.addTransitionState('detached', function(newElement) {
  var $newElement = $(newElement);

  // Filter to only paragraph tags using more compatible jQuery.
  if ($newElement.is('p')) {
    $newElement.stop().fadeOut();
  }
});

// Diff in some markup.
document.body.diffInnerHTML = '<p></p><span></span>';

// Wait a bit, but before animation completes.
setTimeout(function() {
  document.body.diffInnerHTML = '<span></span>';
}, 90);

// => p will fade in and then back out as diffhtml adds and removes the
// element.

Support HMR

It should be possible to make the components HMR compatible. I'd image this would be a separate module that could be mixed into any valid ES6 class.

Lightweight middleware

Middleware has several advantages including expanding diffHTML to utilize tooling. I'm going to use nested functions to access more internals and progress further down into the state.

For instance to write a logger that got the start and end time and

const renderLog = (message, method, color, date, args) => {
  const { node, oldTree, newTree, patches, promises } = args;

  console[method](message, color, date.toLocaleString(), date.getMilliseconds() + 'ms');

  console.log('%cnode %O', 'font-weight: bold; color: #333', node);
  console.log('%coldTree %O newTree %O', 'font-weight: bold; color: #333', oldTree, newTree);
  console.log('%cpatches %O', 'font-weight: bold; color: #333', patches);

  if (promises.length) {
    console.log('%ctransition promises %O', 'font-weight: bold; color: #333', promises);
  }

  console.groupEnd();
};

const logger = start => (args) => {
  renderLog(
    '%c∇ diffHTML render transaction started',
    'group',
    'color: #FF0066',
    start,
    args
  );

  return end => renderLog(
    '%c∆ diffHTML render transaction ended  ',
    'groupCollapsed',
    'color: #0095ff',
    end,
    args
  );
};

diff.use(logger);

Which then looks like:
middleware

Can we remove UUID since Worker code is removed?

In the spirit of trimming down as much as possible in here, I think the UUID logic isn't necessary anymore. We retain descriptor objects in memory so a WeakMap would work perfectly to align descriptors with real elements.

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.