Building Applications with AppRun

AppRun is a lightweight library (3K) for developing applications using the Elm architecture, event publication and subscription, and components.

Application code using AppRun is clean, precious, declarative and without ceremony.

The Architecture

There are three separated parts in an AppRun application.

  • Model — the state of your application
  • View — a function to display the state as HTML
  • Update — a set of event handlers to update the state

The 15 lines of code below is a simple counter application demonstrates the architecture using AppRun.

simple counter example

The Model

The model can be any data structure: a number, an array, or an object that reflects the state of the application. In the simple counter example, it is a number.

const model = 0;

The Update

The update contains event handlers that take existing state and create new state.

const update = {
'+1': (state) => state + 1,
'-1': (state) => state — 1
}

BTW, using the on decorator, the update functions look even better.

@on('+1') increase = state => state + 1;
@on('-1') decrease = state => state - 1;

The View

The view generates HTML based on the state. AppRun parses the HTML string into virtual dom. It then calculates the differences against the web page element and renders the changes.

const view = (state) => {
return `<div>
<h1>${state}</h1>
<button onclick=’app.run(“-1”)’>-1</button>
<button onclick=’app.run(“+1”)’>+1</button>
</div>`;
};

Trigger the Update

app.run function from AppRun is used to trigger the update.

app.run(‘+1’);

It can be used in HTML markup directly:

<button onclick=”app.run('-1')”>-1</button>
<button onclick=”app.run('+1')”>+1</button>

AppRun exposes a global object named app that is accessible by JavaScript and TypeScript directly to trigger updates.

Start the Application

app.start function from AppRun ties Model, View and Update together to an element and starts the application.

const element = document.getElementById(‘my-app’);
app.start(element, model, view, update);

The three functions from AppRun (app.run, app.start and app.start) are all you need to make an AppRun application.

Try it online: Simple Counter.

State Management

Internally AppRun manages application states. It triggers Update; passes the new state created by Update to View; renders the HTML/Virtual DOM created by View to the element. It also maintains a state history. The application can have the time travel / undo-redo feature by

  • Make Update create immutable state
  • Enable AppRun state history
multiple counters example

Try it online: Multiple Counters

You can also check out more AppRun examples on glitch.com: https://glitch.com/@yysun

Virtual DOM

When applications get complex, we start to think performance and build system.

In the simple counter example above, the View creates HTML string out of the state. Although HTML string is easy to understand and useful for trying out ideas, it takes time to parse it into virtual dom at run time, which may cause performance issue. It also has some problems that have been documented by the Facebook React team: Why not template literals.

Using JSX, the JSX compiler compiles the JSX into functions at compile time. The functions creates virtual dom in run time directly without parsing HTML, which gives better performance. Therefore, we recommend using JSX in production. To compile and build production code, we recommend using TypeScript and webpack

AppRun CLI

Both TypeScript and webpack are advanced, feature-rich development tools that have many configuration options. But to find out the best configuration for TypeScript and webpack from scratch is difficult. Also, to configure TypeScript and webpack for every single project is a tedious task.

Like many other frameworks and libraries, AppRun comes with a CLI. AppRun CLI can initialize project, configure TypeScript and webpack, scaffold project folders and files, run tests, and run the development server.

Let’s install AppRun and use its CLI to initialize a project.

npx apprun -i

The apprun -i command installs apprun, webpack, webpack-dev-server and typescript. It also generates files: tsconfig.json, webpack.config.js, index.html and main.tsx.

After the command finishes execution, you can start the application.

npm start

Component

Another thing to consider when applications get complex is to divide and organize code into components.

A component in AppRun is a mini model-view-update architecture, which means inside a component, there are model, view and update. Let’s use AppRun CLI to generate a component.

apprun -c Counter

It generates a Counter component:

import app, {Component} from ‘apprun’;
export default class CounterComponent extends Component {
state = ‘Counter’;
view = (state) => {
return <div>
<h1>{state}</h1>
</div>
}
update = {
‘#Counter’: state => state,
}
}

To use the Counter component, create an instance of it and then mount the instance to an element.

import Counter from ‘./Counter’;
const element = document.getElementById(‘my-app’);
new Counter().mount(element);

Notice the update has a ‘#Counter’ function. It is a route.

Routing

The third thing to consider in complex applications is routing. AppRun has an unique way of handling routing. It detects the hash changes in URL and calls functions in update by matching the hash. E.g. when URL in the browser address bar becomes http://..../#Counter, The #Couter update function of the components will be executed.

Each component defines its route in an update function. Once the URL is changed to the route the component defined, the update function is triggered and executed. It can avoid a lot of code for registering and matching routes like in the other frameworks and libraries.

The AppRun demo application was built to have 8 components that are routed into one element.

Event PubSubs

At core, AppRun is an event publication and subscription system, which is also known as event emitter. It is a commonly used pattern in JavaScript programming.

AppRun has two important functions: app.run and app.on. app.run fires events. app.on handles events. E.g. :

Module A subscribes to an event print:

import app from ‘apprun’;
export () => app.on(‘print’, e => console.log(e));

Module B publishes the event print:

import app from ‘apprun’;
export () => app.run(‘print’, {});

Main Module:

import a from ‘./A’;
import b from ‘./B’;
a();
b();

Module A and Module B only have to know the event system, not other modules, so they are only dependent on the event system, not other modules. Therefore modules are decoupled. Thus makes it easier to modify, extend and swap modules.

Use AppRun connect web page events to components. Then it’s up to your application to continue executing.

document.body.addEventListener(‘click’, e => app.run(‘click’, e) );
element.addEventListener(‘click’, e => app.run(‘click’, e));
<input onclick=”e => app.run(‘click’, e)”>

The biggest benefit of such event system is decoupling. In traditional MVC architecture, the model, view and controller are coupled, which makes it difficult to test and maintain. In result, there are many architecture patterns have been developed in order to solve the coupling problem, such as Model-View-Presenter, Presentation Model and Model-View-ViewModel.

AppRun solved the coupling problem by using event publication and subscription. Model,View and Controller/Update don’t know each. They only need to know how to publish and subscribe events by calling app.run and app.on of AppRun.

Even handling routing is just to subscribe to an event.

Conclusion

AppRun itself is lightweight. It is about 3K gzipped. More important is that it gives you a great development experience and makes your applications great.

I have created following videos to demonstrate the concepts described in the post. Enjoy!

Please give it a try and send comments / pull requests.