Closed cmeeren closed 4 years ago
Let me try to break this down.
First of all, what is server-side rendering from the point view of the library? It is having the ability to render Html from the server directly on the initial render only and give that Html to React to hydrate it's internal virtual-dom with it. In other words, creating the virtual-dom of your application from Html that was rendered on the server and served to the client. This allows web crawlers to process that Html returned from the server before your client-side javascript needs to execute.
So this SSR matter is really about being able to run Feliz from the server/backend:
// Server-side code
let initialState, unusedCommand = Shared.init()
let dispatch msg = ignore()
let userInterface : ReactElement = Shared.render initialState dispatch
let staticHtml : string = ReactDOM.renderToStaticMarkup userInterface
Does Feliz supports running this code for SSR? Yes it already does but only when you run code from a node.js server, not a dotnet one. This means you can use Feliz right now to build static websites and whatnot using modern tools like Gatsby or Next.js because they run a node.js server.
How is then Fable.React
able to run on a dotnet server? That is because that library uses compiler directives such that when it is compiled using dotnet, it will render Html using dotnet APIs instead of the Fable/React ones. It is the same public API but it executes different code depending on whether or not it is compiled using Fable or dotnet.
This means the issue really is how to make Feliz run on a dotnet server to support SSR from dotnet? Do we delegate component creation to Fable.React and piggyback on that existing functionality? I don't believe that is a good solution because I want to get rid of the Fable.React dependency eventually (right now, it is only there for compatibility reasons)
Do we do something similar to Fable.React? Well, if you look at the internal code, it is one big mess with all the compiler directives all over the place. I am absolutely against that and would rather develop the library as a pure React binding without considering the limitations of some React API that wouldn't "translate nicely" using dotnet APIs.
I propose a solution to this problem that wouldn't stand in the way of Feliz and still be quite nice to use from dotnet: Build a separate library called Feliz.Server
with the same public API as Feliz when it comes to the Html/style/prop API and React components/hooks that internally uses dotnet to generate the Html.
From a consumer point of view, you write this code in dotnet:
#if FABLE_COMPILER
open Feliz
#else
open Feliz.Server
#endif
let initialState, unusedCommand = Shared.init()
let dispatch msg = ignore()
let userInterface : ReactElement = Shared.render initialState dispatch
let staticHtml : string = ReactDOM.renderToStaticMarkup userInterface
In the snippet above, the functions Shared.render
Shared.init
are shared between client and server.
When someone steps up and build it. It is definitely on the road map but not a priority and I could do it myself, but I am afraid not any time soon because I have many projects in progress (for example Feliz.AntDesign, check it out) and would rather focus on Feliz itself and growing it's ecosystem.
As I understand it, SSR is a must to get any kind of SEO/searchability.
AFAIK modern web crawlers/indexers will gladly wait for the content to show up on your website even if it is not available on initial render (and needs to be rendered after data is retrieved). Google's Googlebot uses a headless browser to look at the content of the page and go through the links included in it, read https://vuejsdevelopers.com/2018/04/09/single-page-app-seo/
Thanks for the detailed breakdown! I understand much better now.
I don't think this is a critical issue, then. SSR is supported, just not on .NET servers, and SSR isn't needed for searchability anyway.
Feel free to close or leave open.
@cmeeren I have renamed the issue to reflect the actual problem but I will keep it open since I believe that a package like Feliz.Server (maybe Feliz.Dotnet?) can be really nice to have for writing server-side Html using the same syntax we have with Feliz even though if it is not used for a SSR solution
@Zaid-Ajaj, thanks a lot for this great library! I tried to play around with it and it feels really amazing. The only thing that stops us from using it in production is the lack of SSR on dotnet. This is so unfortunate. What is your estimate on building Feliz.Dotnet
library? How hard would that be?
Hello Mikhail, I am really glad to hear that Feliz leaves the impression that it was made for :smile:
What is your estimate on building
Feliz.Dotnet
library?
Although it is on the road map, unfortunately I don't have a concrete timeline as to when I will start working on the implementation. I wouldn't say it is hard to build per se since it will be a standalone library with the only requirement of having the exact same public API as that of Feliz but that can be a bit tricky and requires setting up a test project to ensure compilation works and the output is correct.
One complication point might be dealing with React stateful components and hooks and making them compatible from dotnet point of view, but for that I would look up what React does in SSR and emulate that behavior.
There are a couple of options though:
Rendering shouldn't be too hard I guess, what I have always wondered about is how does the server side rendered stuff "hook" into the client side app. I mean, if you already have an initial view the client need to recognize that. Also, having an initial view, doesn't that mean an initial state as well and how is that transferred to the client? It should be possible by returning some hidden element containing the initial state, but is that unheard of?
@mastoj From what I see in Fable.React we are adding special information for react like a data attribute reactroot
. Source
And indeed you need to pass the initial state of your application for the initialization of Elmish. SAFE stack does it by serializing the state into a global variable __INIT_MODEL__
.
Then in your Elmish init function, you check if you have an initial state stored in the __INIT_MODEL__
variable.
From what I remember, if react is able to generate it's VirtualDOM from an existing DOM if it doesn't have it initialized yet.
A first stab at https://github.com/dbrattli/Feliz.Giraffe for using Feliz with Giraffe. Currently no dependency on Feliz or Fable.React. Some more work with property renaming from React style and advanced styles needed, but the basic functionality seems to be working.
That's awesome @dbrattli! Looks like it may not be needed now, but would a paket github dependency on files like Html
and Styles
and shadowing the interop calls make it easier for you?
It's probably best to duplicate for now. Finding the right abstraction will take some time. There will be quite a lot of changes to Properties.fs
and Styles.fs
since React have their own way of naming things. I have btw removed the Giraffe dependency as well, so you can even use it with Fable for e.g email generation. That makes things a bit more confusing having duplication and separate namespaces. What is a good name for such a library?
What do you think? @Zaid-Ajaj
I like ViewEngine personally.
Thanks. I renamed it to ViewEngine just to see how it looks and get the feel after a few days. Will now and then fix property and style issues and also add more unit-tests to make sure the render engine works as expected.
Thanks a lot @dbrattli ! Great work! One thing to mention is the missing hooks. I believe the code won't compile if they are used.
Yes, that's right @mvsmal . I've already added React.functionComponent
and will add more as we go along towards v1.
Hello everyone, great job @dbrattli on the ViewEngine! Using it and it works great. I am working on a Feliz SSR Template for completely static websites, to host with Azure Functions and storage directly, so I am working through the quirks of using Feliz with SSR.
It is actually going really well! Few things need to be added:
To make it easier, I am doing a Feliz.Isomorphic package, that will have "polymorphic" type aliases for whether you are dotnet or Fable compiler. I am doing it manually right now, and probably everyone will need this to reduce headaches. Aliases will be generated from assemblies via reflection.
(This is for Feliz.ViewEngine); We would need to add dummy implementations of the methods on the React type so it can compile (and do nothing) on the server side. I started on my template and will push to Feliz.ViewEngine if agreed upon.
Thanks again for all the hard work!
Yes, for Feliz.ViewEngine we need more dummy implementations for ReactElement, events, ... I'm also doing a SSR example and run into issues every day. Please feel free to add PRs. Btw, How will the Feliz.Isomorphic package help? Currently I'm using #if
this in every file, and I'm tempted to rename the namespace match Feliz to make things easier, but that would make it hard to use the ViewEngine to e.g generate emails within Fable. Not sure if using ViewEngine within Fable is something anyone would do as you could always ask the server to generate the HTML for you.
#if FABLE_COMPILER
open Feliz
open Feliz.Bulma
#else
open Feliz.ViewEngine
open Feliz.Bulma.ViewEngine
#endif
The isomorphic package will make it so, in your shared projects, you will only need to open that one, and you won't have to do the compiler checks in every file, due to the added aliases.
example isomorphism:
namespace Feliz.Isomorphic
#if FABLE_COMPILER
open Feliz
open Feliz.Bulma
type Html = Feliz.Html
#else
open Feliz.ViewEngine
open Feliz.Bulma.ViewEngine
type Html = Feliz.ViewEngine.Html
#endif
Then, in shared files, simply open Feliz.Isomorphic and you no longer need the compiler check. It should be done this week.
I can commit the isomorphic project in ViewEngine, or I can host my own repo. Not sure which would be best, I was thinking of having it in its own package, since it has it own build process and might have to be updated more frequently if we want to support type aliases from other projects.
What do you think?
Sounds great! Looking forwards to try it out. Probably best to use a separate repo. I committed Feliz.Bulma.ViewEngine to Feliz.ViewEngine but now I cannot release it the same way as the Feliz.ViewEngine that is published by just pushing a tag, and I don't want them to have the same release versions. Will probably move Feliz.Bulma.ViewEngine to a separate repo to it can publish it more easily.
Hi @dbrattli, just pushed the first version of Feliz.Isomorphic
nuget. Basically I mapped the namespaces Feliz.X.Y
-> Feliz.Isomorphic.Feliz.X.Y
. Seems to be working pretty decently for now. Here is the repo: https://github.com/bengobeil/feliz.isomorphic. Will add some doc shortly to add a new Feliz
library, for now I have Feliz and Feliz.Bulma configured.
Let me know how it works out!
EDIT: I tested a bit further. This only works as a static library. Pretty obvious once I think about it. Ignore the NuGet, add the Feliz.Isomorphic
project to the solution, and that works.
I love almost everything about Feliz, but the lack of SSR causes me some concern.
In short, I'd like to experiment with Feliz for creating websites. As I understand it, SSR is a must to get any kind of SEO/searchability.
Other than that I know next to nothing about SSR, though. (Including how easy/hard it would be to create a website using SSR.)
What would be needed to support SSR? Is it a large undertaking? Could it be considered, or is this an instant-close wontfix?
For the record, SSR support has been touched on previously in #31 and #49. But #31 did not center on SSR, and #49 was closed after I simply quoted a comment from #31. So I thought I'd make an "official SSR issue" for "official" consideration.