dyo / dyo

Dyo is a JavaScript library for building user interfaces.

Home Page:https://dyo.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Roadmap

thysultan opened this issue · comments

The next major version of DIO will present a good opportunity to improve the ergonomics of some of the API's for a first-class concurrent library.

This is similar to some of the changes that React is going through, and though DIO has had a close mapping of API's to React, future versions might not strictly adhere to this, though would for the most part provide a React-like interface in the broader sense of the word.

The sub-contents of this issue will detail information related to, and in the following order.

  • Scheduling.
  • Lifecycles.
  • API.
  • Scoped package.
  • Server Rendering.

Scheduling

This will be the first major version that introduces scheduling as a first-class concept. At the moment of this writing the semantics of this are still in flux but the underlying concept is rooted in:

  1. Making everything low priority with a special exemption for events.
  2. Cooperatively schedule all low-priority work.

The grunt of which will open up many doors to patterns that can be leveraged to better build user interfaces.

Dan Abramov from React talks a bit about some of the things this would afford React in his talk Beyond React 16 by Dan Abramov - JSConf Iceland.

In a similar vein the same would be afforded to DIO with these changes. The following pull request has some of these in effect.

Lifecycles

getDerivedState

An example of this is the breaking changes to lifecycle methods. React 16 introduced getDerivedStateFromProps, with the intention of future removal of componentWillUpdate, componentWillMount lifecycle methods. DIO will follow in a similar suit with a variety of different "details".

For example instead of getDerivedStateFromProps the method is better suited to the name getDerivedState, the method would be invoked in place of componentWillUpdate and componentWillMount and like getDerivedStateFromProps can be used to update the state of a component, though unlike React getDerivedState is intended as an instance method instead of a static method, this having the benefit of allowing access to this and previous props through this.props, the benefits of which overshadows the gains to be had with the direction that React has taken with a static method.

getInitialState, componentWillMount and componentWillUpdate, and componentWillRecieveProps will all be removed/replaced in favor of getDerivedState.

componentDidCatch

The main difference that will occur with this lifecycle is the compatibility with React in regards to the behavior of unmount-ing a mounted node on a raised error. Given that the API already allows us to explicitly do this and more, the implicit forced unmount is not the best ergonomics for the API and works against the grain of being able to implement suspense-like features outside of the internals of the library without being destructive to the last valid state of the interface.

API

unmountComponetAtNode

API's like unmountComponetAtNode have always been convenient React compatibility API's that could be archived with rendering null i.e render(null). The intention is to remove this API surface.

findDOMNode

In a similar fashion the findDOMNode API is better suited to the explicit use of refs especially with the advent of Fragments that introduce undefined behavior with this API given that fragments don't have DOM representations of their own, resulting in undefined behavior on components that returns a fragments/arrays.

Scoped package

If you've ever installed DIO chances are you might have wonder why you had to type dio.js instead of dio. The crust of the issue is better expanded by NPM with the following blog post - Solving npm’s hard problem: naming packages.

The intention is to transition to a scoped package, this inadvertently means a change to the name of the package. Since the name dio is already taken the closest i could get to the name DIO was DYO which has the same sounding syllables.

With that in mind it might also be a good to time to publish the different renderers separated into the following:

  • npm/dyo(canonical default renderer symlinked to dom renderer)
  • npm/@dyo/dom(dom renderer)
  • npm/@dyo/server(server renderer)
  • npm/@dyo/dyo(sources for custom renderers)

Server Rendering.

With all the mentioned changes the server rendering will also go through some significant changes the meat of which is the introduction of a double buffer renderer that renderers directly to a stream. We can either go the route of having a two pass renderer or a renderer that can interject previous payloads with the use of self-destructive <script> chunks. The intention with this is to support anything async specific that the main reconciler might support.

If possible it would be nice to have the server render be generic enough to render to any stream, i.e in the future a deno stream or a browser stream... etc, and not be explicitly locked to node streams.

Scoped package

against name change, I think dio.js is fine

Server Rendering

(this deserves it's own discussion thread)
Recently I integrated GraphQL for data queries, which (usually) means I can make a single API query to render a page.
For any performance gain, you'd have to stream the page before the API responds, which is usually not possible due to the <head> element depending on the response (SEO, code-splitting).

  • e.g. <title>John Doe's profile | 123 likes</title>
  • (perhaps you could stream the GraphQL response... maybe)

Therefore I think solutions like self-destructive <script> may not add much value (as opposed to blocking).

Reference code for how Zorium handles server-side rendering with dio.js:

app.use (req, res, next) ->
  model = new Model {server: {req, res, productionResources}}
  z.renderToString new App({model})
  .then (html) ->
    res.send '<!DOCTYPE html><html>' + html + '</html>'

z.renderToString()
z.untilStable() (essentially a multi-pass renderer)

Yes i'm also leaning towards the variant that flushes it all at once. The self-destructive <script> variant "might" benefit if you have multi-layered async dependencies independent of each other.

As it stands there is no longer a need to create a npm/@dyo/dom(dom renderer) package because the heuristics around custom renderers now allows us to create them at runtime.

I opted for a runtime heuristic that is as close to the DOM as possible. This means that we don't need to create default DOM renderers if the DOM is the environment you are rendering in. The way this works revolves around the passed container and presence of the API's present on that container.

The interface between the interface and Dyo is intentionally low; Only a few API's are interacted with name-ly: creation API's (.createElement, createElementNS, .createDocumentFragment, .createComment, .querySelector) getter/setter API's (.ownerDocument, .style, .textContent, .nodeValue), and attribute API's (setAttribute, .removeAttribute, addEventListener) which all implicitly exist on the node.

A renderer is thus any interface that creates corresponding nodes with these interfaces where the entry point of these is .ownerDocument.

This allows runtime creation of renderers for subtrees all while preserving the isolation aspect of having a render-target.

Branch specific renderers use portals as an entry interface given their ability to accept containers.