# `qIsolate` for SSR of portions of the site ## Overview When rendering portions of the application on the SSR there needs to be a way to pass data to the client component in a way so that the component can render and serialize on the server such that it can be properly de-serialized in the context of the page it is being inserted to. This is useful for: - Navigation: So that we can navigate to new page as HTML and than rehydrate the content. - When we want a particular component to be SSR (either for full duration or just for start) ## Problem description ``` +-------------------+ | AppChrome | | | | +-----------+ | | | AppOutlet | | | | | | | +-----------+ | +-------------------+ ``` Assume you have outer shell components which represents `AppChrome` and into it you want to insert `AppOutlet`. Logically this is simple and straightforward. The complexity comes in these forms: 1. The server needs to execute the rendering of `AppOutlet` without knowing the state of the `AppChrome`. 2. On the client the `AppOutlet` needs to be inserted into the `AppChrome` and have the `AppOutlet` state deserialized in a way which is takes `AppChrome`'s state into account. ## Example ```typescript= interface User { first: string; last: string; } const AppChrome = qComponent<{}, {user: User}>({ tagName: 'app-chrome', onMount: qHook(() => {user: {first: 'Qwik', last: 'Builder'}}), onRender: qHook((props, user) => ( <> <span>Hello {user.last}, {user.first}!<span> <OutletPlaceholder user={state.user}/> </> )) }) ``` Above is a simplistic `AppChrome` implementation. Below is what we would get from the server as HTML. Notice that the component state is serialized into JSON. ```htmlembedded= <app-chrome on:q-render="..." q:obj=":3doeu"> <span>Hello Builder, Qwik!</span> <outlet-placeholder user="*:3doeu"/> </app-chrome> <script type="text/qwik"> { ":3doeu": { first: 'Qwik', last: 'Builder' } } </script> ``` Reminder: `q:obj` list which objects are associated with a given component. Now let's imagine that we would like to do a route change and that we want the content of the `<OutletPlaceholder/>` to be sent from the server as HTML. ```typescript= const AppOutlet = qComponent<{user: User}, {adjective: string}>({ tagName: 'app-outlet', onMount: qHook(() => ({adjective: 'awesome'})) onRender: qHook((props, state) => ( <span>{props.user.first} is {state.adjective}!</span> )) }) ``` The resulting HTML may be something like this? ```htmlembedded= <app-outlet on:q-render user="*????" q:obj="*??? :8uo3"> <span>Qwik is awesome!</span> </app-outlet> <script type="text/qwik"> { "*????": *????, ":8uo3": { adjective: 'awesome' } } </script> ``` Note the `*????` represents the unknowns. Now we have to solve few issues. While thinking about the issues keep in mind that `AppChrome` and `AppOutlet` were SSRed at different times (and maybe even on different servers). Additionally we want to make sure that all responses are CDN cacheable. However because IDs to objects are randomly assigned two runs of the same code will not have stable IDs (only the graph is stable.) 1. How can `AppChrome` send `User` instance to the `AppOutlet`? (Another way to say this is how does the server rendering of `AppOutlet` get a hold of the `AppChrome`'s `User` instance?) 3. How can the HTML of `AppOutlet` be rehydrated on the client such that it correctly associates with the right `User` instance. ## Mental Model Qwik needs to introduce an isolate boundary. (In our case we need to isolate the `AppChrome` from the `AppOutlet`.) The purpose tho the isolate is to create few rules. 1. Objects are only allowed to cross from outside of isolate into isolate. (In our case `AppChrome` can create objects which `AppOutlet` can consume. The opposite is not allowed. `AppOutlet` can't create objects which `AppChrome` can consume. Part of the reason is that the parent of isolate has longer life-span that the content of isolate) 2. Any objects which cross from outside into the isolate needs to be named. (This solves the objects id stability and CDN cacheability) 3. The isolate is not allowed to mutate the object which is passed into it. (This ensures that the `AppOutlet` can run on server and can then transfer to the client without any surprises.) 4. An isolate acts as serialization context. (`qDehydrate` will store it's state in as a child of the isolate.) Isolate is really an interface, and any isolate implementation can fit into it. ## Example ```typescript= interface User { first: string; last: string; } const AppOutlet = qIsolate<{user: User}>(); const AppChrome = qComponent<{}, {user: User}>({ tagName: 'app-chrome', onMount: qHook(() => {user: {first: 'Qwik', last: 'Builder'}}), onRender: qHook((props, user) => ( <> <span>Hello {user.last}, {user.first}!<span> <AppOutlet path="./app-outlet" context={{user: {state.user}}}/> </> )) }) ``` ```htmlembedded= <app-chrome on:q-render="..." q:obj=":3doeu"> <span>Hello Builder, Qwik!</span> <q:isolate render="./app-outlet" context="*89ou9e" q:obj="89ou9e"/> </app-chrome> <script type="text/qwik"> { "89ou9e": { "user": ":3doeu", }, ":3doeu": { first: 'Qwik', last: 'Builder' } } </script> ``` The above will let the client make request to the server like so: ``` http://localhost/some-path/app-outlet?user={"first":"Qwik","last":"Builder"} ``` The server executes this: ```typescript= const AppOutlet = qComponent<{user: User}, {adjective: string}>({ tagName: 'app-outlet', onMount: qHook(() => ({adjective: 'awesome'})) onRender: qHook((props, state) => ( <span>{props.user.first} is {state.adjective}!</span> )) }) ``` The resulting HTML may be something like this? ```htmlembedded= <app-outlet on:q-render user='{"first":"Qwik","last":"Builder"}' q:obj=":8uo3"> <span>Qwik is awesome!</span> </app-outlet> <script type="text/qwik"> { ":8uo3": { adjective: 'awesome' } } </script> ``` # FAQs: - **Q**: How does `AppOutlet` send data to the `AppChrome`? - **A**: Use `qEvent` and `qBubble` to communicate with the parent. - **Q**: The `AppOutlet` in `const AppOutlet = qIsolate<{user: User}>();` is an interface, and the `AppOutlet` in `const AppOutlet = qComponent<{user: User}, {adjective: string}>...` is an instance of the interface, right? How about renaming the latter for clarification? - **A**: