In this post I will show how to wire up a simple implementation of a Bazel build with a “live refresh” dev server.
This post is a continuation of my previous Bazel article, so you might want to read that article first.
The main thing the previous implementation lacked was a dev server with support for live refreshing after saving source files.
There are many ways to approach a dev server for Bazel builds. This is just one example, so I would love some feedback about improvements.
Bundling is technically optional. There is nothing stopping you from serving the app from the individual files using SystemJS.
However, after trying this approach it became clear that it doesn’t scale for large projects. Serving thousands of files just isn’t scalable, even from localhost.
Instead the files should be bundled as part of the Bazel build.
The Angular team is currently figuring out the best approach for bundling Angular with Bazel. One simple idea they are experimenting with is file concatenation on-the-fly.
My poc uses a slight variation on the approach in Alex’s great demo repo.
The bundling is as simple as just combining all the application code into a single file.
Bazel gives us a manifest (.MF file) of all the source files, which tells me which files to bundle.
Here is the concatenation script:
The ngsummary.js files are not part of the application, so I am omitting them.
The output of the concat.js script is a single bundle.js file.
In order to serve this file from lite-server I create a symlink from my project folder to the bazel-bin folder, where the Bazel output is found.
From there on it’s just regular lite-server configurations to serve the site.
Every time you make a code change, iBazel will cause the build to re-trigger and refresh bundle.js.
Note: I had to disable Git integration in Visual Studio Code since it was thrashing the iBazel process by triggering rebuilds every few seconds. I suspect there is some polling that causes constant rebuilding.
As I mentioned earlier, it’s not practical to serve the individual application files using SystemJS. However, there are a few advantages to using SystemJS to serve the generated application bundle + external dependencies like Angular umds and rxjs.
Bazel outputs the files as named AMD modules. You can load the AMD modules using something like require.js, but this creates a slight challenge with RxJs.
RxJs does not ship as AMD. To make RxjS compatible with require.js, operators etc will have to be wrapped as AMD modules.
Back to SystemJS…
Instead of wrapping RxJs I decided to go with SystemJS since it can understand most module formats, which means we can serve the application with mixed module formats.
This does not have the performance penalty of the previous implementation since the number of files is greatly reduced.
Here is my index.html file:
Notice the nested System.import calls.
The first import call loads the application bundle. The second loads a named AMD module defined in the bundle.js file.
One clear advantage of this is that there is no special handling of RxJs operators since SystemJS is able to normalize different module formats (commonJS vs AMD).
To make this a bit more realistic I added a few semi-complex Angular components to the project.
As part of this I wired up Sass compilation to one of the components.
You can see the component code below:
Notice the style url treeview-styles.css. This file was generated by a Bazel Sass rule defined in the BUILD.bazel file for this component’s NgModule (seen below).
The name of the generated css file corresponds to the name of the Sass Bazel rule, not the name of the original .scss file.
External Html Templates
If your components have external templates you have to list them as assets in the Bazel rule for the component's ngModule. This will ensure that the application will rebuild the component if the template changes. Look at my grid component for an example.
I have put the code on Github.
Look in the dev-envrionment branch for the code.
To run the project run: bazel run @yarn//:yarn and npm run start