AppRun ( https://apprunjs.org ) is a JavaScript library for developing applications using the Elm architecture, events, and components. It is an open source project released and published on GitHub ( https://github.com/yysun/apprun ) under the MIT license. The goal of introducing this library into the world, which already has many frameworks and libraries, is to make it simple for developers to build high-performance and reliable applications. The simplicity of AppRun brings many benefits. Developers can learn it easily and quickly develop product-ready applications. Developers also can maintain and improve the applications easily because they are architecturally simple. jQuery has ruled the development world for a long time because it is simple. However, jQuery lacks architectural rules for complex applications. Other frameworks and libraries have been invented for developing complex applications. However, then the development becomes more difficult, with more concepts to learn and more rules to follow.
In this chapter, you will learn about the technologies that other frameworks and libraries have introduced for developing complex applications that inspired AppRun. You will learn about the architecture concepts of AppRun using a counter application as an example; this is commonly used as an example with many other frameworks and libraries. Finally, you will take a look at how to make a static typed application using TypeScript if you prefer static typing to dynamic typing.
Background
Application development using JavaScript can be traced back to the 1990s when developers started to add dynamic content and interactions to static HTML web pages. Nowadays, JavaScript has evolved and become the ubiquitous language that powers not only web applications but also server applications, desktop applications, and mobile applications. Because of its widespread usage and its broad goal of serving the development needs of all platforms, we are now experiencing the phenomenon of JavaScript fatigue, which is the overwhelming situation of too many frameworks and libraries, too many tools, and too many APIs. Popular technologies are coming out so frequently that developers are finding it increasingly difficult to keep up with all the trends, and they worry that they will not able to leverage the latest and greatest technology. This book reveals the secret weapon to conquer the confusion and fear of JavaScript fatigue.
Keep it simple; always seek opportunities to simplify. Find out the real value of any new tools. These methods resulted in the birth of the new library AppRun.
Before getting into the technology, let me tell you about my personal experiences with the Disney theme park Epcot. Twenty years ago, when I visited Epcot for the first time, there was a hall that had a wall of machines to send e-mails. It was an amazing experience to send a couple of e-mails to China. It took two weeks to send a letter at that time. The funny sound that came from the modem was music to my ears. Ten years later, I revisited Epcot; the e-mail machines were replaced with digital photo-taking machines that could take and e-mail photos immediately. It was again an amazing experience. Fast-forward ten more years to today, and we now have high-resolution cameras on our mobile phones that can even remove wrinkles. And at Epcot, no new high-tech machines exist to excite us. In fact, the most interesting place to visit in Epcot is now the World Showcase, which contains scaled-down historical buildings, city streets, and restaurants of 11 countries. The traditional Africa streets, the Middle East market, and the French sweets are fantastic. All the ancient and old-fashioned stuff has beaten out the high-technology.
This story tells us that technology changes, but culture lasts. The real value of culture is buried in time. So, sticking to core concepts and finding out the true value of tools is the way to navigate through JavaScript fatigue.
Let’s review the JavaScript development history. JavaScript started as a scripting language running inside web pages. It is lightweight and doesn’t have a compiler. Everyone can code in JavaScript with or without a computer science degree or formal training. Developers would write functions in the web pages and refresh to see the result right away, and this ease of use significantly contributed the current success of JavaScript.
Despite that some senior developers, myself included, thought JavaScript was a toy or even a joke when compared with their estimated knowledge of enterprise architecture, layered approaches, and design patterns, the fact was that JavaScript spread widely quickly. jQuery and the jQuery plug-in ecosystem once ruled the JavaScript development world because jQuery made developing with JavaScript even easier by abstracting the details and differences of browser implementations. For example, handling button clicks just requires $().click(), and Ajax calls are handled with $.ajax(). Its ease of use and convenience are real values.
Of course, the concerns of senior developers were not nonsense. As soon as jQuery became successful, jQuery also became synonymous with spaghetti code. This is the direct result of it missing one of the core concepts of application development: application architecture. Application architecture is the discipline that guides application design, as defined in the Gartner IT Glossary. In the book Patterns of Enterprise Application Architecture, Martin Fowler explains application architecture as “The highest-level breakdown of a system into its parts.” Application architecture is not only the structure of the application but also the discipline for breaking down the application logic.
jQuery has provided a great deal of convenience but not architecture. It does not tell you how to break down and organize the application logic. Since jQuery’s release, the JavaScript community continues to improve not only the convenience but also the architecture. Continuous innovations have led to many new frameworks and libraries, such as Angular, React Vue, and Elm.
jQuery : Abstraction, ease of use
Angular: Components, modules, services, dependency injection, two-way data binding, strong typing, and template syntax
React: Components, one-way data binding, virtual DOM, JSX
Vue : Single-file components, two-way data binding, a particular compiler for temple syntax
Elm: Elm architecture and functional programming
I like the architecture of Elm and the one-way data binding concept from Elm and React because they are simple yet brilliant solutions. Bruce Lee believes that “simplicity is the key to brilliance”1 and so do I.
However, I did not find a good answer to the architectural question of how to decouple code modules. Coupling is the degree of interdependence between software modules. When modules are dependent on each other, they are harder to change because changes in one module might break other modules. Coupled modules are also difficult to reuse because dependencies require more effort to manage and might even have conflicts that prevent us from assembling the modules. The decoupling of modules makes applications easy to modify, extend, and test. Well-structured applications have decoupled modules or loosely coupled modules.
Getting back to the jQuery era, to achieve better application architecture when building production business applications, I used a common design pattern called event publication and subscription (event pub-sub), also known as the event emitter pattern. Event pub-sub is the recognized and effective way of decoupling modules.2 You will see how it is used in AppRun to archive the decoupling goal in the next section.
Developers can choose to include it as a <script> tag or use it with a build process.
Developers can choose to use JavaScript or TypeScript.
Developers can choose to apply the architecture globally or use components.
Developers can choose to use HTML or use the virtual DOM/JSX to create the views.
Developers can choose to use dynamic types or static types.
Following these design concepts and goals, I built AppRun. The overall result of the AppRun library is encouraging. AppRun applications have simpler project structure; more straightforward build script and process, which leads to better performance; and fewer lines of code when compared to many other popular frameworks and libraries (according to third-party research3). AppRun performance benchmarks are publicly published for comparison as well.4
Let’s get started.
Introducing AppRun
AppRun is 3KB to 4KB when it is minified and zipped. The underlining AppRun architecture has adopted modern architectural concepts and functional programming techniques. It lets you focus on creating the application logic via an established architecture pattern without the distraction of the functional programming language syntax, types, and other nonbusiness logic concepts.
AppRun Architecture
Model: The state of your application
View: A way to view your state as HTML
Update: A way to update your state
At first glance, it may look like the Model-View-Controller (MVC) architecture, which dates back to the 1970s. In the MVC architecture, the model, view, and controller are the three logical building blocks of the application front end. Although this model is an excellent logical and conceptual breakdown of the application logic, it has the problem that the three blocks usually are coupled modules. They reference and manipulate each other. Because the code is coupled, it is difficult to test and maintain. There are many patterns derived from the MVC pattern aiming to solve the coupling problem, such as Model-View-Presenter, Presentation Model, and Model-View-ViewModel. Ultimately, they all attempt to either reduce the manipulations between the model, view, and controller or at least simplify the manipulations.
The Elm architecture solved the coupling problem brilliantly using the functional programming concept. For example, the view function is a pure function, which means it always returns the same result as long as the state is the same and it does not produce side effects, which means it does not change values outside the function or the passed parameters. The view function never changes the DOM. The view function returns the data that represents the HTML. The Elm runtime does the rendering to the Document Object Model (DOM). The update function is a pure function and returns the data that represents the operations. The Elm runtime performs the operations. If the operations have side effects, the Elm runtime handles them. This way, Elm claims that Elm applications have zero runtime exceptions. The Elm architecture has made Elm great for web application development.
However, Elm is a Haskell-style language. Compared to JavaScript’s ease of use, the Elm language has a higher barrier for entry before developers can start developing. The Elm syntax is a burden for many developers.
Combining the Elm architecture and JavaScript leads to the AppRun library. AppRun allows the application developer to use the Elm architecture but with JavaScript. We can leverage the power of the Elm architecture but without going through the learning curve of the Elm language syntax.
State: The state of your application
View: A function to display the state as HTML
Update: A collection of event handlers to update the state
When developing an AppRun application, we break down the application logic into the state, view, and update parts, and we use the app.start function to tie them together and mount them to a web page element.
The AppRun Architecture
In the AppRun architecture, state is an object represents the application state. view is a function that creates HTML from the state. It does not change the DOM. AppRun renders the HTML to the web page. The view function is a function meant to be a pure function just like the correspondent view function in the Elm architecture . update is an object that contains a number of named event handlers. The event handlers process AppRun events and create and return new states. AppRun applications are event-driven.
Event Pub-Sub
As mentioned, AppRun uses the event publication and subscription pattern (event pub-sub). The event pub-sub pattern is fundamental to the web application programming model. The DOM-based web page development API is solely based on event pub-sub.
Publishing an event means to raise an event for some other code to handle. Publishing an event is also referred as firing an event or triggering an event.
Subscribing an event means to register an event handler function to the event. The event handler function executes when the corresponding event is published.
app.on for registering event handlers (event subscription)
app.run for firing events (event publication)
The benefit of using events is that they can decouple modules. Module A and Module B do not know each other. They only need to know the global app object. Module B does not have reference to and is not dependent on Module A. Module A and Module B depend only on the global app . Therefore, Module A and Module B are decoupled. Event pub-sub is an effective method to decouple modules. By using event pub-sub, the building blocks or modules in the AppRun architecture are decoupled from each other. They communicate and invoke the functionalities through the events.
- 1.
AppRun dispatches the events to the event handlers in the update along with the current application state.
- 2.
The event handlers create a new state based on the current state.
- 3.
AppRun passes the new state to the view function .
- 4.
The view function creates HTML or a virtual DOM.
- 5.
AppRun renders the HTML to the screen and calls the optional rendered function to complete the AppRun event lifecycle (see Figure 1-1).
There are two checkpoints in the AppRun event lifecycle where AppRun lets us stop the event lifecycle. They are also the points that AppRun publishes the built-in debug event to let us examine the state in the event lifecycle. There is also an optional callback function called rendered . If we create the rendered function , AppRun calls into the rendered function when it finishes rendering the DOM.
You will learn about all the types of the events, the checkpoints, and the asynchronous event handlers in Chapter 5 and Chapter 6. You will learn about integrating third-party libraries using the rendered function in Chapter 8.
Note
The app.run function publishes the events to drive the AppRun application logic ultimately. It is so important that the AppRun library is named after it.
Component
A component is a technique to decompose the large system into smaller, manageable, and reusable pieces. Usually, a component is an autonomous and reusable module that encapsulates a set of data and functions. A component is the basic building block in other popular frameworks and libraries such as Angular, React, and Vue. Elm does not have components. Elm has the concern that the relationship and communication between components might prevent or cause difficulties to ensure everything is done in the functional programming style.
AppRun solves the component relationship and communication problem by using event pub-sub. AppRun components are decoupled and isolated modules. Elm’s concern is not an issue in AppRun. In AppRun applications, the component is a mini-application and has a component-scoped AppRun architecture, which includes the three architecture parts discussed previously: state, view, and update. Components communicate with each other through the events.
The AppRun Component
The component is suitable for developing single-page web applications (SPAs). An SPA is a modern and trendy style of web application. It loads the main web page once and switches the functionalities dynamically without a page refresh. Each functionality in an SPA is a mini-application. A famous SPA example is Gmail. You can search, read, compose, reply, and forward e-mails. You can also manage the calendar and even chat with your friends on a single page.
Using components to organize, modularize, and encapsulate the states makes your application code testable and maintainable. The SPAs developed in this book are business application development examples. In Chapter 7 of this book, you will learn how to build an SPA using AppRun components. You will also learn how to optimize the component modules such as code splitting, dynamic loading, and server-side rendering.
However, unlike other frameworks and libraries, which force us to make everything a component, AppRun is more flexible. AppRun does not force us always to use components. We can choose to develop applications using the AppRun architecture globally just like Elm.
A Counter App
The state is a number that represents the counter.
The view function displays the counter and two buttons.
The update has two event handlers for increasing and decreasing the counter.
Source Code of the Counter Application
Let’s review the code.
Event Lifecycle
The buttons publish the -1 and +1 events (lines 15–16). The event names are strings. You can name them creatively.
AppRun invokes the two event handlers (lines 20–21) with the current state as an event parameter.
The two event handlers create a new state out of the current state.
AppRun then invokes the view function with the new state.
The view function creates HTML using the new state (line 12).
AppRun renders the HTML to the element that has an ID of my-app.
In AppRun applications, the state access is only through the function parameter. There is no need to use this.state. Let AppRun manage the states for you.
Watch the State
AppRun has a built-in debug event to help us visualize the state at the two checkpoints in the AppRun event lifecycle (see Figure 1-3). When AppRun finishes invoking the event handlers, it publishes the debug event with the event name, event parameters, current state, and new state as event parameters. When AppRun finishes invoking the view function , AppRun publishes the debug event with the state and generates HTML as the event parameters.
Figure 1-3 shows the logged event parameters of the debug event. The initial state 0 is rendered into HTML as the h1 tag. It also renders the two buttons to publish the events. The event +1 changed the current state from 0 to the new state 1.
The debug event is the way for you to debug the applications by examining how AppRun manages the state internally. This is good for development. It makes the state transparent during the event lifecycle. There is no hidden or implicit magic. In production, we should not subscribe to the debug event.
Virtual DOM
Besides parsing the HTML string, AppRun supports using JSX in the view function . JSX is the syntactic sugar of function calls. We can compose the functions and apply dynamic and conditional rendering without the runtime cost of parsing the HTML string. You will learn about many view patterns of using JSX in Chapter 4.
State History
Source Code of the Counter Application with History
To enable the state history, we set the history option to true in the app.start function call (line 25); then we can use the back button and the forward button to step back and forth, like undo and redo.
AppRun maintains internally a state history stack and a state history pointer. AppRun has two internal events for moving the history pointer: history-prev and history-next. The back button publishes the history-prev event to let AppRun set the current state to the state before the state history pointer. The forward button publishes the history-next event to let AppRun set the current state to the state after the state history pointer.
You will learn details about AppRun state management in Chapter 3.
Typed Architecture
The counter application uses JavaScript. JavaScript is dynamic typed/weak typed. Dynamic typing allows developers to focus on solving the application logic. It makes JavaScript easy to use. However, when modern web applications become complicated, a static typed/strong typed language compiler can identify code issues by checking the types at compile time instead of runtime.
We can use TypeScript for static typing. TypeScript is an open source programming language developed and maintained by Microsoft for developing large applications. Microsoft defines that TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Before exploring how to use the TypeScript type system, we need to understand the meaning of TypeScript as a superset of JavaScript. In other words, it means any valid JavaScript code is TypeScript code. For example, you can rename a JavaScript code file from *.js to a TypeScript code file (*.ts), which the TypeScript compiler compiles into the script file for running in the browser. The benefit of compiling is that it allows us to use the JavaScript language features that are not supported by the browsers such as JSX, async/await, class properties, and decorators. You will learn about using these features throughout the book.
We will take a first look at how to add types to the three parts of the AppRun architecture: state, view, and update. TypeScript has a type system that includes interfaces, classes, generics, and type aliases for static typing.5 It is straightforward to define the AppRun architecture using TypeScript.
State
This is an extremely helpful feature. We are able to add, remove, and change data fields in the state object freely and let TypeScript figure out the type. This allows us to focus on modeling the application state without manually synchronizing the object fields between the type definitions and the instant objects. It saves time and increases the developer productivity.
View
Using the static type state parameter, we can check whether we are using the state parameter correctly. For example, if we try to access an object property that does not exist in the state type, the compiler can report the error (see Figure 1-6 in the following section).
You will notice the <div> tag in the view function . It looks like the HTML tags, but it is the JSX language extension to JavaScript supported by TypeScript.
Update
You will notice we use +1 as the event name. The event name is a string, not a variable name. We can be creative when naming the events. For example, we can group similar events by using namespaces in the event names such as auth:login, auth:login:success, and auth:login:failed, where we have multiple namespaces separated by colons.
Static Typed Counter Application
In Figure 1-6, it shows that the Visual Studio code editor caught two type errors. One is at line 7 where the state should not have a counter property because the state is a number. The other error is at line 13 where the state cannot be a string.
TypeScript is not mandatory for developing applications with AppRun. You can start with JavaScript and add types gradually when you need them. When you choose to use TypeScript, however, the AppRun development environment enables the debugging of the TypeScript source code as well as the AppRun source code.
Summary
The key to winning the battle against JavaScript fatigue is simplicity. Always remember that less is more. We should keep challenging ourselves to simplify things as much as possible. The architectural elements irrelevant to the business logic are the architectural ceremony elements. The code irrelevant to the business logic is the code ceremony.6 The ceremony has no business values. Can we simply remove the ceremony elements, such as dependency injection, custom templating language, artificial concepts such as actions, reducers and dispatchers, subscriptions and commands, and so forth?
AppRun’s answer to the challenge is yes. These ceremonies are not needed and do not exist in AppRun applications. Pursuing simplicity has made AppRun a lightweight library. Not only it is just 3KB to 4KB when minimized and compressed, but it also has a tiny API with only three functions.
The AppRun architecture organizes the logic into the state, view, and update. AppRun does the heavy lifting to drive the application logic. Using AppRun, you capture the essence of the business logic and write less irrelevant code. Your application logic has the ultimate business value. AppRun adds no overhead or ceremony to your applications. AppRun also gives you options to choose what makes the most sense to your applications, such as choosing between vanilla JavaScript and TypeScript. AppRun plays well with other UI libraries, animation libraries, and visualization libraries, such as jQuery, D3, Electron, and Framework7. It can be used for web applications, desktop applications, and mobile applications.
In the next chapter, we will introduce a production-ready configuration that has development productivity in mind to prepare you for complex app development.