Skip to content

Instantly share code, notes, and snippets.

@neurosnap
Last active January 20, 2024 02:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save neurosnap/d552045dafcc8cc3bd6e3ab865701a17 to your computer and use it in GitHub Desktop.
Save neurosnap/d552045dafcc8cc3bd6e3ab865701a17 to your computer and use it in GitHub Desktop.
/*
* This is an experimental view library that would compete with react, svelte, qwik.js, vue.js etc.
*
* Implementation reference: https://git.sr.ht/~erock/alpfx
*
* Features:
* - Instead of `view = func(state)` we have `view = func(event)`
* - This paradigm shift will:
* - make prop mgmt (e.g. when to update component) more manual
* - animations easier (events or transitions are sent to the component that the end-user
* can control)
* - Doesn't pretend to be declarative, there's no difference between how a component is
* rendered and what happens when user triggers event
* - They both function by responding to events
* - Components are pure functions -- no side-effects allowed
* - Components can return:
* - A command to perform IO
* - An array that represents HTML for a component
* - Both
* - `show` prop will conditionally render element
* - All state management for library and end-users are stored in a single global store (e.g. redux)
* - There is no local component state
* - Commands are generator functions that also do not contain side-effects but instead
* instruct the library runtime to perform IO side-effects. (ala redux-saga)
* - No JSX, just TS
* - With a global state mgmt system, we could add resumability on the server (ala qwik.js)
* - We could have a compilation step that figures out the different states a component could be in
* and then do manual DOM manipulations (ala svelte)
* - This would mean there's no virtual DOM
* - Async rendering, suspense, etc, is a bunch of overhead we then don't need to implement
*/
import type { Store } from './types';
interface MyState {
show: boolean;
movies: { id: string; title: string }[];
movieProps: { title: string };
}
const initMsg = () => ({ type: 'init', payload: null });
// imaginary function that will emit events specific to a component
const emit = (type: string, ...args: any[]) => {};
// imaginary function that will describe how an async function will by called by the runtime
const call = (...args: any[]) => {};
function* cmdState<S extends Store = Store>(store: S, key: string, value: any) {
const next = { key, value };
store.dispatch({ type: 'UPDATE_STATE', payload: next });
}
function Toggle(store: Store<MyState>, msg = initMsg()) {
const { show = false } = store.getState();
if (msg.type === 'click') {
return cmdState(store, 'show', !show);
}
return [
'div',
[
['button', { click: true }, show ? 'Collapse' : 'Expand'],
['div', { show }, 'This is some content'],
],
];
}
function* cmdFetch(url: string) {
yield emit('loading');
const resp = yield call(fetch, url);
if (!resp.ok) {
yield emit('error');
}
const payload = yield call([resp, 'json']);
yield emit('success', payload);
}
function Movies(store: Store<MyState>, msg = initMsg()) {
switch (msg.type) {
case 'init':
return [cmdState(store, 'movieProps', msg.payload), cmdFetch('/movies')];
case 'props':
// also have access to msg.payload.prev
return cmdState(store, 'movieProps', msg.payload.next);
case 'loading':
return ['div', 'loading'];
case 'error':
return ['div', `error: ${msg.payload}`];
case 'success':
return [['div', 'success!'], cmdState(store, 'movies', msg.payload)];
default:
const { movies = [], movieProps } = store.getState();
return [
'div',
[
['div', movieProps.title],
movies.map((movie) => ['div', { key: movie.id }, movie.title]),
],
];
}
}
export function App() {
return ['div', [[Toggle], ['hr'], [Movies, { title: 'nice' }]]];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment