diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2024-01-23 00:09:45 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-23 00:09:45 +0000 |
commit | 24f6962aa457d32319756f6217aafde7b0a9c752 (patch) | |
tree | 42119d9b073f56eabe9dda4ae2065ef4b2086e6a /src/server-runtime.ffi.mjs | |
parent | 45e671ac32de95ae1a0a9f9e98da8645d01af3cf (diff) | |
download | lustre-24f6962aa457d32319756f6217aafde7b0a9c752.tar.gz lustre-24f6962aa457d32319756f6217aafde7b0a9c752.zip |
✨ Add universal components that can run on the server (#39)
* :heavy_plus_sign: Add gleam_erlang gleam_otp and gleam_json dependencies.
* :sparkles: Add json encoders for elememnts and attributes.
* :sparkles: Add the ability to perform an effect with a custom dispatch function.
* :construction: Experiment with a server-side component runtime.
* :construction: Expose special server click events.
* :construction: Experiment with a server-side component runtime.
* :construction: Experiment with a server-side component runtime.
* :construction: Experiment with a server-side component runtime.
* :construction: Create a basic server component client bundle.
* :construction: Create a basic server component demo.
* :bug: Fixed a bug where the runtime stopped performing patches.
* :refactor: Roll back introduction of shadow dom.
* :recycle: Refactor to Custom Element-based approach to encapsulating server components.
* :truck: Move some things around.
* :sparkles: Add a minified version of the server component runtime.
* :wrench: Add lustre/server/* to internal modules.
* :recycle: on_attribute_change and on_client_event handlers are now functions not dicts.
* :recycle: Refactor server component event handling to no longer need explicit tags.
* :fire: Remove unnecessary attempt to stringify events.
* :memo: Start documeint lustre/server functions.
* :construction: Experiment with a js implementation of the server component backend runtime.
* :recycle: Experiment with an API that makes heavier use of conditional complilation.
* :recycle: Big refactor to unify server components, client components, and client apps.
* :bug: Fixed some bugs with client runtimes.
* :recycle: Update examples to new lustre api/
* :truck: Move server demo into examples/ folder/
* :wrench: Add lustre/runtime to internal modules.
* :construction: Experiment with a diffing implementation.
* :wrench: Hide internal modules from docs.
* :heavy_plus_sign: Update deps to latest versions.
* :recycle: Move diffing and vdom code into separate internal modules.
* :sparkles: Bring server components to feature parity with client components.
* :recycle: Update server component demo.
* :bug: Fix bug where attribute changes weren't properly broadcast.
* :fire: Remove unused 'Patch' type.
* :recycle: Stub out empty js implementations so we can build for js.
* :memo: Docs for the docs gods.
* :recycle: Rename lustre.server_component to lustre.component.
Diffstat (limited to 'src/server-runtime.ffi.mjs')
-rw-r--r-- | src/server-runtime.ffi.mjs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/src/server-runtime.ffi.mjs b/src/server-runtime.ffi.mjs new file mode 100644 index 0000000..e82e82d --- /dev/null +++ b/src/server-runtime.ffi.mjs @@ -0,0 +1,143 @@ +import { Ok, isEqual } from "./gleam.mjs"; +import { + AddRenderer, + Dispatch, + Event, + RemoveRenderer, + Shutdown, +} from "./lustre/runtime.mjs"; + +export class LustreServerApplication { + #queue = []; + #effects = []; + #didUpdate = false; + + #vdom = null; + #handlers = new Map(); + #renderers = new Set(); + + #model = null; + #update = null; + #view = null; + + static start(flags, init, update, view) { + const app = new LustreServerApplication(init(flags), update, view, root); + + return new Ok((msg) => app.send(msg)); + } + + // PUBLIC METHODS ------------------------------------------------------------ + + constructor([model, effects], update, view) { + this.#model = model; + this.#update = update; + this.#view = view; + this.#vdom = this.#view(this.#model); + this.#effects = effects.all.toArray(); + this.#didUpdate = true; + + globalThis.queueMicrotask(() => this.#tick()); + } + + send(action) { + switch (true) { + case action instanceof AddRenderer: { + this.#renderers.add(action[0]); + return; + } + + case action instanceof Dispatch: { + this.#queue.push(action[0]); + this.#tick(); + + return; + } + + case action instanceof Event: { + const [event, data] = action; + + if (this.#handlers.has(event)) { + const msg = this.#handlers.get(event)(data); + + if (msg.isOk()) { + this.#queue.push(msg[0]); + this.#tick(); + } + } + } + + case action instanceof RemoveRenderer: { + this.#renderers.delete(action[0]); + return; + } + + case action instanceof Shutdown: { + this.#shutdown(); + return; + } + + default: + return; + } + } + + // PRIVATE METHODS ----------------------------------------------------------- + + #tick() { + this.#flush_queue(); + + if (this.#didUpdate) { + this.#vdom = this.#view(this.#model); + + for (const renderer of this.#renderers) { + renderer.render(this.#vdom); + } + } + } + + #flush_queue(iterations = 0) { + while (this.#queue.length) { + const [next, effects] = this.#update(this.#model, this.#queue.shift()); + + this.#model = next; + this.#didUpdate ||= !isEqual(this.#model, next); + this.#effects = this.#effects.concat(effects.all.toArray()); + } + + while (this.#effects.length) { + this.#effects.shift()( + (msg) => this.send(new Dispatch(msg)), + (event, data) => this.emit(event, data) + ); + } + + if (this.#queue.length) { + if (iterations < 5) { + this.#flush_queue(++iterations); + } else { + window.requestAnimationFrame(() => this.#tick()); + } + } + } + + #shutdown() { + this.#model = null; + this.#queue = []; + this.#effects = []; + this.#didUpdate = false; + this.#update = () => {}; + this.#view = () => {}; + this.#vdom = null; + this.#handlers = new Map(); + this.#renderers = new Set(); + } +} + +export const start = (app, selector, flags) => + LustreClientApplication.start( + flags, + selector, + app.init, + app.update, + app.view + ); |