top | item 38510209

Learn how modern JavaScript frameworks work by building one

531 points| kristianp | 2 years ago |nolanlawson.com

99 comments

order
[+] n2d4|2 years ago|reply
I like the article, but it gets some things subtly wrong.

> To grossly oversimplify things: React assumes that your entire virtual DOM tree needs to be rebuilt from scratch, and the only way to prevent these updates is to implement useMemo

Not quite, on a state update, it rebuilds the component that was updated and all of its children. Not the entire virtual DOM; old versions of Angular did this, but it was wasteful.

useMemo doesn't prevent that, but React.memo can (useMemo has a different role; it lets you choose when to recompute or recreate a normal JavaScript object. But on its own it won't stop rerendering of child components!) [0]

This invalidates some of their assumptions. The reason why React isn't "push-only" isn't because it does that, it's because it sometimes buffers updates instead of always pushing them immediately. In fact, other frameworks like ~~Svelte also aren't "push-only" and hence not strictly reactive~~! [edit: this is no longer true after Svelte v5, see discussion below] (Funnily enough, OP uses an article as a source that explains this correctly [1], but it seems they took the wrong lesson from it).

The reason why signals are so cool is because the framework knows for any given state change which exact attributes in the DOM need to be re-rendered, even more specifically than "the element and all its children". But this neither implies reactivity nor the other way around. The two concepts are orthogonal.

Anyways, kudos to the author for diving into this so deeply!

[0] useMemo is useful in combination with React.memo sometimes, as the latter compares objects shallowly/by reference instead of their contents, so useMemo can be used to only recreate shallow references if its contents changed. You could probably also reimplement React.memo with useMemo, but you probably shouldn't. [1] https://dev.to/this-is-learning/how-react-isn-t-reactive-and...

[+] nolanl|2 years ago|reply
Author here. Thanks for the thoughtful reply!

I did indeed mix up `useMemo` and `React.memo` – fixed it in the post.

You're right, I am skipping a lot of details (hence "to grossly oversimplify"). I know that React doesn't invalidate the whole tree, but it does in the worst case. Maybe I should add a note about that.

Svelte not being truly reactive makes perfect sense, but in Svelte v5 my understanding is that "runes mode" does exactly that. This is what I mean by "moving in that direction."

[+] pavlov|2 years ago|reply
> "useMemo doesn't prevent that, but React.memo can"

You can definitely use useMemo with JSX elements to prevent child components from being re-rendered too often.

There's an example right in the React Hooks FAQ where it reads: "Conveniently, useMemo also lets you skip an expensive re-render of a child"

https://legacy.reactjs.org/docs/hooks-faq.html#how-to-memoiz...

AFAIK there's no magic to React.memo. It's basically a shorthand for useMemo that takes the props as the dependency.

[+] thorway288|2 years ago|reply
The quote quite literally states “grossly oversimplify”. It being “not quite” correct is sort of the aim, for the sake of conveying a broader point.
[+] xrd|2 years ago|reply
I really love Svelte. The compiler is great and very extensible. For example, you can easily add functions to the processing pipeline to process Svelte templates (or the script elements or style sections) in your own special way. It's a fantastic way to build JavaScript frameworks. Svelte people always note Svelte isn't a framework, so this isn't a framework on top of another framework!

I used this to build Svekyll, a Jekyll clone (the original static blog tool).

https://extrastatic.dev/svekyll/svekyll-cli

Not to toot my own horn, but I'm really proud of it. Svekyll scores all 100s with lighthouse but still has all the cool things you get from a Svelte app. It's a true single page app, all JS is inlined and can be put on any web server for hosting. Plus, a bunch of other cool things that only are possible with a native JS blog.

[+] mhh__|2 years ago|reply
I also like svelte but as a compiler-y person who happens to be doing some JS I can't work out why I'm annotating so much stuff by hand (even with svelte 5). I can get why you might want this for react, but isn't svelte a compiler? Can't we do dataflow analysis?

I'm 50% convinced there's a Chesterton's fence I'm mising but where?

