Strong Typing in AppRun

Yiyi Sun
8 min readMay 18, 2019

--

Introduction

AppRun is a JavaScript library for developing high-performance and reliable web applications using the elm inspired architecture, events, and components. You can write JavaScript code and use AppRun without strong typing. Strong typing is optional when using AppRun.

However, if you choose to use strong typing, AppRun ships with a type definition file, apprun.d.ts since the first release. You can use the type definition file for strong typing with TypeScript. Strong typing gives you many benefits such as compile-time validation of your code, which you will see below. This post is for those want to opted-in TypeScript and strong typing.

Let’s start with exploring where strong typing is applicable in AppRun applications.

AppRun Architecture

AppRun is an event system, a state management system, and a virtual DOM rendering system that runs the applications. We divide our application logic into three declarative parts and let AppRun manages and runs the applications.

State View and Update

All AppRun applications have three parts: state, view, and update. (Figure 1)

  • State (a.k.a. Model) — the state of your application
  • View — a function to display the state
  • Update — a collection of event handlers to update the state
Figure 1, An AppRun Application

AppRun Event Life Cycle

We pass the three parts to the app.start function. AppRun starts to wait for AppRun events. When AppRun events are published, AppRun manages the event life cycle. (Figure 2)

Figure 2, AppRun Event Life Cycle

The AppRun event life cycle has the following steps:

  • AppRun dispatches the events to the event handlers defined in the update along with the current state.
  • The event handlers create a new state based on the current state.
  • AppRun passes the new state to the view function.
  • The view function creates HTML or a Virtual DOM.
  • AppRun renders the HTML/Virtual DOM to the screen
  • AppRun calls the optional rendered function to complete the AppRun event life cycle.

Web programming is also event-driven. We can subscribe to the DOM events and then publish the AppRun events. Often it likes converting DOM events to AppRun events.

You can try the above application in the AppRun Playground to feel the AppRun event life-cycle.

Needs for Strong Typing

From the AppRun architecture and AppRun event life cycle, we can see there are two places require strong type: state and event names.

  1. From the initial state, the state passed between the view function and event handlers requires to be strongly typed
  2. The event names for publishing and subscribing Apprun events require to be strong typed

Strong Typing for State

Typed View

Throughout the AppRun event life cycle, we can make the state strongly typed. First, we import the View type from AppRun and apply it to the view function.

const view: View<number> => state => <div>...</div>

The View type imported from AppRun is a generic type. It makes the view function into a generic function, which let us define the type of its parameters. E.g., we define the view function to be View<number>, TypeScript recognizes the state parameter of the view function is number. (Figure 3)

Figure 3, Typed View

Typed Update

Next, let’s import the Update type from AppRun and apply it to the update object.

const update: Update<number> = { ... }

The Update type imported from AppRun is also a generic type. It makes the event handler functions in the update object into a generic function. TypeScript recognizes the state parameter of the event handler functions are number. (Figure 4)

Figure 4, Typed Update

Typed Application

Finally, let’s make the application strongly type. The app.start is generic function already. If we give the app.start function a type, E.g., number, it means the state of the application is number, the view of the application is View<number> and the update of the application is Update<number>.

app.start<number>( ... )

If the type of state does not match the type assigned to the app.start function, TypeScript catches the type error. (Figure 5)

Figure 5, Catch State Type Error

If the type of the view function does not match the type assigned to the app.start function, TypeScript catches the type error. (Figure 6)

Figure 6, Catch View Type Error

If the type of the update object does not match the type assigned to the app.start function, TypeScript catches the type error. (Figure 7)

Figure 7, Catch Update Type Error

You have seen we have made the strongly typed state to the view function, the event handlers in the update object, and the application. Next, we will move to make strongly typed events.

Strong Typing for Events

AppRun update object is a named collection or dictionary of the event handlers. The event names are strings, such as +1 and -1. They are not typed, not checked by the compiler, and not IDE friendly. We can use TypeScript enum to define the event names.

Enum for Event Names

We define the Events enum and use it in the view functions and the update object. (Figure 8).

enum Events { inc = '+1', dec = '-1' }
Figure 8, Enum for Event Names

You can see when we type ‘Events.’, Visual Studio code provides the intelliSense to help us typing. However, you can also see that the string +0 is still allowed in the update object. This is because the object index can only be number or string. Or as long as number or string is used, the compiler is happy and has no more type checking.

Update Tuple

Starting 1.19.0, AppRun introduced update as an array of tuples for defining AppRun event handlers. (Figure 9)

Figure 9, Update Tuple

The event handler tuple has two fields: the event name and the event handler function.

With the tuple, we can use two advanced TypeScript types: union type and string literal type to create so call discriminated union. For more information about union type, string literal type, and discriminated union please refer to the TypeScript handbook.

Discriminated Union Typed Event Names

First, we define a union type called Events, which is made out of two string literal types, -1 and +1.

type Events = '-1' | '+1';

Then, we use Events type as the second generic of the Update type imported from AppRun and apply to update tuple.

const update: Update<number, Events> = [ ... ]

The event names are strongly typed. TypeScript can even provide code auto-completion (IntelliSense) to hint the event names. (Figure 10)

Figure 10, Typed Update Tuple

If the event name is not one of the strings defined in Events type, TypeScript catch the error. (Figure 11)

Figure 11, Catch Event Name Error in Update Tuple

Typed Event Names in View

To make the event names in the view function strongly type, we need to make the event names of the application strongly type first. We can add the Events type to the app.start generic function.

app.start<number, Events>(...)

It makes the application can only trigger the events that are defined in the Events type. (Figure 12)

Figure 12, Typed Event Names in Application

Because we have made the event names strongly typed TypeScript provides intelliSense to the event names the application. (Figure 12) TypeScript also catches the event name errors in the view function. (Figure 13)

Figure 13, Catch Event Name Error in View

So far, we have accomplished strong typing for AppRun global application. Next, we will implement strong typing for AppRun Components.

Strong Typing for Component

AppRun Component is like a scoped AppRun application. It has also three parts: state, view, and update, but the three parts are scoped inside the component. AppRun Component can be used as application building blocks like in many other frameworks and libraries.

AppRun Component is a generic class. We create our components by extending the AppRun Component class. We can add the state type and events type to the Component class, as well as to the view function and update tuple to make the component strongly typed so that TypeScript can provide IntelliSense and type validation while we are coding. (Figure 14)

Figure 14, Typed State and Type Event Names in Component

Typed Event Decorator

In addition to the update object and update tuple, we can the on decorator to mark a function field to be an AppRun event handler. The on decorator is also generic. When we add the event type to the on decorator, TypeScript recognizes and enforces the event names to be the event type. (Figure 15)

Figure 15, Typed On Decorator

Typed $on Directive

Finally, starting AppRun 1.18.0 AppRun introduced the Directive, a syntax sugar for JSX. E.g., the $on directive is useful for converting DOM events to AppRun events. In AppRun 1.19.0 the $on directive links the DOM events to class functions and triggers the AppRun event life cycle without explicit event names. And it is strongly typed. (Figure 16)

Figure 16, Typed $on Directive

Conclusion

Thanks to TypeScript, we can have strongly typed state and event names in the AppRun application by using enums, generic types, and discriminated types. Starting AppRun 1.19.0, we also have an alternative approach, which is to use the $on directive to hide the event names so that we only need to focus on the state type.

Happy coding!

--

--