add ssr-counter example demonstrating SSR + hydration end-to-end

Counter.el defines Counter, CounterPair, and App components with {#if}
conditionals, event handlers, and child component composition.

render.js (generated by elc-ui --target=server) is the Node.js render
script the server calls. app.js (generated by elc-ui --target=web) is the
compiled browser module. hydrate.js attaches the runtime to the
server-rendered DOM without replacing it.

index.html shows the integration point with instructions for injecting
server output and the two CLI commands needed to regenerate both targets.
This commit is contained in:
Will Anderson
2026-05-04 12:49:19 -05:00
parent f8e32e7719
commit 143137e2b4
6 changed files with 632 additions and 10 deletions
+119
View File
@@ -0,0 +1,119 @@
import { Component, Graph, Renderer, Router, mount } from '../../dist/el-ui.js';
class Counter extends Component {
constructor(props = {}) {
super();
this.props = props;
this._graph = new Graph();
this._stateNodes = {};
this._state = {};
this._stateNodes['count'] = this._graph.seed({ type: 'state', name: 'count', content: 0 });
this._state['count'] = 0;
this._stateNodes['history'] = this._graph.seed({ type: 'state', name: 'history', content: "" });
this._state['history'] = "";
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();
});
}
}
setState(name, value) {
if (this._stateNodes[name] !== undefined) {
this._graph.update(this._stateNodes[name], value);
}
}
increment() {
const __self = this;
const count = this._state['count'];
const history = this._state['history'];
const initialValue = this.props['initialValue'] !== undefined ? this.props['initialValue'] : 0;
const label = this.props['label'] !== undefined ? this.props['label'] : "Counter";
__self.setState('count', count + 1)
__self.setState('history', history + "+" + count + " ")
}
decrement() {
const __self = this;
const count = this._state['count'];
const history = this._state['history'];
const initialValue = this.props['initialValue'] !== undefined ? this.props['initialValue'] : 0;
const label = this.props['label'] !== undefined ? this.props['label'] : "Counter";
__self.setState('count', count - 1)
__self.setState('history', history + "-" + count + " ")
}
reset() {
const __self = this;
const count = this._state['count'];
const history = this._state['history'];
const initialValue = this.props['initialValue'] !== undefined ? this.props['initialValue'] : 0;
const label = this.props['label'] !== undefined ? this.props['label'] : "Counter";
__self.setState('count', 0)
__self.setState('history', "")
}
render() {
const __self = this;
const count = this._state['count'];
const history = this._state['history'];
const initialValue = this.props['initialValue'] !== undefined ? this.props['initialValue'] : 0;
const label = this.props['label'] !== undefined ? this.props['label'] : "Counter";
return `<div class="counter" data-el-tag="div"><h2 class="counter-label" data-el-tag="h2">${label }</h2><div class="count-display" data-el-tag="div"><span class="${count > 0 ? "count positive" : count < 0 ? "count negative" : "count zero"}" data-el-tag="span">${count }</span></div><div class="buttons" data-el-tag="div"><button class="btn btn-decrement" data-el-click="() => decrement()" data-el-tag="button">-</button><button class="btn btn-reset" data-el-click="() => reset()" data-el-tag="button">reset</button><button class="btn btn-increment" data-el-click="() => increment()" data-el-tag="button">+</button></div>${(history != "") ? `<div class="history" data-el-tag="div"><span class="history-label" data-el-tag="span">history:</span><code data-el-tag="code">${history }</code></div>` : ``}${(count == 0) ? `<p class="hint" data-el-tag="p">Press + or - to begin.</p>` : ``}</div>`;
}
}
class CounterPair extends Component {
constructor(props = {}) {
super();
this.props = props;
this._graph = new Graph();
this._stateNodes = {};
this._state = {};
}
setState(name, value) {
if (this._stateNodes[name] !== undefined) {
this._graph.update(this._stateNodes[name], value);
}
}
render() {
const __self = this;
return `<div class="counter-pair" data-el-tag="div">${__self._child(Counter, { label: "Left" })}${__self._child(Counter, { label: "Right" })}</div>`;
}
}
class App extends Component {
constructor(props = {}) {
super();
this.props = props;
this._graph = new Graph();
this._stateNodes = {};
this._state = {};
}
setState(name, value) {
if (this._stateNodes[name] !== undefined) {
this._graph.update(this._stateNodes[name], value);
}
}
render() {
const __self = this;
return `<div class="app" data-el-tag="div"><header class="app-header" data-el-tag="header"><h1 data-el-tag="h1">el-ui SSR + Hydration</h1><p class="subtitle" data-el-tag="p">Server-rendered, client-hydrated with spreading activation</p></header><main class="app-main" data-el-tag="main">${__self._child(Counter, { label: "Main Counter" })}${__self._child(CounterPair, { })}</main></div>`;
}
}
export { Counter, CounterPair, App };