Improving Ember.js serve and testing performance
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.
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 run
s 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.
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.