For a web framework to be effective, it should offer you more than just functionality. It doesn’t matter how much hard work you put into your application if it breaks when people use it. Beyond testing, let’s explore how frameworks can help us build better code before we test it.
While we have been comparing web frameworks to music, soundness in this context isn’t how good the framework sounds on a turntable. It is the concept of our code being in a good condition and robust. While we should and can test for this, helping us in other ways should be one of the main benefits of using a framework.
To get our application to perform properly, the server environment that it runs in can have a dramatic impact on our application
To get our application to perform properly, the server environment that it runs in can have a dramatic impact on our application. Furthermore, we still must contend with running our application in different contexts, from older browsers to low powered mobile devices.
We will look at how each framework approaches these challenge of helping an end developer better know that the code they author will operate as they intend.
There are generally three areas where a framework can help us, and we will look at each framework from those stages. First, we have development time. What sort of information can we get during development time that helps us know if we are authoring our code properly? Second, when we go to build our applications, before we deploy them, is there any secret sauce that helps ensure that our application will work? Third, once we have our application loaded on the server, is there anything that helps us ensure that the application runs properly?
Angular 2+ is authored in TypeScript, which provides internal soundness to the Angular code base. TypeScript can also provide a level of downstream type soundness when authoring code with Angular 2+ in TypeScript. The Angular 2+ CLI includes a linting command, which will help ensure that code styling and conventions are enforced.
At development time, when authoring in TypeScript there is support leveraging the libraries in a sound way, with code completion and static detection of some logic errors. Because Angular 2+ relies heavily on ES.Next Decorators, which change the run-time behavior of components, as well as a specialized template language, it is difficult to fully ensure that run-time behavior is ensured at development time.
For Visual Studio Code, there is an Angular template language service available, which provides IntelliSense for Angular 2+ templates, which helps improve the support for detecting template syntactic errors at design time.
At build time, the TypeScript compiler can help ensure sound behavior, again with the limitations of being able to statically analyze the behavior added via decorators. For templates, Angular offers ahead of time (AOT) compilation. If used, static syntactical issues with templates will be identified at build time. If not used, the just in time (JIT) template compiler is used within the browser at run-time.
Both testing and linting are not part of the out of the box Angular build process. They are standalone commands when using the CLI build command, which the developer would need to be aware of to ensure they have a robust build process. Of course, these can easily be integrated into a CI process.
When using the JIT template compiler, template errors are identified in the browser. In addition, there is Augury, a Chrome Dev Tools extension for debugging Angular 2+ applications.
React + Redux
Another Facebook project, Flow, provides static type checking for projects like React. Redux is built without any strong type checking. Both React and Redux offer official TypeScript typings that are bundled with the distribution packages. In addition, there are guides to using React and Redux with Flow.
One of the more challenging aspects of sound development with React + Redux is that Redux action types are strings. As a result, it can be quite easy at design time to utilize actions improperly, making it impossible to statically identify incorrect actions at design time.
React and Redux both promote writing code that is separable and fully testable. In particular, Redux promotes the use of pure functions for reducers, which, when followed, ensures that a reducer is fully testable without a need for injecting state into the reducer. The belief of this architecture is that it promotes patterns that reduce cross dependencies between units of code, which could make an entire system fragile.
There are generally two approaches to providing soundness at development time with React + Redux. First, most IDEs have support for Flow or the support is added via an extension. WebStorm (and IntelliJ) support Flow out of the box, Visual Studio Code and Atom are supported through plugins. This support allows static issues to be identified within the source code using the Flow type system.
Also, it has become somewhat common to develop React + Redux applications in TypeScript. Since both packages provide their TypeScript typings integrated with the package, it is possible to author downstream applications that are sound, based on the provided typings. This includes that ability to author JSX in a sound fashion using the builtin JSX support of TypeScript, also known as TSX.
Since both React and Redux are standalone libraries, they do not presume a specific build process. Both Flow and TypeScript can be enforced at build time, as well as JSX syntax.
Another Facebook project, Immutable.js is often used with React + Redux applications. It provides immutable data structures that are designed provide run-time enforcement of unintentional mutations of data structures in an application.
React provides a React Developer Tools extension for Chrome. There is also a special debug mode for React which provides additional run-time metadata which can be used to help debug an application.
Vue uses Flow internally for typing, and it provides TypeScript definitions for external use for core Vue, vue-router, and Vuex. They provide the view-class-component library to simplify the process of using types when creating components.
The Vue documentation encourages unit testing of Vue components, although the Vue project does not recommend any particular unit testing framework.
Vue has an official CLI app, vue-cli. This app has several built-in templates that provide an initial structure to a Vue app.
The Vue docs encourage unit testing of components. The vue-loader docs specifically address unit testing Vue single-file components, including test running with Karma and mocking dependencies.
Vue components can include property validators. These specify requirements for received property values, and Vue will emit warnings if these requirements are met when a property value is set.
An application can register a global error handler through
Vue.config.errorHandler. All render errors will be passed to this handler.
Dojo 2 is authored in TypeScript and is distributed with its TypeScript typings. It is intended that downstream code is also authored in TypeScript. The framework is made up of modular packages that can be used together or independently.
The Dojo 2 CLI is designed to make it easier for developers to ensure that their applications are structured appropriately, testable, and built in an efficient way.
One of the main design goals of Dojo 2 is that the use of TypeScript should promote sound code development for the end user. The framework is authored to help ensure that types are fully leveraged to provide as much information to the developer at development time. This includes the use of CSS Modules for the theming system to provide IntelliSense and code completion for CSS at design time and ensuring that CSS is tightly coupled with the modules that use that CSS.
The out of the box CLI build process ensures sound development via TypeScript and that CSS dependencies are bundled with the build. In addition, if a
tslint.json file is present, the CLI will automatically lint the project as part of the build process.
Currently, outside of a few warnings, there is no additional tooling beyond standard browser developer tools to assist the developer in debugging an application. Further run-time tooling is on the Dojo roadmap.
Traditionally, Ember.js has focused heavily on convention to encourage the developer to build sound applications. Now that there is fundamental tooling that allows a better approach, Ember.js has started to adopt this. Glimmer is authored in TypeScript and Ember has introduced TypeScript typings for downstream code authoring in TypeScript. In addition, Ember has had a long-term vocal presence in the standards committee for ECMAScript, advocating for solutions to common problems and issues that developers experience when authoring applications.
Ember.js has a feature-rich CLI which provides significant development time structure to applications. Again, Ember.js provides many conventions to help ensure that applications behave as designed.
There are third-party plugins for WebStorm (IntelliJ), Visual Studio Code, and Atom which can help provide IntelliSense when developing an Ember.js based application.
At build time, Ember.js provides an out of the box build solution with their CLI tool. The Ember.js CLI is designed to be extensible and the build command can incorporate other functionality like linting. Ember.js tends to utilize templates, authored in Handlebars.js, and the validation of those templates occurs at build time.
Ember.js provides Ember debug options which will provide enhanced diagnostics to make it easier to identify issues in an application at runtime. In addition, the Ember Inspector is a Chrome Dev Tool plugin that provides additional tools to facilitate debugging of Ember.js applications.
Some of Aurelia is authored in TypeScript, and all of the modules provide their TypeScript typings to make downstream authoring in TypeScript a possibility. The main focus of Aurelia is to try to keep the entire framework modular to promote a decoupled architecture and testability.
When authoring downstream applications in TypeScript it is possible to harness the typings for Aurelia to author sound downstream code.
Aurelia, like other frameworks, does rely on templates for creating components. There is a Visual Studio Code extension which gives some code completion and IntelliSense for the template language. The last commit to this project was over 4 months ago, so it is difficult to determine if this extension is actively developed and maintained.
When scaffolding applications, Aurelia’s CLI will encourage linting and promote patterns around testing. The biggest way in which Aurelia ensures soundness at build time is by encouraging the development of applications in TypeScript.
Aurelia encourages patterns around providing informative error messages but does not have any formal structures for providing instrumentation or debugging information for an application.
Angular 2+ using a strongly typed language like TypeScript certainly improves the soundness of the project. The end developer experience is not quite as sound and straightforward due to the heavy reliance on templates, which tend to contain application logic, and the use of decorators to achieve run-time behavior.
There is a decent amount of tooling to make an entire Angular application hang together and good tooling to provide run-time insight and debugging to an application.
React + Redux
React + Redux provide typing information to make it easy to author downstream applications in Flow or TypeScript. They also promote patterns that make source code more separable and testable. Like many things with React and Redux, it is up to the end development team to ensure that these practices and conventions are followed. For teams that know what they are doing, these libraries and other associated libraries are great tools.
Vue.js makes developing code in a sound and structured fashion straightforward. There is a good set of tooling to help ensure that applications behave as expected. Vue.js provides good information about how to go about testing code based on its framework. There are run-time capabilities which help ensure that data running through the application at run-time is valid.
Dojo 2 is built with TypeScript with a specific focus to provide a sound framework for developers to create applications. Dojo 2 has a good set of tooling ensuring that a built application behaves properly at run-time. It also promotes patterns of modular separable code that can be fully tested.
Ember.js mostly relies upon strong structural conventions to ensure that applications behave properly at run-time. Ember continues to be receptive to new technologies, like TypeScript, which can promote more sound coding. Ember provides an extensive set of tooling which is highly extendable by third-party plugins to provide a robust pipeline and offers run-time instrumentation and debugging tools.
Aurelia’s encouragement of downstream development in TypeScript is the biggest benefit it has for creating sound applications. While Aurelia relies upon templates like some other frameworks, they rely less on magic being injected into components. Like many things though with Aurelia, it feels like they have a robust approach and direction, but have had some difficulty in materializing a full solution.
Up to this point, we have looked at what each of the frameworks has to offer you, mostly from a technical perspective. One of the challenges of modern web application development though is that it is not just source code, it is about the wider community of people and how each of the frameworks governs itself. Invariably you will need help with adopting a framework and having an understanding of the larger community can impact your decisions. In the next in the series, we will explore the community aspect of each framework.