google / incremental-dom Goto Github PK
View Code? Open in Web Editor NEWAn in-place DOM diffing library
Home Page: http://google.github.io/incremental-dom/
License: Apache License 2.0
An in-place DOM diffing library
Home Page: http://google.github.io/incremental-dom/
License: Apache License 2.0
One of the perks of React is the ability to render its virtual dom into strings on the server. It'd be nice to have the same capabilities.
Do you think it'd be worth while to maintain string element*
that could be required on the server?
// string.js
var elements = require('virtual_elements'),
elementVoid = elements.elementVoid,
elementOpenStart = elements.elementOpenStart,
elementOpenEnd = elements.elementOpenEnd,
attr = elements.attr;
var patch = function(node, fn) {
// Will have to consider how to translate this.
// For now, just run the patch function
fn();
};
var elementOpen = function(tag, key, statics, ...attrs) {
// combine statics and attrs
// translate combined into strings `${name}="${value}`
return `<${tag} ${combined}>`;
};
// Void tags aren't gonna like this, but psuedocode. :smile:
var elementClose = function(tag) {
return `</${tag}>`;
}
var text = function(value) {
return value;
};
module.exports = {
patch: patch,
elementVoid: elements.elementVoid,
elementOpenStart: elements.elementOpenStart,
elementOpenEnd: elements.elementOpenEnd,
elementOpen: elementOpen,
elementClose: elementClose,
text: text,
attr: elements.attr
};
currentElement
cannot be called inside virtual element (elementOpenStart
and elementOpenEnd
)elementOpenEnd
tag must match elementOpenStart
tagIt's been the Achille's heel of templating/vdom/web components for some time, but SVG elements are in a different namespace than HTML. There are many issues that arise from this. I'm unfamiliar with this implementation, but I didn't see anything about this and I wanted to put it on your radar early. Data visualization is a hot use-case for DOM rendering, but for that, first-class support of SVG is a must.
Are you accounting for other namespaces in your design? If so, how?
This seems like a very interesting project. I'd be particularly interested in how this relates to Facebook's React. Is this intended to be a competitor project or are there any severe design differences?
Maybe it would be helpful for other people to briefly compare both projects in the README
since React is very common.
Thanks!
I love this project, as me many will do as articles and repositories will grow talking about and using it.
I was thinking it would be good to maintain a separate markdown file with a list of libraries, blog posts, benchmarks to help people get oriented and started.
I've been publishing one template library recently on github depending on idom (which already has a project in production), plus some experiments and have another project to come.
Was thinking to ask to have my little library added to the readme.md among the others for discoverability, but realized the number of projects around is going to grow soon and the blog posts and tutorials are as much valuable to new comers.
So probably best take is to move everything to a separate markdown file and link it from the main readme.
Also I was wondering if their is any "official" recommendation about how to call the projects, sort of a shared prefix or suffix to help people figure out a first sight idom is in the game. Since now I have been prefixing my projects "incremental-dom-something" but realize maybe this should be reserved to the idom team efforts to be discovered on npm, and we can come up with a separate ecosystem prefix, something like "idom-".
What do you think? And anyway thanks for this stimulating project.
I see a whole lot of changes have landed since the last release, but I can't tell what state master is in. Are there plans for a new release soon, or some kind of idea what needs to be done for one?
Thanks!
Cross posting from HTMLBars: tildeio/htmlbars#380
We suffer from the same issue. Setting the type of an input that is already a part of any DOM tree will cause an error in IE < 9.
patch(container, function() {
elementVoid('input', null, null, 'type', 'text');
});
We're safe using statics
, since the node won't have been inserted anywhere yet โ statics
are set during the creation and before alignment
can insert it.
patch(container, function() {
elementVoid('input', null, ['type', 'text']);
});
Consider the following failing test case...
IncrementalDOM.patch(document.body, function(){
IncrementalDOM.elementVoid('input', null, null, 'value', 'initial');
});
// Simulates typing some words in the <input>... (same results if you manually type in the input)
document.querySelector('input').value = "NOT RIGHT";
IncrementalDOM.patch(document.body, function(){
IncrementalDOM.elementVoid('input', null, null, 'value', 'Hello World');
});
Input text is still "NOT RIGHT" and not "Hello World"
Currently, IncrementalDOM decides how to assign an attribute based on the value's type.
Function and Objects are assigned as properties (ex. node.onclick = function(){}
). Everything
else is assigned with setAttribute()
/removeAttribute
. Docs
Unfortunately, setting an <input>
's value
(or checked
) must be set as a property after the user
types in the input.
Currently, I'm working around the issue by wrapping the attribute's value with new String()
...
IncrementalDOM.elementVoid('input', null, ['type', 'text'], 'value', new String('Hello World'));
... this works as typeof (new String('Hello World')) === 'object'
.
Not sure if a change is needed or just requires docs to note a workaround.
Here's the requirebin's for the above testcase and workaround:
http://requirebin.com/?gist=100e687ddffd8de6e59c
http://requirebin.com/?gist=7c30ebcf7613c5afc947
Hi,
Can we get a link to superviews.js, a simple template engine that targets incremental-dom.
It's an early PoC at the moment. It has a syntax that closely maps to incremental-dom, allowing it to make full use of the features in its API.
Happy to issue a PR for this.
Any tips/contributions welcome.
Cheers
Dave
Any plans or idea on adding support for asynchronous rendering?
For example, allowing the following example to work.
elementOpen('div', '', null);
setTimeout(function() {
text('hi');
elementClose('div');
}, 1000);
This has been flagged by others but given the focus on size is it be worth reducing the licence header to one line or something similar to ember or rsvp.js
For example would be more acceptable?
/**!
* @copyright 2015 The Incremental DOM Authors. All Rights Reserved
* @licence Licensed under Apache License, Version 2.0
* https://github.com/google/incremental-dom/blob/master/LICENSE
*/
Running Chrome with the following command gives detailed information on which functions it is and is not inlining.
google-chrome --js-flags="--trace-inlining"
The output may be useful to see which functions are and are not getting inlined.
@jridgewell Looking into this might help with evaluating #121
ie_open and friends are somewhat confusing.
Apps can, of couse choose to alias to that, but I'd prefer just
element_open(
element_close(
text(
How many more changes are you planning before cutting a new tagged release? Would also be worth moving iDOM.mutators.attributes
up to iDOM.attributes
comment #67.
I'm trying to integrate iDOM into a bigger es6 codebase that uses the closure compiler directly (ie without any browserfy crud).
Normally I would use something like /** @define {boolean} */ var ENABLE_DEBUG = true;
and --define='ENABLE_DEBUG=true'
as descibed here.
But its not clear to me how I get the same thing with process.env.NODE_ENV
.
So as ES2015 (prev. ES6) was standardized, and we have such great utility like Babel, maybe its good to rewrite the project to ES2015? I can send initial pr.
0.2.0 from npm seems to expose only elementPlaceholder but no skip method, version 0.2.0 here on git has skip
which is to be considered the latest?
if it's the one here on git how to declare it as an npm dependency?
You stated that you are implementing incremental-dom as a backend for Closure Templates .
I didn't find anything incremental-dom specific there, can you please point where exactly I could monitor the progress of your efforts?
Setting innerHTML
and textContent
for an Element should be supported. To support this the following needs to be done:
setAttribute
/removeAttribute
for primitive (non object/function) values, check if it should be set using the property insteadinnerHTML
or textContent
set, do not sweep out children on exitelementOpen
, etc. are not called inside an Element with innerHTML
or textContent
textContent
= ''
, allowing for Elements to act as insertion points for non-library managed DOMCompiling template syntaxes that allow users to specify whether an attribute or property is to be set, like Polymer or Angular 2, requires wrapping all primitive values in objects to force property setting. It'd be a lot friendlier of an API to just accept attributes and properties separately in elementOpen
.
Currently, Incremental DOM avoids considering things like what should be set as attributes/props based on attribute name and instead allows a library building on top of Incremental DOM to decide.
It is probably useful to create an Incremental DOM "core" that provides the core functionality and hooks, with a more complete version implementing some of the nice to have sort of things.
Suggestions / ideas for value added things are definitely welcome.
TLDR;
We might want to break skip
into three functions:
prepend
append
skip
(yup)And we have an issue with keyed elements and skip
, leaving an invalid keyMap
with references to zombie children nodes that are no longer in the tree.
To start with, I'm curious how you see skip
being used. It actually solves a problem with the Glimmer render I'm working on.
The way I see this working is like so:
function render(data) {
return <div>
<div id={data.id} />
</div>;
};
// - - -
// Translates to something like
function render(data) {
var elements = render.elements = [];
elements.push(elementOpen("div"));
elementVoid("div", null, null, "id", data.id);
elementClose('div');
return elements[0];
}
// A more efficient rerender function, since we don't need to walk the _entire_ tree
render.secondRender = function(data) {
patch(render.elements[0], (data) => {
elementVoid("div", null, null, "id", data.id);
}, data);
// Use skip to prevent clearing from outer patch context,
// since `secondRender` was called like so:
// patch(container, render.secondRender, data)
skip();
return render.elements[0];
};
patch(container, render, data);
// Later, repatch
patch(container, render.secondRender, data);
Specifically, I imagine opening an element, skipping, and then immediately closing it โ no children. The children route is an option, but there are issues. Obviously we don't want to clear children, but means we could be creating a whole slew of new elements without ever garbage collecting the old:
function render(data) {
elementVoid('div', data.key);
skip();
}
patch(container, render, { key: i++ });
patch(container, render, { key: i++ });
That could be useful for prepending to lists... ๐. But actually, that example won't work because of a niggle in our alignment algorithm. We'll replaceChild
the old keyed element (because it has a key), and it'll just be left sitting in the keyMap
, even though it's no longer in the DOM (and keyMapValid === false
).
Instead, we might want to break skip
into three functions:
prepend
- Useful for my hypothetical case above
elementOpen
replaceChild
alignmentappend
- Useful for the opposite case, appending to the list
elementOpen
currentNode
to null
and previousNode
to currentParent.lastChild
skip
- Useful for my Glimmer case
elementOpen
I realise this might be a contentious suggestion but I'm finding I end up remapping the element*
to shorter names to make raw idom read better.
import {
text,
patch,
elementOpen,
elementClose,
elementVoid
} from 'idom/index';
var open = elementOpen, close = elementClose, tag = elementVoid;
Given that idom is focused on the dom would you be open to a breaking change and dropping the element
?
This is partly based on using broccoli-jsx2idom for the bulk of ui and then dropping down to raw idom when jsx does not descibe the component I'm trying to render.
This should also help reduce the size of js UI's that don't mangle function calls.
Are there any plans to support attribute maps, rather than serial arguments? This seems a lot easier to use to me:
elementOpen('li', {'class': 'foo', 'data-bar': 'baz'});
than:
elementOpen('li', 'class', 'foo', 'data-bar', 'baz');
The former is also a lot easier to work with programmatically. Passing an arbitrarily long list of attributes shouldn't involve having to construct a call like this:
var args = Object.keys(attrs).reduce(function(attr, list) {
list.push(attr, attrs[attr]);
return list;
}, []);
elementOpen.apply(null, ['li'].concat(args));
I haven't fully grokked how the statics array feature relates to this, but that seems even more obtuse, at least syntax-wise.
Update docs
Create a changelist
Publish to NPM
Lets get us in here http://vdom-benchmark.github.io/vdom-benchmark/
@sparhami I know you have one. Would be great to get it committed into a sub directory.
Add docs for the change introduced in #58
When trying to use the latest incremental-dom
version through npm
, by adding the repository URL into the package.json
file, nothing is downloaded, apart from the LICENSE
and AUTHORS
file. I think this happens because of the files
key defined in the package.json
file.
However, if the project where you want to use it uses ES6 classes, and has already a process for building, I think it makes much more sense to be able to directly link into the source files, rather than using the compiled result (which of course is not present on the repo either).
Would it be worth then for that to add the src
key into the array; or is it better to point to it as a git submodule?
I encountered some unexpected behaviour whilst using conditional logic.
This could be a bug or more likely my misunderstanding the difference between statics and dynamics properties.
Consider the following:
var patch = IncrementalDOM.patch
var text = IncrementalDOM.text
var elementOpen = IncrementalDOM.elementOpen
var elementClose = IncrementalDOM.elementClose
function description (data) {
if (data.show) {
elementOpen('a', null, ['class', 'show', 'href', '/foo'])
text('Show')
elementClose('a')
} else {
elementOpen('a', null, ['class', 'hide', 'href', '/bar'])
text('Hide')
elementClose('a')
}
}
var data = {
show: false
}
function render () {
data.show = !data.show
patch(document.body, description, data)
}
render()
I expected the a
tags attributes to change when toggling the value of data.show
between true/false.
This doesn't happen. The inner text does however change between 'Show' and 'Hide'.
Promoting the class
and href
attributes from statics to dynamic properties does yield the expected behaviour but my understanding was that this shouldn't be required in this case.
Dave
Are there any plans to support custom elements that inherit from native elements, e.g. <input is="custom-input" />
(in this case, the element must be created with doc.createElement('input', 'custom-input')
instead of doc.createElement('input')
)?
There are currently two known IE8 bugs:
Currently, supporting IE8 seems to only require modest code changes, though it may have additional impacts on future development.
For reference, Polymer and Angular 2.0 are targeting IE10+, Ember 2.0 targeting IE9 and React targeting IE8, though some polyfills are required.
According to the documentation, this code should work:
patch(document.body, function() {
var p = elementOpen('p');
patch(p, function() {
elementVoid('span');
});
elementClose();
});
And the result should be:
<body>
<p>
<span></span>
</p>
</body>
But the <span>
node is not there. Apparently the problem is with this line. I've tried to remove it, but some tests are failing then (however, based on the name they don't seem to be releated). I will investigate a bit more, but if you have any ideas on why this was added it would be nice :).
Currently, Incremental DOM does not have any good performance benchmarks to base changes on. Ideally they would be able to run as a part of the build process as well as standalone. In addition to testing for something like average time per pass, it should also check for the maximum time per pass to make sure GC is not kicking in.
This is fairly difficult as it is hard to model how the rest of an application generates garbage during updates. While the library itself may never generate enough garbage to cause more than an young gen GC, any allocation may end up ultimately causing old-gen GC which is hard to isolate and reproduce in a test, especially as heap sizes are likely to vary between environments. Since young gen GC can be very fast, it can be difficult to notice GC occurring with the amount of noise in a test.
Additionally, a spike does not necessarily indicate a GC occurred, but could happen due to the process being scheduled off which is likely to occur if running many iterations and hogging the CPU. This is not likely to occur in actual usage while updating web pages, but is something to watch out for when creating tests
Ultimately, any sort of environment we are likely to test on will have a huge amount of RAM compared to a smartphone in a developing country, which may have as little as 256MB and much of that taken by the OS.
Hi,
I was trying the Custom Element demo, and I noticed the lack of browser support for the Web Components part.
Is that something that will ever be included in incremental-dom
? If not, it would probably make more sense to use a library like polymer
in the demo?
Just a place to aggregate things that need to be thought over:
matches
during alignment) #152 (comment)Often, you see something like:
text(formatFn(someText))
If the formatFn
is a pure function, then we could add a helper that allows calling the formatting function to only be called when someText
. Maybe text
could take a series of formatting functions to apply if the text has changed as variable arguments.
It may also be useful for attributes, support for which could be added through the attr
function.
Currently creating a new subtree can be a somewhat slow due to the fact that the general diffing pass is used. This forces allocation of a few objects/arrays that are unneccessary when everything is a change. This should be transparent to the caller (meaning they should just be able to call patch
and not care about whether the subtree exists or not).
There are two ways I have experimented with doing this in the past:
firstChild
firstChild
With the current code base, the latter ends up being a bit cleaner and makes later changes easier. It also seems to be a good approach as a patch
operation corresponds to a component and if things are broken down into components, there likely is no benefit to doing it on a per node basis.
There also may be some performance benefit to not switching to a creation mode for trivially sized subrees.
Hi, do you have any plans for animation support (for node removal/insertion)? Some methods which can be used for such purpose? Or maybe you have some ideas how to animate nodes removal during the patch?
If a DocumentFragment
is being patched and currentElement
is called at the root, then it will actually return a Node and not an Element.
I'm not sure if an assert should be added or if it should be currentNode
instead. It doesn't seem like a good idea to expose the node being patched from inside, so an assert making sure that it isn't called at the root might be a good idea.
https://github.com/google/incremental-dom/blob/master/src/attributes.js#L86
Sorry for the poor quality of this issue, I'm on my phone.
Basically, SVGElement has an SVGAnimationString type (or some such name) in className
, so you'll either have to do type checking then set .className.baseVal = x
if it's an SVGElement, or use classList
, or use setAttribute("class", x)
There aren't many more issues like this with SVG.
Forgive me posting here - I'm sure it's not the right forum. Please let me know where might be a good place to ask and I'll get it moved.
I wonder if anyone could offer their opinion on a how I should define static attributes values in the superviews.js template language I'm building.
Regular properties use single curly braces {} to define values.
<div class="{data.className}"></div>
I needed to distinguish statics and had settled upon {=} . This works ok for referencing simple values as in the first example in the list below, but I thought it didn't look great in the case where an assignment is made like the 6th example. This will define a static handler function against the onchange
event. I thought having two equal signs looked a bit naff.
I've come up with some alternatives in the hope someone could offer some opinion/advice. :)
I've batted this round my head for a day or two but I'm not convinced by any of the options really, maybe I'm over thinking it? Aesthetically I like the greater than sign, semantically the hash sign given it has no special meaning in javascript (but html it does of course :( I have just realised).
<div title="{=data.title}"></div>
<div title="={data.title}"></div>
<div title="{>data.title}"></div>
<div title="{#data.title}"></div>
<div title="{{data.title}}"></div>
<input type="text" value="{data.val}" onchange="{=data.val = this.value}">
<input type="text" value="{data.val}" onchange="={data.val = this.value}">
<input type="text" value="{data.val}" onchange="{>data.val = this.value}">
<input type="text" value="{data.val}" onchange="{#data.val = this.value}">
<input type="text" value="{data.val}" onchange="{{data.val = this.value}}">
When dynamically evaluating templates to incremental dom you don't statically know the attributes to set, so you must call elementOpen
via Function.apply
. It would be more convenient to have elementOpen
accept arrays of attribute and property name/value lists.
It's great that this library is so small, but a big portion of the dist file is actually related to browserify, stuff like this:
}, {
"./alignment": 2,
"./attributes": 3,
"./node_data": 4,
"./traversal": 7,
"./walker": 10
}],
10: [function(t, e, n) {
This is only necessary because of the require
syntax. If you guys switch to ES2015 (ES6) modules you can use a tool like esperanto to make your dist file significantly smaller.
For an example setup of this, check out babel-library-boilerplate
I can make a PR with this if you'd like?
What is the correct way to detect if nodes are being or have been unmounted?
As example would would be the best way to call onClickCleanup()
in the following contrived code.
patch( document.body, Render, 1)
patch( document.body, Render, 2)
function Render(id) {
var el = elementVoid('input', id, ['type', 'button', 'value', 'click me'])
var onClickCleanup = listener(el, 'click', function(evt) {
alert('clicked', evt.target)
})
// how to run onClickCleanup() on removal?
}
function listener(el, type, fn) {
if (el.addEventListener) {
el.addEventListener(type, fn, false)
return function() { el.removeEventListener(type, fn, false) }
} else if (el.detachEvent) {
el.attachEvent('on' + type, fn)
return function() { el.detachEvent('on' + type, fn) }
}
}
Currently the build doesn't do anything to make sure the type annotations are correct. Building with closure-compiler during the dist
target would help make sure that the types stay correct across changes.
Since there's no dist
preinstall we can't just use rawgithub is there a way we can get a cdn version of incremental-dom up so I can use it jsbin / jsfiddle?
@sparhami Did you benchmark switching from using nextSibling, etc. to caching those in a node's backing data, so that no DOM operations would be involved in traversal?
I just finished implementation of Incremental DOM for JSX.
Repository is here: https://github.com/jsx-ir/jsx-to-idom
More details here: https://medium.com/@nekrtemplar/jsx-everywhere-a6ee99290e0d
hi, we want to add your lib on https://cdnjs.com with npm package auto-update.
because incremental-dom-cjs.js
doesn't have min file, so we want to confirm if it is for browser.
thank you very much!
cdnjs/cdnjs#5823 (comment)
cdnjs/cdnjs#5729
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.