DEV Community

Cover image for JavaScript vs JavaScript. Fight!

JavaScript vs JavaScript. Fight!

Ryan Carniato on August 19, 2021

With software development, we are often presented with alternative choices for libraries and tools that appear to accomplish the same sort of thing...
Collapse
 
artalar profile image
Artyom

I am researching reactivity in general and a few reactive patterns (frp, push \ pull, and so on) for a few years already and I want to insist on a "reactive" definition.

Reactive programming is a pattern of delegating the responsibility of initialization data processing to the data source, which doesn't know dependent units and can't interact with it outside one-way notification.
FRP, OORP, Flux, two-way binding, single-store, granular updates are just buzzwords on top of that, other high order patterns.
Rx, Solid, React, Even Emmiter is all about reactive programming.

The main reason for that is to move components coupling from code to runtime, that's all.

Collapse
 
bobdotjs profile image
Bob Bass

I never thought about this before I listened to Rich Harris and his talk about rethinking reactivity where he explained how React uses the virtual DOM, and Svelte essentially uses hierarchical chaining (I'm not sure if there's a proper name for it but that sounds about right).

The reason it was so interesting to me is because if I didn't know what the virtual DOM was and someone asked me how I thought Reactivity worked, my instinct would be to describe a chain of dependencies for each object.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

So basically having events for data changes?

Collapse
 
peerreynders profile image
peerreynders

What are we diffing - data, VDOM abstraction

Doesn't it make more sense to diff application state? And then only derive and update those parts of the view that need updating.

Granted diffing the DOM is more abstract and could perhaps be more reusable across a larger range of applications but it's like preferring to catch the horse after it has left the barn rather than just locking the barn door. Maybe it's more to the point that VDOM-based solutions are more focused on providing an alternative to the DOM API that is as far removed from the DOM API as possible.


don't assume the word component means the same thing to everyone.

Good luck with that one.

Certain members of the front end developer community seem to have a hard time differentiating between a framework in general and an "application framework". There seems to be a "one word", "(my personal) one meaning" mentality out there.

The first is an interopt story, the second an organization story.

I think the second one is even more problematic though - organization of what?

If you can get people to accept Web components as mere custom elements (while the existence of the shadow DOM suggests the DOM component perspective) I think people will quickly lose interest - especially as Safari only supports autonomous custom elements without a polyfill (or ponyfill).

But as soon as you take the solution farther, like SSR you are inventing new territory.

I think not foreseeing the need for direct support of SSR/partial hydration may turn out to be the Achilles heel of the v1 spec. Hand designed solutions (that take over browser parsed DOM fragments) are possible with the technologies introduced by the spec but are not likely going to be embraced by a community with a very specific expectation of what a "component" should be.

but the way we organize our applications is not.

… or components for that matter.

What I'm alluding to is that components boundaries need a lot more attention. You've published a number of articles about the performance impact of component boundaries but there is another aspect that I think needs more scrutiny.

On a legacy automotive engine a carburetor can be considered a "component". The way many front end framework components are authored is reminiscent of a small carburetor permanently joined to a slice of an engine block containing a single cylinder. When it comes to combustion engine design that idea would be considered ludicrous, yet in front end development this type of "self-containment" with high internal coupling is considered necessary to maximize productivity.

The Elm community gave up on "components" because of two distinct aspects of The Elm Architecture:

  • View: Transforming (part of) the model to (part of) the view (markup/DOM)
  • Update: Using a message from (some part of) the view to transform (part of) the model.

While both aspects communicate via the Model there is an important insight - the boundary around the part of state affected by a message often didn't match up with the boundary around the parts of state needed to render the affected parts of the view - thus making it impossible to package "a bit of View" together with a "bit of Update" (and therefore bit of Model) into a "self-contained component".

Now granted SolidJS's fine-grained reactivity gets around this by be being - well - fine-grained enough to not have to worry about these courser grained boundaries. But …

Any solution to a problem changes the problem.

While framework components need to manage view state - shouldn't that be separate from client-side application state? This of course goes back to UI as an Afterthought which itself is based on earlier work. It seems to me that the current generation of "light weight" front end frameworks promote framework component boundaries that aren't necessarily ideal for maintainability - trading it off for perceived short-term productivity gain and perceived but usually unrealized reusability.


Great Article!

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Doesn't it make more sense to diff application state? And then only derive and update those parts of the view that need updating.

As I mentioned in my comment; doesn't it make even more sense to diff nothing, and instead react directly to the data changes?

Collapse
 
peerreynders profile image
peerreynders • Edited

Not all "data" is created equal.

UI as an Afterthought:

State is the root of all revenue.

In the article state refers to state handled by MobX. So

Client side application state is the root of all revenue.

  • Events affect client side application state (i.e. not necessarily view state directly).
  • View state only represents a restricted but relevant part of client side application state in some transformed way.
  • So view state only has to be updated after the relevant portions of client side application state change (and preferably only once all the cascading changes have settled).
Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I don't see the problem here. State changes -> you respond accordingly. If only a small part of your state changes, you could specifically respond to only those changes (and only update the UI where necessary, or not at all).