[+] sureglymop|2 years ago|reply
I love svelte too but I've found it hard to integrate it in different places where I may run js (e.g. obsidian plugins, browser plugins). It was hard to configure the necessary tooling but maybe it's not sveltes fault. I think svelte would really benefit from better documentation on how to do this (and I don't mean sveltekit documentation). It was also at least non-trivial to use with typescript and even more non-trivial to have some third-party dependencies/components that don't use typescript. Again, maybe not sveltes fault and hard in every framework but those were my show-stopping issues the last time I tried to build something real in svelte.
[+] synergy20|2 years ago|reply
Until svelte can have a client-side router builtin, and treat SPA as its first class citizen(the way Vue.js does so far. React also shifts to SSR-SPA mixed situation jus like svelte, both are impacted by Vercel, which is really sad), instead of just focusing on its sveltekit SSR-first, I have zero interest in it. Yes I know I can customize sveltekit to do SPA, but it's very ugly and I don't need all your SSR mental load to an already complex frontend world.

Both Svelte and React are shifting to SSR-first, which is what Vercel can make money with, I read somewhere Vercel had many React core members now, after it bought out Svelte.

[+] jph|2 years ago|reply
Toot your horn because your project is really great. I blog with SvelteKit, and your way has many improvements galore. Thank you for sharing this!
[+] noduerme|2 years ago|reply
You need a class called ScreenManager, one called Screen, and one called Component. Make Screen and Component able to load an htm file, then hook its tags on the DOM and do whatever you want. Update a counter? Write that in your component. Some static networking class in the background either long-polls or gets pushed new data, dispatches update events that any component can listen to. Each component can make its own calls and update its own data instantly when a user interacts with it.

No abstraction, no nonstandard HTML tags, no <template>, no Proxy, no master class trying to figure out what part of the DOM should or shouldn't be redrawn based on inbound data. Every component should be autonomous, every screen should be able to destroy or resurrect its own components. If you need a central data cache, put that on the ping and let every component deal with it on the event firing.

[edit] I've built and maintained two frameworks, one for websites and one for single page apps, rewritten and improved over 20 years, originally in PHP, now in Nodejs. The main guiding principle for me has always been decoupling design from code.

[+] WA|2 years ago|reply
Sounds like you implemented MVC. Model (=Component) handles its own state. Screen (=View) subscribes to state changes in the model. ScreenManager (=Controller) glues it all together.

It probably works just fine, but gets cumbersome if you want to know exactly where a piece of state is managed or the order of event processing is important for some reason.

[+] localvoid|2 years ago|reply
If anyone is interested in this topic, I would recommend to start from fundamentals, so it would provide some answers on why some "not so modern" frameworks aren't jumping on a "signals" hype-train.

- Incremental computing - https://en.wikipedia.org/wiki/Incremental_computing

- Self-Adjusting Computation (Umut A. Acar) - https://www.cs.cmu.edu/~rwh/students/acar.pdf

- Introducing incremental (JaneStreet) - https://blog.janestreet.com/introducing-incremental/

- Incremental computation and the web (JaneStreet) - https://blog.janestreet.com/incrementality-and-the-web/

- Self Adjusting DOM (JaneStreet) - https://blog.janestreet.com/self-adjusting-dom/

- Self Adjusting DOM and Diffable Data (JaneStreet) - https://blog.janestreet.com/self-adjusting-dom-and-diffable-...

- Incremental Computation (Draft of part 1) (Rado Kirov) - https://rkirov.github.io/posts/incremental_computation/

- Incremental Computation (Draft of part 2) (Rado Kirov) - https://rkirov.github.io/posts/incremental_computation_2/

- Incremental Computation (Draft of part 3) (Rado Kirov) - https://rkirov.github.io/posts/incremental_computation_3/

- Towards a unified theory of reactive UI (Raph Levien) - https://raphlinus.github.io/ui/druid/2019/11/22/reactive-ui....

[+] genuine_smiles|2 years ago|reply
Besides React, are any of the popular frameworks not on the signals type-train?
[+] stanislavb|2 years ago|reply
Please, do not build more JS frameworks (see-no-evil emoji).
[+] breadwinner|2 years ago|reply
You mean, you want React to continue for the rest of human history? Hope not!
[+] slmjkdbtl|2 years ago|reply
It's more about learning how current frameworks works, and the best way is to learn to build one
[+] gumballindie|2 years ago|reply
Coincidence or not but since the tech winter started there are fewer js modules and frameworks released. As it should be. Hope react manages to somehow die in the process.
[+] recursive|2 years ago|reply
I recently did this because none of them are exactly what I wanted. I really like the idea of reactive proxies and pushing changes. Things get trickier when you try to address mutable arrays and other scenarios.

The one I made is https://mutraction.dev/ The name is a portmanteau of mutation tracking.

[+] notnullorvoid|2 years ago|reply
For arrays, did you end up taking the diffing/memoization approach like solid?

I've yet to see any framework have O(1) collection mutation reactivity.

[+] samsquire|2 years ago|reply
I haven't really kept up with frontend developments. My experience was with Knockout which I enjoyed and I did some hacky React more recently for devops-pipeline.com

I am curious, to find the sweet area of maintainability + performance.

Is the problem usually latency ? If you load too many items into a grid view you get performance problems.

Maybe this problem is solved already - Facebook solved it - I would like to be able to create a "weak iterator" that handles forward and back navigation of collections with extremely fast rendering when moved backwards or forwards.

Computers are fast, I like the ideas of immediate mode but they burn CPU.

[+] EGreg|2 years ago|reply
When they say Svelte “compiles” your code into Javascript, doesn’t it still mean it has to ship some common code alongside your transformed code? Isn’t that still the core of a Framework?
[+] valenterry|2 years ago|reply
Question to the folks with a lot of frontend framework experience:

Is there a framework/library that supports the usage of an effect-system when it comes to rendering actions?

For instance, in react, a component (or rather it's render-function) has to return the element(s) directly. Is there a framework where the render-function accepts something effect-like or promise-like instead, even if that means that the rendering might potentially be delayed?

[+] n2d4|2 years ago|reply
React now supports this using Suspense boundaries [0]. Some frameworks (eg. NextJS) already ship variants of it but here is some code you could use in React's development version:

    function MyComponent() {
      const promise = ...;
      const result = use(promise);  // use is like await
      // do something
    }

    function Wrapper() {
      return (
        <Suspense fallback={<Loading />}>
          <MyComponent />
        </Suspense>
      );
    }

You don't need to know this to use it, but the implementation is both interesting and horrifying: The `use` hook checks if the Promise has resolved, and if not, it throws an error that is caught by the Suspense boundary. And because there is no property like `hasResolved` on JS promises, `use` adds one itself. (At least this was how an early draft proposed it, a lot of changes have been done since, and my knowledge might be out of date.)

[0] https://react.dev/reference/react/Suspense

[+] marcelr|2 years ago|reply
If you mean effects as data, I can't think of anything off top other than elm which is a language + framework.

I've made my own though: https://github.com/marcellerusu/capable-js.

Its not for use but it was an interesting experience that enables a lot of new patterns by using generators.

I don't claim that it is better than other frameworks though, there's a lot of times where this pattern is significantly more cumbersome than just using react.

[+] tipiirai|2 years ago|reply
> Question to the folks with a lot of frontend framework experience:

I think this should be "...with a lot of React experience".

[+] threatofrain|2 years ago|reply
Does anyone have a recent one for building RxJS-lite?
[+] djbusby|2 years ago|reply
I'm always surprised when articles mention a bunch of JS frameworks and leave out RiotJS. I can't be the only person using it?
[+] nonethewiser|2 years ago|reply
Never heard of it. Looks sveltish.
[+] izelnakri|2 years ago|reply
Yeah these type of articles always have hypester tone with less substance unfortunately. RiotJS is great but ember.js API/tools are even greater, highly suggest learning ember.js, if you value good APIs, testing & good community ;)
[+] sesm|2 years ago|reply
The article doesn’t state what problem those ‘modern’ web frameworks are trying to solve. It’s already been known that you can make a faster framework at the cost of less ergonomic API and more complicated mental model, but in most cases it’s not worth it. And when it’s worth it, React had the tools to ‘eject’ a subtree from the very start.
[+] diamondfist25|2 years ago|reply
Is there a way to make react “reactive” and have to define all the useState, useEffect?
[+] zubairq|2 years ago|reply
I ended up building a JS Framework too.. all part of the journey! ;)