Server Side Rendering (SSR) in any JavaScript framework, with preservation of the reactivity behavior of the application, depends on the ability to hibernate the runtime state (at server), so that it can be later (at client) hydrated back to its previous state.
Video
https://www.youtube.com/watch?v=HXmWYlhQCeU
(PS, this is my first video in a decade, I hope my coding skills make up for the video quality ;)
Source code
The following code snippet is a basic SSR simulation of how hibernation and hydration could work.
// ~/pages/hello-world.ts
export default function server() {
console.log("Hello World");
function client() {
console.log("Hello World");
}
return client;
}
I was hoping I could use an existing solution for a UI library that I wrote called @xania/view
but all solution that I could find are specific to other UI frameworks.
So I decided to build a UI agnostic solution. It is extremely hard and it evolves writing code transpiler/compiler but I did it anyway and I am hyped to share with you the results.
Introducing vite-plugin-resumable
This plugin is a UI framework agnostic solution for hibernating and hydrating javascript closures. You can say we found a way to serialize functions. It is the foundation for SSR feature of @xania/view
but it can be used with any other UI library.
Continue reading to learn how to setup your project or visit github here for full working source code of the example.
Setup
Currently we have only support for vite projects.
-
create new vite project
npm init vite my-vite-app
-
install resumable package
npm i vite-plugin-resumable
next step is to add the plugin to your vite configuration
import resumable from "vite-plugin-resumable"
...
plugins: [
resumable();
]
...
By default, this plugin looks in pages
folder for all files with .tsx extension and creates a server entry for each file found.
So go ahead and create a page called hello-world.tsx script in pages folder
// ~/pages/hello-world.ts
export function view() {
console.log("Hello server");
const counter = new State(1);
counter.subscribe({
next(value) {
console.log("counter", value);
}
});
function client() {
console.log("Hello client");
document.body.addEventListener("click",
() => counter.set(x => x + 1));
}
return client;
}
The resumable package will automatically infer the client code from the result of the server entry function.
In this case the client code is generated from the function declaration of client
. This function will be executed on client and will use the hydrated instance of the counter object, including the registered next-observer.
Let's start the application and see this in action
npm start
Open a browser and navigate to http://localhost:5173/hello-world
// terminal output
Hello server
// browser output
Hello client
Clicking on the document will increment the counter and trigger the observer which will print current value to the console of your browser.
// browser output
counter, 2
Conclusion
This plugin streamlines the development process by simplifying the code structure required to make a web application that can be executed on both the server and client side.
This is currently an alpha (aka proof of concept) version to verify usefulness of this approach. So far, this plugin captures the generic features of SSR so that any UI library could benefit from it.
We optimize the amount of client script that is sent to the client, but in the future we will be looking for more opportunities to optimize the state data (e.g. if we only access a property of an object then we may want to sent only the value of that property instead of the whole object.
next is to try this out with React and I will also build an adapter for @xania/view
Top comments (2)
I guess qwik's way is somehow helpful?
qwik was a big inspiration but I didnt like the component$ primitives in qwik, xania infers the client code from the return expression instead.
Another more significant difference is that qwik does not allow for closures to close over instance of State class as I demonstrated with xania.