Thread Thread
 
peerreynders profile image
peerreynders

Because …

Initially, design your state, stores, processes as if you were building a CLI, not a web app.

… because …

first encode what interactions our customers will have with our systems. What are the processes. What is the information he will need? What is the information he will send? In other words, let’s start with modelling our problem domain.

The solutions to these problems are things we can code without reaching for a UI library.

Current generation UI frameworks have a tendency to encourage client side architectures where "the UI is your application" - it typically takes a significant amount of discipline and effort to persevere with a "the UI is not your application" client side architecture.

Jest has pretty much shown the downsides of the "React is your application" architectural style. The testing framework is bloated and slow because that is what happens when you need to "unit test" "Smart UI Components".

Extreme Programming Explained (p.117, 2000):

You should test things that might break. If code is so simple that it can't possibly break, and you measure that the code in question doesn't actually break in practice, then you shouldn't write a test for it...

This lead to the The Humble Dialog Box (more generally the Humble Object) back in 2002 - yet for some reason front end development 20 years later continues to ignore this insight:

  • Use a lean, "dumb UI" that is so simple that it couldn't possibly break - that way you don't have to microtest it.
  • Without a "UI" to test, microtests can get away with lean and mean testing tools like uvu. Fast running microtest are more likely to be run frequently to generate feedback ASAP.
  • The UI is still tested but that can now become part of the heavier, less frequently run integration tests.

Elm's update/view separation has a counterpart in the OO world - Command Query Separation. Traditional OO would argue the accessors and mutators belong on the same object because that object manages a single concern. Command Query Separation challenges that notion; it argues that updating state (via commands) and retrieving a representation of state (via queries) are separate concerns.

Similarly a dumb UI fragment is simply a representation (query result via subscription) of some part of the application state. That fragment may contain some simple actions (commands) to initiate a change of some part of application state - perhaps even a portion distinct from the part the original representation was based on.

jQuery projects typically ended up being a Big Ball of Mud because as a library jQuery made it extremely easy to work with the DOM, leading developers to try to solve all problems within the DOM. But the DOM was simply the representation of server application state as parsed by the browser - if you were doing anything interactive on the client side that representation might no be enough to rehydrate client side application state (that is what Embedding data in HTML is for). Client-side application state needed to be managed separately from the DOM and it's changes needed to be propagated to the DOM. Segregated DOM demonstrated that approach but by that point AngularJS and React already had all the attention.

shift as much logic as possible out of the UI.

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Isn't that exactly what I'm proposing though? Having the data live in one place, and an mutation observer sitting on top of it changing the UI to reflect data changes. The data doesn't need to know about the UI and can live in its own little (testable) world.

User actions could then be passed back to the data as events, and suddenly the whole thing starts looking a lot like an MVC design. Or, if we want to look at it from the object side, we'd end up with two objects that communicate through message-passing, which is also an excellent way of de-coupling different parts of an application.

Collapse
 
redbar0n profile image
Magne Playful Programming

It is a decision you make depending on how important initial load performance(MPA) is versus future navigation experience(SPA). Either approach has tools to improve their weaknesses but each is fundamentally tailored to optimize for their primary usage.

Doesn't it suck that we have to make that tradeoff? I mean, both features are really really important for any app or site.

Why are SPA's naturally better at navigation experience? Because of animations, and relying more on replacing components on the page instead of forcing a full page change?

What is even a "page", in this case? When swapping out components, fluidly updating/morphing the UI bit by bit, when would/should a SPA decide to change the URL?

It's so complex. I'm starting to appreciate that there is a fundamental mismatch between the concepts of "documents/pages" and "application" which cannot simply be padded over by frameworks/libraries. The problem is exacerbated by the fact that to a consumer there isn't any material distinction between the two concepts (it's just a GUI), and sites often evolve into apps, as consulting clients want more and more features. Thus often invalidating any sound architectural trade-off one might have made in advance. In addition to the architectural complexities needed to be juggled by developers, who mostly really just want one single uniform way of working, which "just works".

Collapse
 
redbar0n profile image
Magne Playful Programming • Edited

Also, when reading about Resumability vs Replayability (re: Qwik), I wonder how approaches like Hotwire Turbo, which you mentioned, compare? Both Qwik and Hotwire seem to be DOM-centric.

Isn't Hotwire basically already "resumable"? Since Turbo Drive circumvents the need for rehydration, Turbo Streams reuses the server-rendered HTML, and Hotwire Stimulus also stores state within the HTML, and doesn't perform heavy bootstrapping or reconciliation? And how is Fine-Grained Lazy-Loading different from Turbo Frames?

Maybe @adamdbradley , @mhevery and/or @excid3 , @julianrubisch , @javan , @kirillplatonov could help clarify the similarities and differences between Qwik and Hotwire (specifically its Turbo and Stimulus parts).

Collapse
 
ryansolid profile image
Ryan Carniato Playful Programming

The difference is that Qwik isn't saying everything needs to be rendered on the server. You can mix Turbo with Stimulus and get elements of this. But Turbo is more just a means of HTML partials. You could use that with any of the partial hydration approaches to have benefit of not shipping js to render it. But Marko or Qwik(and really modern js frameworks) are designed so the shift between hydration and client render is seamless.

