Archived
implement el-ui SSR engine with renderToString and hydration markers
Adds runtime/src/ssr.js: a Node.js SSR engine that parses .el component
source and renders to HTML strings without a DOM. Covers the full template
AST -- elements, text, interpolations, {#if}, {#each}, {#activate}, and
child component recursion.
Every component root element receives hydration markers:
data-el-component, data-el-id, data-el-state
Event bindings (on:click etc.) are preserved as data-el-* attributes so
the client hydration pass can bind them without re-rendering.
Updates Component base class with:
- initialState constructor param for SSR state recovery
- _hydrate(root): binds events on existing DOM, no re-render
- onHydrate() lifecycle hook
Updates dist/el-ui.js bundle to match.
This commit is contained in:
Vendored
+35
-3
@@ -351,15 +351,16 @@ export class Router {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Component base class & mount() ───────────────────────────────────────────
|
||||
// ── Component base class, mount(), and hydrate() ─────────────────────────────
|
||||
|
||||
export class Component {
|
||||
constructor(props = {}) {
|
||||
constructor(props = {}, initialState = {}) {
|
||||
this._graph = new Graph();
|
||||
this._stateNodes = {};
|
||||
this._state = {};
|
||||
this.props = props;
|
||||
this._renderer = null;
|
||||
this._ssrState = initialState;
|
||||
}
|
||||
|
||||
setState(name, value) {
|
||||
@@ -382,6 +383,20 @@ export class Component {
|
||||
|
||||
render() { return ''; }
|
||||
onMount() {}
|
||||
onHydrate() {}
|
||||
|
||||
_hydrate(root) {
|
||||
for (const [key, nodeId] of Object.entries(this._stateNodes)) {
|
||||
this._graph.subscribe(nodeId, (node) => {
|
||||
this._state[key] = node.content;
|
||||
if (this._renderer) this._renderer.patch();
|
||||
});
|
||||
}
|
||||
this._renderer = new Renderer(root, this);
|
||||
this._renderer._currentHtml = root.innerHTML;
|
||||
this._renderer._bindEvents();
|
||||
if (typeof this.onHydrate === 'function') this.onHydrate();
|
||||
}
|
||||
}
|
||||
|
||||
export function mount(ComponentClass, selector, props = {}) {
|
||||
@@ -394,4 +409,21 @@ export function mount(ComponentClass, selector, props = {}) {
|
||||
return component;
|
||||
}
|
||||
|
||||
export default { mount, Component, Graph, Renderer, Router };
|
||||
export function hydrate(selector, ComponentClass, props = {}) {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) throw new Error(`el-ui hydrate: element not found: ${selector}`);
|
||||
const stateJson = el.getAttribute('data-el-state');
|
||||
const initialState = stateJson ? JSON.parse(stateJson) : {};
|
||||
const instance = new ComponentClass(props, initialState);
|
||||
for (const [key, value] of Object.entries(initialState)) {
|
||||
if (instance._stateNodes[key] !== undefined) {
|
||||
const node = instance._graph.get(instance._stateNodes[key]);
|
||||
if (node) node.content = value;
|
||||
instance._state[key] = value;
|
||||
}
|
||||
}
|
||||
instance._hydrate(el);
|
||||
return instance;
|
||||
}
|
||||
|
||||
export default { mount, hydrate, Component, Graph, Renderer, Router };
|
||||
|
||||
Reference in New Issue
Block a user