Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make your own TEA #1

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open

Make your own TEA #1

wants to merge 14 commits into from

Conversation

eeue56
Copy link
Owner

@eeue56 eeue56 commented Dec 11, 2022

This pull request demonstrates how to build an Elm-style event loop in TypeScript, as is used in Derw. The Elm Architecture was implemented by other JavaScript libraries such as Redux. This PR approaches the core loop behind the idea, along with examples on how to limit the API so only valid elements can be created. It also has support for hydration, with an example, and async event handling. There is also a way to subscribe to state changes inside the update loop.

The way to consume this PR is to go through commit by commit, where there is a long message in the commit, along with lots of inline comments explaining the different parts of the code.

To run it locally, clone the repo then use esbuild --bundle src/index.ts --outfile=build/index.js

For more info see the blog post: https://derw.substack.com/p/make-your-own-tea-the-elm-architecture?sd=pf

The Elm archiceture, otherwise known as TEA, is a model for handling interactive user interfaces. This concept was popularized in the frontend world by Redux, though Elm got there first. The idea is simple: every program has state, known as the model, a way of modifying the model, known as update, and finally a way to represent the model and allow for interactions known as the view. The model should be immutable - the update function should return a modified copy of the model. There are two many user-provided types: model, and message. The model type is state. The messages are triggered by interactions - for example, ClickOnSubmit.

This pattern is best applied to single page applications, where it makes sense to have a runtime that can render different content based on the model. In this initial commit, only the structure is provided. Later commits will introduce the flow that enables this pattern to be used.
To render content into the DOM, it's handy to have an Abstract Syntax Tree (AST) which represents the content to be rendered. In Elm, this is provided via the Html package. In React, JSX provides a user-familiar syntax while still turning into Reacty type stuff. In this example, we create our AST with 3 constructor functions that users can call - instead of using another templating language, make-your-own-tea focuses on everything being a function. Once we have our AST, we can use that the render and fill the DOM with actual elements.
Now it's time to introduce event handlers to our AST. Each node can have events attached to it. An event is composed of a name of an event to listen for, and the callback that will take event data then produce a <message> that can be fed into the update loop. In order to modify the event handlers attached to the DOM elements, we store the event handlers with their IDs on the nodes. This allows us to remove the event handler. Patching is used in order to avoid fully rerendering the existing DOM, and instead only apply changes between the currently rendered version and the next.
We move the valid tag types out into a seperate union, and reduce code repetition by using a helper to construct nodes. This refactor helps us prepare for adding support for more tags.
In order to get text from a user, we add an input node that contains user-input.
Attributes allow the DOM elements to change their settings. Attributes fall into two groups: attributes set via =, and properties set by modifying the properties of the DOM node in JavaScript. For now we just add support for attributes that have string values. Different tags support different attributes, but we'll leave that as an exercise for the reader.
To give a more realistic of example of how to use the framework, we move the code out from being one giant view function into several smaller functions.
Boolean attributes are those that are either true or false. A great example is a checkbox.
To send a message asynchronously to the update loop, we add a callback that can be given a message.
To prevent unnecessary re-renders or re-draws, we add better support for diffing between previous and current attributes.
In order to either server-side render content, or to do a simple one time render without an update loop, we add a way to render Html as a string.
In order to add an event loop to a statically rendered DOM, we provide a hydration function which will attach events and take over the drawing loop.
To provide functions outside of the update loop with model changes and messages, we expose a subscription function.
There are a number of tags which cannot have children. We represent these in our AST with VoidNode.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant