For the past months we've been working away at SolidStart and that has led to some incredible exploration into the realm of islands, partial hydration, and hybrid routing. Solid 1.6 backfills that into the core so that other Solid projects can benefit.
We've also addressed a long-time issue with the proper merging of JSX spreads on native elements. When components don't re-execute dynamic spreads can one of the most complicated pieces to preserve granularity. Overall this is a smaller release but it represents a unique innovation into our vision of the future.
Special thanks to @nksaraf and @rturnq work to guide exploration in these directions and contributions from @trusktr, @LiQuidProQuo, and @thetarnav
Highlights
Official Partial Hydration Support
Solid has worked for quite some time in partial hydrated ("Islands") frameworks like Astro, Iles, Solitude, etc.. but now we have added core features to support this effort better. These features are mostly designed for meta-framework authors rather than the end user they are exposed through a couple APIs.
<Hydration />
joins <NoHydration />
as being a way to resume hydration and hydration ids during server rendering. Now we can stop and start hydratable sections. This is important because it opens up a new optimization.
createResource
calls under non-hydrating sections do not serialize. That means that resources that are server only stay on the server. The intention is that hydrating Islands can then serialize their props
coming in. Essentially only shipping the JSON for data actually used on the client. Reducing the double data problem significantly.
The power here is static markup can interleave dynamic components.
<h1>Server Rendered Header</h1>
<Island>
<h2>Server Rendered Sub Header</h2>
<p>{serverOnlyResource().text}</p>
<DifferentIsland>
<p>More server-renderd content</p>
</DifferentIsland>
</Island>
Keep in mind Server rendered content like this can only be rendered on the server so maintaining client navigation with this paradigm requires a special router that handles HTML partials.
Similarly, we want the trees to talk to each other so hydrate
calls now have been expanded to accept a parent Owner
this will allow Islands to communicate through Contex without shipping the whole tree to the browser.
<h1>Server Only Rendered Header</h1>
<ClientProvider>
<h2>Server Only Rendered Sub Header</h2>
<ClientIslandThatReadsContext />
</ClientProvider>
These improvements make it easier to create Partial Hydration solutions on top of Solid, and serve to improve the capabilities of the ones we already have.
Native Spread Improvements
Native spreads are something we started at very naively. Simply just iterating an object that has some reactive properties and updating the DOM element. However, this didn't take into consideration two problems.
First properties on objects can change, they can be added or removed, and more so the object itself can be swapped. Since Solid doesn't re-render it needs to keep a fixed reference to the merged properties. Secondly, these are merged. Properties override others. What this means is we need to consider the element holistically to know that the right things are applied.
For Components, this was never a problem since they are just function calls. Unfortunately for native elements, this means all those compiler optimizations we do for specific bindings now need to get pulled into this. Which is why we avoided it in the past. But the behavior was too unpredictable.
In 1.6 we have smartened spread to merge properly using a similar approach to how we process Components.
// A`class` property in `props` now takes priority even if someSignal() updates.
// In fact it won't even be subscribed to unless props does not have a `class` property.
<div class={someSignal()} {...props} />
We've also found new ways to optimize the experience. (See below).
Other Improvements
Deproxification
Working on new Spread behavior we realized that while we can't tell from compilation which spreads can change. We can tell at runtime which are proxies. And in so if we only need to merge things that don't swap, and aren't proxies we can avoid making a Proxy.
What is great about this is it has a cascading effect. If component props aren't a proxy, then splitProps
and mergeProps
don't need to create them, and so on. While this requires a little extra code it is a real win.
We get a lot requests for low-end IoT devices because of Solid's incredible performance. In tests, Solid outperforms many of the Virtual DOM solutions in this space. However, most of them don't support proxies.
So now if you don't use a Store
or swap out the props object:
// this is fine
<div {...props} />
// these could swap out the object so they make proxies
<div {...props.something} />
// or
<div {...someSignal()} />
We don't need to introduce any proxy the user didn't create. This makes Solid a viable option for these low-end devices.
Note on the title
Castle in the Sky was a landmark film for director Hayao Miyazaki, the first created at their newly formed studio, Studio Ghibli back in 1985. I found the imagery of floating Islands irresistible in the sense with our client-routed Islands it is like pulling them out of the water.
A "Castle in the Sky" also refers to an idealistic, almost unrealizable goal. I think our isomorphic ambition of where we are taking things in frontend development feels like that sometimes. Attempting to keep aloft balancing the restrictions of the weight of JavaScript, the desire for interactivity, and the polish of experience seamlessly across both platforms immediately brings to mind these floating structures. A Solarpunk-esque return to our roots in server rendering with advanced technology that attempts to make itself invisible.
So whether that is for you, Laputa, Skyloft, Zeal, Skypiea, Sanctaphrax, or some other, I wish you luck on your journey upwards.
Best, @ryansolid