Hacker News new | past | comments | ask | show | jobs | submit | atlassubbed's comments login

Ah interesting, a trip down memory lane! I made something very similar 5 years ago: https://github.com/atlassubbed/atlas-relax. It was an attempt to build a "stronger" version of React (using DAGs) in under 2.5kb, and completely decouple the diffing/rendering engine from the DOM. So think "nodes that can return JSX templates in their compute functions", but the JSX templates don't need to represent the DOM. They could represent anything, like Terraform config since JSX is just a syntax for javascript objects, similar to the hyperscript function `h`. To create React with my framework, I used a plugin (one such plugin https://github.com/atlassubbed/atlas-mini-dom) which registers listeners on nodes' create/update/destroy lifecycle methods. The simple idea is when a DAG node is added and corresponds to a DOM node, add the corresponding DOM element to the DOM. When removed, remove the element, etc. The cool thing was that the "build your own react" plugin is like 20 lines of code since the base diffing/rendering is abstracted away by the underlying DAG engine, so the interface ends up being similar to an AST crawler interface, where you're just implementing listeners as you encounter new/changed tags.

  const { diff } = require("atlas-relax");
  const DOMRenderer = require("atlas-mini-dom");
  const App = () => (
    <div>
      Bonsly evolves into Sudowoodo after learning mimic.
    </div>
  )
  // create a DOMRenderer plugin
  const rootEl = document.getElementById("root");
  const renderingPlugin = new DOMRenderer(rootEl);
  // mount <App/> against the "null" DAG and render it to the DOM.
  diff(<App/>, null, renderingPlugin);
The cool thing is you could diff two different DAGs against each other and listen to the delta, like `diff(<App1/>, <App2/>, consoleLogPlugin)`. The base library could be used to generate application frameworks as long as your application framework can be thought of as a DAG operating on data. React is an example of such a framework, but so is something like Airflow, so you could write a plugin that lets you build your own kind of Airflow. That was the motivation behind my DAG abstraction -- to make it easy to create DAG frameworks for frontend and backend. Let the base library do all the hard reconciliation work, and you can build application frameworks on top.

Anyway that was all mostly an exercise. I didn't end up using my framework for anything more than a state management solution for React. It handles global data perfectly, although these days React context or hook management is usually enough.


That looks really remarkable.


Topological sorting has many applications. I built this a couple years ago https://github.com/atlassubbed/atlas-relax.

It's a DAG-computing framework in JS that allows you to listen to updates in a compute graph. It's a set of legos that I used to build both a MobX clone (https://github.com/atlassubbed/atlas-munchlax) and a React clone (https://github.com/atlassubbed/atlas-mini-dom). React and MobX are actually the same thing mathematically: reactive DAGs, so they can both be built using the exact same abstraction.


FWIW, I found it weird that the article didn't mention "DAG" once...


Error-boundaries in VDOM frameworks. If you have an asynchronous diff cycle, you might get multiple errors from more than one node in a single diff cycle. One option is to bubble up errors to the nearest LCA that is an error-boundary. In general if you have marked two nodes in a tree and need to modify the smallest subtree that contains those nodes, then you will probably need to know the LCA.


I found this pretty easy to do with ngraph.forcelayout[0]. I used it to build a basic VDOM-diff visualizing demo[1], which involved dynamically animating edge changes to a graph. If you need something like that, it's pretty easy with ngraph in case this library doesn't support it.

If you end up using this library, please let me know how it turns out! I'd love to see a demo of exactly what you're talking about. I ended writing quite a bit of mouse-event and panning/zooming code on top of ngraph so maybe this might be easier?

[0] https://github.com/anvaka/ngraph.forcelayout

[1] https://github.com/atlassubbed/play-relax-visualized


Will check this out thank you!!


This is a funny coincidence. I stumbled upon this library just yesterday from the Big-O cheatsheet[0] site, but didn't think to post it here. Has anyone here done a detailed comparison between ElGrapho and ngraph.pixel[1] or vivagraph[2]? I currently use ngraph for an interactive VDOM diffing demo[3] and it works great for large amounts of nodes, but I guess I should check this out in more detail now.

[0] http://bigocheatsheet.com/

[1] https://github.com/anvaka/ngraph.pixel

[2] https://github.com/anvaka/VivaGraphJS

[3] https://github.com/atlassubbed/play-relax-visualized


I've always liked Dan Abramov's posts. He is very real, if that makes sense. This one really helps me mentally. Showing new work the world is hard, and people will always have something negative to say. There's also the issue of timing. When is it "too early" to show off a project? I always wanna post my projects to HN but then I remember how my documentation isn't done, or how I haven't written any tutorials or articles, and I figure I'd better wait, otherwise people will just bounce because they didn't find anything. I think the fraction of people who look at source code because they didn't find any docs is very small.


Imba claims to use an "imperative, memoized DOM" as opposed to a VDOM. Apparently it looks like they still use a VDOM (i.e. a wrapper around the DOM). The difference is that, at compile time, they attempt to unmix the dynamic parts of a template from the static parts, then they memoize the static parts for you automatically.

Sure, there are issues with existing VDOM implementations that do not do these compile-time optimizations:

  1. Returning new JSX elements from every render
  2. Building key indexes for every subdiff
The first issue can be solved with application-specified memoization in render. The second issue is pretty hard to avoid if you want to avoid matching elements based on indexes (naive subdiffs). Implicit and explicit keys are there for a reason, namely that moving elements is assumed to be a much less expensive operation than unmounting and re-mounting them. The O(N) trash memory overhead per subdiff incurred due to key indexes is justified, in my opinion. If using an algorithm like LCS, you'll incur more time/space cost, but you will even further minimize the edit path.

If I'm not mistaken, Imba would have to use something similar to key indexes if they want to avoid unnecessary unmounting and re-mounting for templates which are composed of large dynamic lists.

I'm not sold on Imba's value proposition. The value proposition is compile-time memoization for static elements, so you avoid re-creating those templates during renders. That's great and all, but how much of SPA webapps are purely static elements? For example, consider the the following JSX template:

  <ul>
    {this.state.bigList.map(i => <li>${i}</li>)}
  </ul>
I don't think Imba would perform much better than these other libraries for templates which are 99% dynamic (like this one), and so the dominating term here is the subdiff term, where you will end up either:

  1. Creating at least O(N) trash memory (e.g. key indexes)
  2. Doing unnecessary mounts and unmounts


We do recommend using keyed elements (indexing using <tag@{mykey}> syntax) for large lists. It includes automatic pruning of cached unmounted elements etc if you reach a certain threshold of unique memoized nodes within a list over time).

It performs a lot better for a platform like scrimba at least - which is pretty dynamic.

I just hope other frameworks will adopt our approach, because it really is a huge step forward compared to most of what is out there today.


I don't think any of my concerns were addressed. If I index a <p@key1/> and its position changes in the child list, will the diff move the node (e.g. insertBefore) as opposed to unmounting/remounting it? Also, have you tested the performance of a Fisher-Yates shuffle on a list like [<p@key_i>]? I'm curious if that is faster than React. There are some cases where you'd want many list elements moving around.


Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: