When I saw the result of my artistic efforts to turn the angular.json file into something cool, I just had to say “Eh”…

From custom Webpack build to Angular CLI

Martin Džejky Jakubik
7 min readMay 13, 2018

I make a small code change in my Angular application and wait for the code to recompile. Carefully watching the console as it slowly writes updates about the compilation progress, I am wondering why the build is so slow. My thoughts go to Angular CLI, the shiny toolbox which looks both fast and incredibly useful for generating code.

The browser page is reloaded. Finally! It turns out I made a mistake in the template of one component (quick thought about serving in AOT…) so I fix it and hit Save. It starts all over, the waiting, the wondering. Now the whole application crashes because of some crazy compilation error. Plus my CPU is tanking. Time to restart the build…

Not so long ago I was against using the Angular CLI for my applications. I wanted full control over everything that happens to my application. So I carefully configured Webpack, setting the correct configuration options and choosing the correct (or perhaps just cool) plugins. Then I separated the configuration into multiple files, some of them in a different Git repository used as a submodule, and merged them back together using webpack-merge.

A few months later, I am working on the actual application. The intricate knowledge about the build setup was lost long time ago and I do not have time to tweak the build. Even if I actually understood it. That time is much better spend on the application code.

So I decided to move my project to Angular CLI. The goal is simple — to not have to care about the build system anymore. The old build was buggy anyway, requiring me to restart Webpack every so often.

Additionally, the CLI offers more than just the build system — schematics and tests. I spent enough time creating components and services from scratch! Those components and services also do not have unit tests because I didn’t set up the testing framework before.

That is all about to change…

Two days later, the project is now using the CLI. It is super easy to serve the application (both in JIT and AOT) and to make a fully-optimized production build. I can also easily run unit tests and integration tests. Because of this I have since adopted the TDD mindset when creating new components or services. All-in-all, I am able to work faster and my code is more reliable and tested. But the journey here was not so smooth.

When I started migrating, the project was using custom Webpack build, a different directory structure, and no unit tests (only integration tests using Cypress).

The migration happened in a few steps which will be outlined below:

  1. Refactor the project file structure to reflect that of an Angular CLI project
  2. Add missing dependencies
  3. Set up all the configuration files used by the CLI and Typescript
  4. Configure the testing environment using Mocha + Chai + Sinon
  5. Update the Gitlab CI configuration
  6. Profit!

Refactor the project file structure

The project originally had a different structure than what is commonly used in an Angular CLI project. I wanted to move the structure closer to the CLI for two reasons —to learn and get used to it for future projects, and to keep the configuration simple (and easier to copy from a new generated project).

Original project file structure

As you can see in the screenshot, I only had to make small adjustments. I started by moving all the application code to src/app and moving the module file to the top of this folder. Then I moved all assets into src/assets, moved the environments folder to the source root folder, and put the entry files like main.ts and polyfills.ts to the root of src.

New file structure (including configuration files added later)

If you are wondering how I chose this folder structure… Well, I just generated a new project using the CLI (ng new) and copied the structure of that project.

Add missing dependencies

I inspected the package.json file of a freshly generated CLI project and installed all missing dependencies into my project. For my particular project I had to do something similar to this:

yarn add @angular-devkit/build-angular @angular/{animations,cli,common,compiler,compiler-cli,core,forms,http,language-service,platform-browser,platform-browser-dynamic,router} @types/{chai,mocha,node,sinon} chai classlist.js core-js karma karma-chrome-launcher karma-mocha karma-mocha-reporter karma-sinon mocha puppeteer rxjs sinon web-animations-js zone.js

Some of these dependencies were already installed but I wanted the latest version. After yarn finished downloading, I had all dependencies for the CLI and for testing (described later in this article).

Set up all the configuration files

Configuration files are everything. Those small pieces of text, structured in a JSON file (or perhaps YAML?). There’s no need to write the logic again. You just configure the existing logic and run the command. Configuration files are there for the command. They run our lives, they define our future…

Err… I meant to say that the next step is to create configuration files for the CLI and Typescript. I copied most of the settings from the new generated project mentioned before and adjusted a few names and file paths.

These configuration files include:

  • tslint.json
  • tsconfig.json, tsconfig.app.json, tsconfig.spec.json
  • angular.json
  • karma.conf.js

I will return to the Karma configuration in the next section. Again, the remaining files are almost the same as they are in a new generated CLI project (ng new).

The rest of the article is specific to my project and diverges from a standard Angular CLI application. Hold your horses!

Configure the testing environment

Angular CLI projects come with a testing framework out of the box. This framework is set up to use Karma and Jasmine by default. However, I now prefer a different combo — Mocha, Chai, and Sinon.

It took me a while to set everything up. But my mom always said that I am unstoppable. And I wanted Typescript typings for everything. Thank god that there are fidget spinners for use when nothing compiles.

Anyway, now I can have .spec files anywhere in the project (usually next to the relevant component or service, just like the default CLI project works). The tests are written using Mocha syntax and they are configured to run either in Chrome or in headless Chromium (good for CI).

Here’s a basic test:

A very primitive test to ensure that the root module is created

And here is how I configured Karma:

process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function (config) {
config.set({
basePath: '',
reporters: ['mocha'],
browsers: ['Chrome'],

frameworks: ['mocha', '@angular-devkit/build-angular'],
plugins: [
require('karma-mocha'),
require('karma-mocha-reporter'),

require('karma-chrome-launcher'),
require('@angular-devkit/build-angular/plugins/karma')
],

port: 9876,
colors: true,
logLevel: config.LOG_INFO,

customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: [
'--no-sandbox',
'--disable-setuid-sandbox'
]
}
}

});
};

The important parts are highlighted. The rest of the file is the same as the original. Additionally, the test.ts file is completely the same as the original.

Here is another test for good measure:

A single unit test for showing a banner when there are new updates from the service worker

It is now the appropriate time to say that Cypress tests were left as they were. They are handled outside of the CLI. If you want to know how I set up Cypress tests and Typescript, see my other article here on Medium.

Moreover, I forgot to mention that because I am using Mocha instead of Jasmine, the default spec files that are generated by schematics do not work and you have to change the syntax to Mocha after generating them. Maybe there is a way to replace the schematics for tests?

Update the Gitlab CI configuration

At Exponea we use Gitlab CI to build our projects. I also use the CI to test, build, lint, and release my Angular application.

I use NPM scripts as the task manager for this project so there was not much to change in the CI configuration. I only changed the scripts in package.json file (for example tests are now run simply using ng test). However, there was one notable change. Tests are now run using Puppeteer to run in headless Chromium. So I had to install the dependencies for Chromium in the CI.

apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
Running the (few) tests in the project is smooth like butter

Profit!

So now the project is using Angular CLI, I can leverage schematics to generate components, services, etc. It is also easy to add new functionality into the project such as service workers. It was almost as easy as running this command:

ng add @angular/pwa --project frontend

And BAM! I have a service worker in production.

Thank you for reading this article! Phew, you really got to the end… I appreciate it. If you like this article, go ahead and give it a clap. I you want to hear more from me and read more about Angular and my experiences with the framework, feel free to follow me or Bratislava Angular.

If you were wondering what this Angular application is, well, it is the frontend application of Exponea Smallbiz. Check it out to see it in action and use it if you like it, after all, it is completely free.

--

--

Martin Džejky Jakubik

Frontend developer @ Exponea. Writing about things I learn along the way.