You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is pretty much a complete rewrite of the implementation aimed at solving three specific issues with nanohtml:
Performance – rebuilding the complete DOM tree on every update is a significant overhead
Components – current implementations come with shortcomings like leaking proxy nodes
Asynchronous render – authoring asynchronous behaviour, especially in SSR, is a hassle
This is not a finished implementation, but I wish to get a discussion going and would like to get feedback on this from the community. Included in the PR is a sample implementation of a component interface as well as a wrapper for lazy (async) components with fallback – these are in a state of experimentation.
Nanohtml is commonly used with nanomorph. Though the DOM isn't necessarily slow, rebuilding the complete DOM tree on each update and diffing + morphing that tree does impose a significant performance overhead.
This implementation is based on an implementation by @goto-bus-stop (https://gist.github.com/goto-bus-stop/5b54d652af860f614a1dcba28eb80691). In short, it identifies which parts of the template might change and creates bindings for updating these parts. The bindings are stored in a WeakMap with the element as key, meaning they can be garbage collected when the element is removed. Templates are only parsed once and cached with the template itself as key – thanks to the neat feature of template literals that the template array is unique per template, not per invocation.
This approach has resulted in a ~11x performance improvement over nanohtml + nanomorph.
Components
The most popular implementation of components for nanohtml is nanocomponent. The interplay between nanomnorph and nanocomponent sometimes results in leaking proxy nodes and a complex and flawed diffing algorithm. Nanocomponent in itself comes with a sometimes overly verbose API and demands quite complex implementations to determine if the component should update. Authors also have to manually manage component instances.
This implementation does not completely replace nanocomponent, but it offers an API for doing so. By extending the built in Partial class (requires implementing the render and update methods) one can create any kind of component interface. I have taken a stab at it with nanohtml/component. A few examples of use can be seen in this gist.
See an example implementation of a stateful component
As part of choo the most prominent behaviour for async render to emit an event, render some loading state and once data is available, issue another render. In my experience, this works fine for consumer websites and simple web apps, but as complexity increases this has proven quite the bottleneck. The issue is most prominent when you have nested async dependencies, e.g. fetch list of things and then fetch item from list. This problem is especially troublesome when doing SSR. One solution, used by bankai is to perform a double render pass, once to collect promises and once again to render with resolved data. The API for this isn't really ideal and I find that it is prone to race condition, especially when having nested async dependencies.
This implementation addresses promises in tandem with generators. This is so that a component can be both asynchronous and synchronous, exposing promises when the data is not cached, but being rendered synchronously once it is.
The server side implementation unwinds generators to find promises and returns a promise which resolves to the rendered HTML string – pretty much what @diffcunha proposes in choojs/choo#646.
I've started work on a way to render fallback content while a promise is being resolved but need to work out how to handle race conditions which easily arise when content changes while awaiting promise resolution, see nanohtml/lazy and accompanying tests.
Other benefits
Async render can easily be used to make lazy routes a core functionality of choo to fetch views as they are needed.
The fact that state and bindings are attached to the DOM element enables a lot of interesting possibilities for debugging tools which can inspect and manipulate any elements internal state, even in production.
Trade-offs
File size is at ~6kb, making it hard to keep the 4kb promise of choo. Though about 30% of that is hyperx which can be dropped as soon as we migrate the transforms.
It's all in one big file. I tried splitting it up using an OOP approach but quickly ran into circular dependency problems. Having it in one big file enables extensive scoping, which is nice. I tried commenting as best I could.
Irregular return values – returns promises when top level partial is async, otherwise returns DOM. Same goes for SSR, returns string or promise.
I haven't had time to look at this closely and won't in the near/not-so-near future :( IMO, if you've been using it successfully in projects already, you should feel free to merge this PR.
No problem @goto-bus-stop. Actually, I'll go ahead and close this. I have a way less bulky version in the works but I'm hesitant to wether this is a reasonable evolution of nanohtml. There are so many breaking changes that an entirely new module almost makes more sense, also, I'm seeing great benefit in merging framework code (state handling, events, i.e. choo) with the view engine (nanohtml/nanomorph). On top of that I think the logical next move is to migrate over to es modules to achieve bundle-less development (see snowpack). All this put together means choo/nanohtml would be barely recognisable, which is why I'm considering a new module.
If development on choo has ceased or if there is wide support in the community for this kind of progression I'd be happy continue here, but it doesn't feel right to just merge such breaking changes without broad consensus in the community.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is pretty much a complete rewrite of the implementation aimed at solving three specific issues with nanohtml:
This is not a finished implementation, but I wish to get a discussion going and would like to get feedback on this from the community. Included in the PR is a sample implementation of a component interface as well as a wrapper for lazy (async) components with fallback – these are in a state of experimentation.
API
Performance
Nanohtml is commonly used with nanomorph. Though the DOM isn't necessarily slow, rebuilding the complete DOM tree on each update and diffing + morphing that tree does impose a significant performance overhead.
This implementation is based on an implementation by @goto-bus-stop (https://gist.github.com/goto-bus-stop/5b54d652af860f614a1dcba28eb80691). In short, it identifies which parts of the template might change and creates bindings for updating these parts. The bindings are stored in a
WeakMapwith the element as key, meaning they can be garbage collected when the element is removed. Templates are only parsed once and cached with the template itself as key – thanks to the neat feature of template literals that the template array is unique per template, not per invocation.This approach has resulted in a ~11x performance improvement over nanohtml + nanomorph.
Components
The most popular implementation of components for nanohtml is nanocomponent. The interplay between nanomnorph and nanocomponent sometimes results in leaking proxy nodes and a complex and flawed diffing algorithm. Nanocomponent in itself comes with a sometimes overly verbose API and demands quite complex implementations to determine if the component should update. Authors also have to manually manage component instances.
This implementation does not completely replace nanocomponent, but it offers an API for doing so. By extending the built in
Partialclass (requires implementing therenderandupdatemethods) one can create any kind of component interface. I have taken a stab at it with nanohtml/component. A few examples of use can be seen in this gist.See an example implementation of a stateful component
Asynchronous render
As part of choo the most prominent behaviour for async render to emit an event, render some loading state and once data is available, issue another render. In my experience, this works fine for consumer websites and simple web apps, but as complexity increases this has proven quite the bottleneck. The issue is most prominent when you have nested async dependencies, e.g. fetch list of things and then fetch item from list. This problem is especially troublesome when doing SSR. One solution, used by bankai is to perform a double render pass, once to collect promises and once again to render with resolved data. The API for this isn't really ideal and I find that it is prone to race condition, especially when having nested async dependencies.
This implementation addresses promises in tandem with generators. This is so that a component can be both asynchronous and synchronous, exposing promises when the data is not cached, but being rendered synchronously once it is.
The server side implementation unwinds generators to find promises and returns a promise which resolves to the rendered HTML string – pretty much what @diffcunha proposes in choojs/choo#646.
I've started work on a way to render fallback content while a promise is being resolved but need to work out how to handle race conditions which easily arise when content changes while awaiting promise resolution, see nanohtml/lazy and accompanying tests.
Other benefits
Trade-offs