> The calls are not yet made—we’re only building a blueprint of those calls:
<alert>
<concat>
Hello,
<prompt>Who are you?</prompt>
</concat>
</alert>
> This blueprint of “potential calls” looks code, but it acts like data. It’s structurally similar to function calls but it is more passive, inert, open to interpretation. We’re yet to send this blueprint to the other computer which will actually interpret it.
That article is a bit dense if you're less familiar with Haskell, but the basic idea is that you can write code that looks like:
do
name <- prompt "Who are you?"
message <- concat "Hello, " name
alert message
And what actually gets assembled is something resembling a syntax tree of operations, waiting to be interpreted.
You could interpret it locally, or have the interpreter send requests to another computer. And (if such are your desired semantics) the interpreter can short-circuit the whole thing and return an error message if any step fails - similar to how you describe these as being "potential function calls", i.e. calls which as of yet may or may not successfully execute.
Analogously, JavaScript already has a monad-like construct which can achieve something similar:
async () => {
const name = await prompt()
const message = await concat("Hello, ", World)
const result = await alert(message)
return result
}
But this is pure code, rather than data that can be interpreted in a custom way - it will always be "interpreted" by the runtime itself. The Haskell example is somewhat of a blend between data and code (it desugars to a tree of closures calling each other in sequence, which you have a lot of control over the execution of).
Your React-style example attempts to make this concept into pure data. Though in some ways, a DSL becoming pure data wraps around and becomes pure code again. You need to parse it, semantically analyze it, and then execute it - in other words, you've invented a compiler.
Thanks for the context! I give Haskell a one-word shoutout in the article as a hint but I really wanted to take the reader on the journey of inventing some of these things themselves.
Interestingly Wat (https://github.com/manuel/wat-js) handles Dynamic Variables, which fit well with serialized code, in that the serialization boundary can be better partitioned into variables you need to serialize (locals etc.), and environmental (dynamic) variables which are subject to their runtime env.
Wat also happens to have a good concurrency model, and a portable bytecode. Worth looking at in this space.
Sort of! My impression is that LISP is a little more powerful than what we're building up in the article. I wanted to focus on a few specific aspects (like first-class cross-environment imports and serializing continuations) but I'd ofc expect all of that to be expressible in LISP.
It's fun to theorize about an alternate universe where JavaScript has a LISP-like syntax (Brendan Eich originally wanted "Scheme in the browser"[1], so this isn't so far-fetched!). Indeed, `interpret` sounds like a LISP macro that can "partially evaluate" code (so someone else, ie. the browser can continue to evaluate it).
This is very long, I’ve skimmed over the article.
In the end, what I’m trying to understand, is what server components solve? Faster performance? Serving compiled app html on refresh? Why is it so much better than the old school hydration?
Value proposition is “components for server”. But it’s worth to take a step back first.
What is value proposition of react component in comparison to older “mvc-inspired” frameworks? That you can drop a component in your code and call it a day. Don’t need to wire up separate controllers, models and views. Just one thing.
Now, if you want to add “auth” to your app (in react meta framework like next) you need to add component and probably add some routes and inject some middleware. With server components, you just add one component somewhere in the root of your app. Just one thing.
All that over the wire format, suspended data fetching etc, are a result of solving that problem and keeping in mind that those components are slightly different. It takes more work, but in return we can interleave both types of components nearly freely.
Sorry, having trouble understanding the nuance of the auth example (FYI in react I have experience only in small scale projects).
What’s the difference in classic client components having a root component validation auth?
It might be a contrived example, but I found the example of syntax highlighting a blog post to be a good example of the benefits of RSC.
If you hydrate on the client, you need to run the same exact code on the server and again on the client - however, that means you now might need to bring in a big syntax highlighting library to the client.
With RSC all of that is done server side, so you don't need to ship the syntax highlighting library.
That may be fine for static rendering libraries, but chart libraries for example would have to be pushed to the client as well.
So it seems to me that the benefits are so small and network speeds today are so fast, that switching your entire backend stack for that is over complicated to the point of absurdity…
I didn't really attempt to motivate it from the practical standpoint in the article. The article aims to be more of "reinvent some old ideas from first principles, while explaining some novel parts" kind of thing.
In particular, the callback to Miguel's article about async/await (https://tirania.org/blog/archive/2013/Aug-15.html) is intentional. I'd like to see some parts of my article as making a similar argument for 'use client' / 'use server' and the concept of first-class cross-environment references module system.
And as for practical benefits, I think the main benefit is composition--ability to compose and weave Server/Client building blocks together. Of course there's been myriads of approaches to server/client apps but I've never seen anything with the same compositional properties as RSC. I think Sam's talk (https://www.youtube.com/watch?app=desktop&v=9CN9RCzznZc) makes an amazing argument in favor of that viewpoint so I'll defer to him.
Author is explaining react server components in a colorful style.
They aren't reinventing or proposing anything new.
Most of the complaints in the comments here are probably due to author not saying "hey we're going to pretend to invent RSC so you get how it works, hop in"
The need to wax philosophical really shows how complex and overengineered RSC is compared to solutions from other ecosystems that are just as effective but far simpler to work with and understand.
I'll add I think you're mistaking the cause and the effect. I'm not trying to sell you on something; I don't give a damn what you use. Like literally, I don't. I'm not personally invested in that. I'm not being paid to work on this or promote it or whatever. But I've been thinking about this stuff for a while and I wanted to write it down on the page. And my thoughts tend to be longwinded because I prefer that style. I think someone will salvage something valuable from my writing if they find it there.
What I've gathered from this: It's an explantion of why serializable closures are a way to solve/tackle the problem of hydration, etc. Hence the compiler technology in newer react.
The selling point looks to be this: you get all the advantages of server side rendering but with the flexibility of react client side components as well. Being able to just use react components for composability on the server side is pretty nice.
You've reinvented Free Monads: https://www.haskellforall.com/2012/06/you-could-have-invente...
That article is a bit dense if you're less familiar with Haskell, but the basic idea is that you can write code that looks like:
And what actually gets assembled is something resembling a syntax tree of operations, waiting to be interpreted.You could interpret it locally, or have the interpreter send requests to another computer. And (if such are your desired semantics) the interpreter can short-circuit the whole thing and return an error message if any step fails - similar to how you describe these as being "potential function calls", i.e. calls which as of yet may or may not successfully execute.
Analogously, JavaScript already has a monad-like construct which can achieve something similar:
But this is pure code, rather than data that can be interpreted in a custom way - it will always be "interpreted" by the runtime itself. The Haskell example is somewhat of a blend between data and code (it desugars to a tree of closures calling each other in sequence, which you have a lot of control over the execution of).Your React-style example attempts to make this concept into pure data. Though in some ways, a DSL becoming pure data wraps around and becomes pure code again. You need to parse it, semantically analyze it, and then execute it - in other words, you've invented a compiler.
Wat also happens to have a good concurrency model, and a portable bytecode. Worth looking at in this space.
[1]: https://wiki.c2.com/?GreenspunsTenthRuleOfProgramming
[1]: https://brendaneich.com/2008/04/popularity/
What is value proposition of react component in comparison to older “mvc-inspired” frameworks? That you can drop a component in your code and call it a day. Don’t need to wire up separate controllers, models and views. Just one thing.
Now, if you want to add “auth” to your app (in react meta framework like next) you need to add component and probably add some routes and inject some middleware. With server components, you just add one component somewhere in the root of your app. Just one thing.
All that over the wire format, suspended data fetching etc, are a result of solving that problem and keeping in mind that those components are slightly different. It takes more work, but in return we can interleave both types of components nearly freely.
If you hydrate on the client, you need to run the same exact code on the server and again on the client - however, that means you now might need to bring in a big syntax highlighting library to the client.
With RSC all of that is done server side, so you don't need to ship the syntax highlighting library.
In particular, the callback to Miguel's article about async/await (https://tirania.org/blog/archive/2013/Aug-15.html) is intentional. I'd like to see some parts of my article as making a similar argument for 'use client' / 'use server' and the concept of first-class cross-environment references module system.
And as for practical benefits, I think the main benefit is composition--ability to compose and weave Server/Client building blocks together. Of course there's been myriads of approaches to server/client apps but I've never seen anything with the same compositional properties as RSC. I think Sam's talk (https://www.youtube.com/watch?app=desktop&v=9CN9RCzznZc) makes an amazing argument in favor of that viewpoint so I'll defer to him.
They aren't reinventing or proposing anything new.
Most of the complaints in the comments here are probably due to author not saying "hey we're going to pretend to invent RSC so you get how it works, hop in"
https://codesandbox.io/p/sandbox/8dgdz8
What's overengineered here?