I will say this html centric is a bit overplayed. I agree from a transport perspective but that can be in the form of inlined script tags etc.. unless you can solve double data which these aren't claiming you can achieve the same without resorting to what can be serialized as an attribute.

In general for what it is doing you can compare these with Turbo and all, but when it comes to app mental model and DX these are worlds apart. One feels like layering a Javascript app on server rendered views, the other feels like authoring a single app experience like you'd find with any JS Framework, ie React etc.

Thread Thread
 
redbar0n profile image
Magne Playful Programming • Edited

Thanks for clarifying it a little bit.

The difference is that Qwik isn't saying everything needs to be rendered on the server.

Hm. But Qwik, being an MPA, has server-side routing, like you said. It's client also focuses on "resuming" HTML rendered on the server, and is apparently also completely stateless. So why wouldn't everything be rendered on the server, then? What would be beneficial to render on the client? Animations (like on entry/exit)? Being stateless, does Qwik ever render anything on the client? To me it seems (from their github description, and blog posts) that SSR is not only Qwik's focus, but its fundamental mode of operation. I can't see how replacing sections of the DOM with SSR'ed HTML is any different (or more client-side rendering) than Turbo?

unless you can solve double data

What do you mean by double data?

when it comes to app mental model and DX these are worlds apart

Yeah, the mental model seems quite different, and I appreciate that authoring components in React/SPA style is preferable DX to having HTML template partials which later has to be targeted from JS (Stimulus).

Thread Thread
 
ryansolid profile image
Ryan Carniato Playful Programming • Edited

They have client state they just put it in the HTML, keep on reading and reflecting it back. Again I think they are just overplaying the it's just HTML thing a tiny bit. It's really no different than anything else other than by keeping it in the HTML they can pause and resume the same page multiple times (like if stored in service worker app cache). Of course limitations here around serialization and after render performance (constantly reading from the DOM/reflecting to the DOM is less than great overhead). One could obtain resumable hydration without doing anything HTML specific. All you need to do is serialize the props to every component to get the same. Of course that is wasteful, so a better approach is to look at it like a reactive data graph. Qwik does that and so could anyone else not doing exactly what Qwik does.

Qwik's approach could apply to a SPA with some refinement but they aren't focusing there right away. And I imagine given Builder.io static site generation will be the starting point. But look at their TodoMVC example Misko tends to show off. That is a very client side application. Each new Todo is client rendered, each removal and edit as well. Qwik only loads the event handlers you need and in so has incredibly granular hydration. But you write your TodoMVC app like a TodoMVC app for the most part. Think of Qwik's state more like a global store with Dependency Injection.

Qwik isn't doing HTML partials. It doesn't replace things from the server. I'm not sure where you got that idea from. It just hydrates out of order. So you could put a client side router on the page and not load it until someone clicks a link, yet before that happens do something interactive on the initial page. This is very applicable to SPAs. Now you could use Qwik with something like Turbo.

The "double data" problem is that technically the data gets serialized twice with hydration. Once in the HTML (ie your manifested view) and once as the data you bootstrap the framework with. I've seen experiments to extract the data back from the manifested view but that has big limitations, and it isn't what Qwik is doing. Their data in the HTML is just the data you may have serialized a different way and is still independent of the manifested DOM.

Thread Thread
 
mhevery profile image
Miško Hevery

I was going to reply but, Ryan you are doing a great job, thank you.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

I've always found the various approaches to reacting to data/state changes in JS frameworks rather over-engineered.

To me the ideal solution would be a MutationObserver for plain JS objects. Something that tells me (via callback) when, for example, an element was pushed on an array.

Built-in reactivity on a data-level seems like a much more straight-forward (and probably easier to optimise) solution than a full re-render with subsequent DOM-diffing or Svelte's broken update = () => state=state™ hack


EDIT: To some extent, it is possible to approximate this behaviour in JavaScript using Proxy objects, which works just fine but comes with the downside of having to use Proxies of everything, because the plain object could still be changed without the proxy knowing.

Even with this limitation though, I've found that this way of reacting to data changes is much easier to handle in plain JS and leads to way less weird shenanigans to convince a framework to really update.

Collapse
 
ryansolid profile image
Ryan Carniato Playful Programming

Have you seen my library SolidJS? It's what I'm hinting at in the end about being more granular and diffing less. But it works as you mentioned more or less. I just don't want to always come in with the strong hand. But performance wise it is reasonably impressive.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Just wanted to say I think that SolidJS is fantastic work.

Collapse
 
bcowley1220 profile image
Brendan Cowley

Very well written article, and kudos for staying in the middle on these discussions

Collapse
 
iamrommel profile image
Rommel C. Manalo

At last a good article on dev.to, worth reading.
Not those kinds of "5 the best etc etc etc of 2021", or the "Top 10 ek ek ek of 2021"

Collapse
 
kebinjoy profile image
Konstantine

JavaScript wins. Flawless victory. FATALITY