# Language-agnostic Svelte SSR?
**Motivation**: Currently SvelteKit only lives in JavaScript ecosystem. As a "disappearing" framework, it would be particularly useful if everything authored in Svelte can be compiled once and rendered statically everywhere, even in another programming language. The server sends the rendered skeleton, and expects rehydrations at client-side.
## Rough ideas
**Input**: An SSR-ready svelte component processed with some specialized renderer + static JSON data to be rendered.
**Output**: A script (TBD, but should be simple enough to be evaluated) that the *host* can read and generate HTML/JS/CSS that is ready to be put into a page. Should any variable not being resolved in the server, an additional script is delivered to "glue" them in a browser runtime for (re-)hydration.
> An SSR-ready component should always be a valid Svelte component. If we must break the rule, it should stop at the top level.
> Though streaming is preferred, putting a string in bulk is fine for now.
A Svelte component compiled on server-side, which is always our case, uses `generate: "ssr"`:
> `generate` (default `"dom"`): If "dom", Svelte emits a JavaScript class for mounting to the DOM. If "ssr", Svelte emits an object with a render method suitable for server-side rendering. If false, no JavaScript or CSS is returned; just metadata.
The client-side code, which is compiled seperately, always re-hydrate (`hydratable: true`).
## <abbr title="minimum viable product">MVP</abbr>
Ingredients:
* Latest CherryPy
* [hn.svelte.dev](https://github.com/sveltejs/sites/tree/ddd6c30e03e1af16b8189f745036ce8d007f1918/sites/hn.svelte.dev)
* A (tentative) JS expression evaluator in Python, when given a JS expression AST (ESTree format emitted by Svelte parser), it can evaluate it statically to yield a piece of JSON data. [The expression spec. in Nunjucks](https://mozilla.github.io/nunjucks/templating.html#expressions) is a prominent effort on this in reverse, parsing a Python-like expression in a JS environment.
> It should be enough to support imports and prop initializations plus some number/string/array/object operations, I think?
> * js2py: https://github.com/PiotrDabkowski/Js2Py
> * QuickJS: https://github.com/bellard/quickjs
# Anatomy
An SSR component is compiled down to a factory call to `create_ssr_component`: https://sourcegraph.com/github.com/sveltejs/svelte@v3.58.0/-/blob/src/runtime/internal/ssr.ts?L141. We will abbreviate it `c_s_c`.
An SSR component contains the following JS code. You can see it in action in [Svelte REPL](https://svelte.dev/repl/hello-world?version=3.58.0) by setting `generate` to `"ssr"` in **Compiler options**.
* CSS-in-JS if enabled: `const #css = ...`
* extracted JS at module level: `component.extract_javascript(...)`
* decls of fully hoisted consts (?)
* const {name} = `c_s_c(($$result, $$props, $$bindings, #slots) => {...})` -- see below
* export default {name}
Note that the banner and imports to helpers like `escape` are inserted in a later pass.
In the callback function of `c_s_c`, the code structure is like:
* A `let` for each `injected_reactive_declaration_vars`
> If a statement consists entirely of an assignment to an undeclared variable, Svelte will inject a let declaration on your behalf. -- [docs](https://svelte.dev/docs#component-format-script-3-$-marks-a-statement-as-reactive)
* A `let $$restProps = compute_rest_props($$props, name)` if rest props are used
* A `let $$slots = compute_slots(slots)` if slots are used
* Reactive store decls:
* `let xxx`
* `let $$subscribe_$xxx`
* Reactive store subs:
* `validate_store` in dev mode
* `let $$unsubscribe_$xxx`
* Instance JS: basically everything in the `<script></script>` block
* parent bindings: `if ($$props.name === void 0 && $$bindings.name && name !== void 0) $$bindings[export_name](name);`
* Add CSS to global state if enabled: `$$result.css.add(#css);`
> `$$result.css` is a `Set`, see below.
* The `main` block
* reactive decls (don't confuse them with reactive store decls!)
* if it has any bindings: wrap this part with a loop; rerender until it settles
* reactive store unsubs
* return the HTML template literal, evaluating all expressions within
Finally, the function `c_s_c` returns an object:
```javascript
{
render(props, {$$slots, context}) => {html, css, head},
$$render(result, props, bindings, slots, context) => html,
}
```
The `render` is only called at top level, and it calls `$$render`, executes the callback, and then returns the HTML: `const html = fn(result, props, bindings, slots);`
* `result` is the global state. The initial state is `{html: '', head: '', css: new Set()}`.
* `bindings` is an Object with keys being variable names and values being setters, to propagate bindings to parent.
* `slots` are similar to bindings, except for values being getters.
Finally, run all collected `onDestroy` functions. Then the result is ready for the consumer to output to a browser client. As far as I know, the only consumer is SvelteKit.
## Renderer
The [renderer for SSR](https://github.com/sveltejs/svelte/blob/v3.58.0/src/compiler/compile/render_ssr/Renderer.ts) maintains a stack and a current *quasi* (a string part of a template literal). An `add_string` appends the string to the current string. An `add_expression` flushes the current string as a quasi, then append the expression. For example, the following rendering procedure
```javascript
const opts = {}
// in the constructor, a `push` is automatically called
const r = new Renderer(opts)
/* the following section is typically done
* when calling r.render(nodes, opts) */
r.add_string('the answer ') // add a quasi
r.add_string('of everything is ') // add another quasi
r.add_expression('6 * 7') // flush, push an expression,
// and then pop
r.add_string(' :)')
console.log(r.pop())
```
will generate a template expression
```javascript
`the answer of everything is ${6 * 7} :)`
```
A more complicated example is:
```javascript
`<p>The <code>${escape(name)}</code> package is ${escape(speed)} fast.
Download version ${escape(version)} from <a href="${"https://www.npmjs.com/package/" + escape(name, true)}">npm</a>
and <a${add_attribute("href", website, 0)}>learn more here</a></p>`;
```
One obvious drawback is that we cannot run JS functions, so function calls in templates used in prerendering process must be detected and be emulated in host. Can we completely ignore Instance JS? Sadly no, since the default value of props are specified there. (but if we assume props are all simple values, basic static analysis suffices)
Now we can appreciate the invention of filter/pipe constructs (c.f. function calls), whose interfaces are simple enough so that any host can interpret them with ease.
> As of v3.58.0, here is the list of all used helper functions by the renderer that the host needs to implement (search-based! [ref.](https://github.com/sveltejs/svelte/blob/v3.58.0/src/runtime/internal/index.ts))...
>
> ```javascript
> // need to polyfill this
> Object.assign = (obj, ...args) => obj
>
> function add_attribute(name, value, boolean)
> function add_classes(classes)
> function add_styles(style_object)
> function compute_rest_props(props, keys)
> function compute_slots(slots)
> function create_ssr_component(fn)
> function debug(file, line, column, values)
> function each(items, fn)
> function escape(value, is_attr)
> function escape_attribute_value(value)
> function escape_object(obj)
> function is_promise(value)
> function is_void(name)
> function merge_ssr_styles(style_attribute, style_directive)
> const missing_component = { $$render }
> function noop()
> function null_to_empty(value)
> function spread(args, attrs_to_add)
> function subscribe(store, ...callbacks)
> function validate_component(component, name)
> function validate_dynamic_element(tag)
> function validate_store(validate_store)
> function validate_void_dynamic_element(tag)
> ```
A few questions I am pondering:
* How can one control the granularity of SSR?
* How is "unknown" data handled? e.g., at a if block where the condition cannot be evaluated, do we evaluate both branches?
* Design a way to determine whether it is in browser mode (or SSR). This has already [been implemented in SvelteKit](https://kit.svelte.dev/docs/modules#$app-environment) by `import { browser, building } from '$app/environment'`.
# Materials
* SSR CLI: https://gist.github.com/andy0130tw/a39a0e45ee12ca999a230e71032440f4
# An alternative method -- "Micro-service" approach
For reference: https://inertiajs.com/
* Python receives a request
* Python fetch data && get which template to render
* Request the underlying NodeJS server to process it
* Receive the HTML && state -> respond to client
# --- Draft below ---
At the top level, Python code can specify any exported-non-const variable to be replaced to static JSON data, and they are to be inlined to the template code. This way, the inlining can be done more aggressively (and it's the main purpose of SSR).
How to be streaming-friendly?
How will the glue code look like?
Duplicate data? (full data in HTML+JS)
How to build a child component: A component refers to its child-component by `import`-ing, and is compiled into its call site to something like...
```javascript
validate_component(Item, "Item").$$render(
$$result,
{ id }, // props
{ // getters for bindings
id: $$value => {
id = $$value;
$$settled = false;
}
},
{ // slots
default: () => {
return `${escape(i + 1)}: ${escape(name)}`;
}
}
)
```
> `validate_component` merely asserts whether its first parameter has a `$$render` method.
> https://github.com/Rich-Harris/code-red
> Bi-directional programming?
## Streaming
For an example of a new "render" mode, see https://github.com/sveltejs/kit/discussions/4831. But it's generally hard. For reference on stream-able source, [see Marko's SSR code](https://markojs.com/try-online/).