I use the cli command ember serve or the abbreviated form ember s everyday, to build and serve locally ember apps for development. I noticed that ember s --path dist and ember t --path dist were taking a long time to start. The --path flag lets you reuse an existing build at the given path.

➜  ~ ember help | grep -- "--path\|^\w" | grep -B1 -- "--path"
ember serve <options...>
  --path (Path) Reuse an existing build at given path.
ember test <options...>
  --path (Path) Reuse an existing build at given path.

Slow Continuous Integration

This long start time also significantly increased the total run time of the continuous integration, CI, test run of the project I was working on. The CI build was split into a compile stage and a test stage. The compile stage fetches dependencies and compiles the app using ember b and prepares the server side API for our full stack smoke tests. The app uses ember-cli-typescript, and the build can take around 2 minutes compiling typescript on the CI. The test stage consists of 7 concurrent jobs which all use the compiled assets from the compile stage. By having a single compile stage and 7 test stages the start time to end time will be quicker. The total run time will be slightly longer due to multiple 7 extra test environment boot steps.

Smoke Tests PWA
Smoke Tests PWA
Smoke Tests Admin App
Smoke Tests Admin App
Smoke Tests Customer App
Smoke Tests Customer App
Acceptance 1
Acceptance 1
Acceptance 2
Acceptance 2
Integration
Integration
Unit
Unit
Boot
Boot
Compile
Compile
Tests
Tests
Type check
Type check
Tests run in series (tests block shortened to fit on page)
Tests run in series (tests block shortened to fit on page)
Tests run concurrently with compile stage
Tests run concurrently with compile stage

However the total run time for the CI test run increased more than could be accounted for by the extra boot steps, that is the total time for all the jobs to complete took around 14 minutes more than expected. Due to using concurrent jobs the time from starting the build and the build finishing reduces, there was a decrease in time to feedback but not what was expected and the total run time increased more than expected.

What happens when you run ember t?

ember is the command line interface for Ember.js whereis ember will tell you where the executable is, in my case ~/.yarn/bin/ember. Opening ~/yarn/bin/ember the first line is #!/usr/bin/env node telling us its a node app, which creates a ember-cli CLI and runs it. Parsing the command line args and creating a Command object with in turn creates the appropriate Task object and runs it. In this case it’s the TestTask, which when run invokes testem, the JS test runner used by Ember.js. The TestTask also allows ember addons to inject middleware into testem. This is very similar to the ServerTask, run when using ember s, with also allows ember addons to inject middleware into the development server.

Identifying slow build stage

There’s some useful documentation describing how to track down build performance issues here and in the ember-cli docs. Using one of the techniques described, I add a DEBUG environment variable before ember s

$ DEBUG==ember-cli:* ember s --path dist
  ember-cli:test-server isForTests: false +10s
  ember-cli:broccoli-watcher serving: /admin/index.html +37s
  ember-cli:broccoli-watcher serving: (prefix stripped) /index.html, was: /admin/index.html +1ms

Why does it take 10s for isForTests, and what is broccoli-watcher doing, I thought we were serving precompiled assets? To dig down a bit more, and produce a lot more debug info ran DEBUG=*:* ember s --path dist

$ DEBUG=*:* ember s --path dist
...
– Serving on http://localhost:4200/admin/
...
ember-cli-typescript:typecheck-worker Typecheck complete (0 diagnostics) +49s
...

It’s not ember-cli:broccoli-watcher but ember-cli-typescript:typecheck-worker taking up all the time. The real culprit is ember-cli-typescript middleware. Type checking is being performed even when the code is already transpiled, the case when using the --path flag.

Improving performance

The code change to improve performance was small. When adding server middleware or testem middleware check the options passed to ember-cli and if they contain the path flag then don’t run typechecking. A small ember-cli update was also required. ember-cli would pass all the CLI flags to the server middleware, but not to the testem middleware. Without the ember-cli update, ember-cli-typechecking would not of been able to perform the check when testem middleware was added. Interesting to note that for each PR I spent a lot more time figuring out how to effectively test the changes than implement them.

Smoke Tests PWA
Smoke Tests PWA
Smoke Tests Admin App
Smoke Tests Admin App
Smoke Tests Customer App
Smoke Tests Customer App
Acceptance 1
Acceptance 1
Acceptance 2
Acceptance 2
Integration
Integration
Unit
Unit
Smoke Tests PWA
Smoke Tests PWA
Smoke Tests Admin App
Smoke Tests Admin App
Smoke Tests Customer App
Smoke Tests Customer App
Acceptance 1
Acceptance 1
Acceptance 2
Acceptance 2
Integration
Integration
Unit
Unit
Tests before performance improvement
Tests before performance improvement
Tests after performance improvement
Tests after performance improvement

These updates significantly sped up the CI build times, and more noticeably, improved my work flow by drastically reducing execution time of the ember commands when using the --path flag.