blob: 74f2c145ad59c8f01701b0b8d9da679e6b219382 [file] [log] [blame] [view]
[![CircleCI](https://circleci.com/gh/angular/angular-bazel-example.svg?style=svg)](https://circleci.com/gh/angular/angular-bazel-example)
# Example Angular monorepo using Bazel
There are a few ways to use Angular with Bazel. See https://bazelbuild.github.io/rules_nodejs/examples#angular for an overview of all the options.
This example demonstrates the Google-internal toolchain, which is fast but not very compatible with existing applications.
## Guide to the example
This example is a monorepo, meant to show many different features and integrations that we expect are generally useful for enterprise use cases.
- **Angular CLI**: you can use the `ng` command to run build, serve, test, and e2e
- **Angular Libraries**: to maximize build incrementality, each Angular module is compiled as a separate step.
This lets us re-use Angular libraries without having to publish them as npm packages. See `src/todos` for a typical `NgModule` compiled as a library for use in the application, using the `ts_project` rule in the `BUILD.bazel` file.
- **TypeScript Libraries**: see `src/lib` for a trivial example of a pure-TS library that's consumed in the application, using the `ts_project` rule in the `BUILD.bazel` file.
- **Sass**: we use Sass for all styling.
Angular components import Sass files, and these are built by Bazel as independent processes calling the modern Sass compiler (written in Dart).
- **Material design**: see `src/material` where we collect the material modules we use.
- **Redux-style state management**: see `src/reducers` where we use the [NgRx Store](https://ngrx.io/guide/store).
- **Lazy loading**: in production mode, the application is served in chunks.
Run `ng serve --prod`
- **Differential loading**: in production mode, we load a pair of `<script>` tags.
Modern browsers will load code in the ES2015 syntax, which is smaller and requires fewer polyfills. Older browsers will load ES5 syntax.
- **Docker**: see below where we package up the production app for [deployment](#deployment) on Kubernetes.
- **Progressive Web App**: support for service worker and the app can be installed on phones and desktops (also has 90+ Lighthouse score)
This example is deployed at https://bazel.angular.io/example
## Installation
You only need to install one build tool, and which one you choose typically depends on what kind of development you do most often.
If you're a frontend developer, you should install NodeJS and yarn.
The `package.json` file has an `engines` section which indicates the range of NodeJS and yarn versions that you could use.
You simply run `yarn` commands shown below, and don't need to install Bazel or any other dependencies.
If you're a full-stack developer, you might be using Bazel for your backend already.
In this case, you should install Bazel following instructions at http://bazel.build.
Also install `ibazel`, which is a watch mode for Bazel not included in the standard distribution. See https://github.com/bazelbuild/bazel-watcher#installation.
You should have a `.bazelversion` file which will ensure Bazel prints an error if your Bazel version is not in the supported range.
You simply run `bazel` commands shown below, and don't need to install NodeJS, yarn, or any other dependencies.
## Development
First we'll run the development server:
```bash
$ ng serve
# or
$ ibazel run //src:devserver
```
This runs in "watch mode", which means it will watch any files that are inputs to the `devserver`, and when they change it will ask Bazel to re-build them.
When the re-build is finished, it will trigger a LiveReload in the browser.
This command prints a URL on the terminal. Open that page to see the demo app running.
Now you can edit one of the source files (`src/lib/file.ts` is an easy one to understand and see the effect).
As soon as you save a change, the app should refresh in the browser with the new content.
Our intent is that this time is less than two seconds, even for a large application.
Control-C twice to kill the `devserver`.
## Testing
We can also run all the unit tests:
```bash
$ ng test
# or
$ bazel test //src/...
```
Or run the end-to-end tests:
```bash
$ ng e2e
# or
$ bazel test //e2e/...
```
In this example, there is a unit test for the `hello-world` component which uses the `@npm//karma` and `web_test_suite` rule.
There are also protractor e2e tests for both the `prodserver` and `devserver` which use the `protractor_web_test_suite` rule.
Note: Bazel will only re-run the tests whose inputs changed since the last run.
## Production
We can run the application in production mode, where the code has been bundled and optimized.
This can be slower than the development mode, because any change requires re-optimizing the app.
This example uses Rollup and Uglify, but other bundlers can be integrated with Bazel.
```bash
$ ng serve --prod
# or
$ bazel run //src:prodserver
```
### Code splitting
The production bundle is code split and routes such as `/` and `/todos` are lazy loaded.
Code splitting is handled by the rollup_bundle rule which now supports the new code splitting feature in rollup.
Note: code splitting is _not_ supported in development mode yet so the `//src:devserver` target does not serve a code split bundle.
The dynamic `import()` statements will resolve to modules that are served in the initial JS payload.
## Npm dependencies
Having a local `node_modules` folder setup by `yarn` or `npm` is not necessary when building this example with Bazel.
This example makes use of Bazel managed npm dependencies (https://bazelbuild.github.io/rules_nodejs/dependencies.html) which means Bazel will setup the npm dependencies in your `package.json` for you outside of your local workspace for use in the build.
However, you may still want to run `yarn` or `npm` to manually setup a local `node_modules` folder for editor and tooling support.
## Deployment
### Firebase
We use the standard firebase deploy command.
Run `yarn deploy` to release changes to bazel.angular.io.
### Kubernetes Engine
We use Bazel's docker support to package up our production server for deployment.
Each time the app changes, we'll get a slim new docker layer with just the modified files, keeping the round-trip for deployment incremental and fast.
This example is configured to run on Google Kubernetes Engine, so we can have an elastic pool of backend machines behind a load balancer.
This setup is more expensive to operate than something like Firebase Functions where the backend code is spun up on-demand, but is also more adaptable to scenarios like backend servers that need to run other binaries on the machine.
The application is currently live at http://35.197.115.230/
To run it under docker:
```bash
$ bazel run src:nodejs_image -- --norun
$ docker run --rm -p 8080:8080 bazel/src:nodejs_image
```
Deploy to production:
1. Install gcloud and kubectl
1. Authenticate to the Google Container Registry
`gcloud auth configure-docker`
1. Authenticate to Kubernetes Engine
`gcloud container clusters get-credentials angular-bazel-example --zone=us-west1-a`
1. For the first deployment: `bazel run :deploy.create`
1. To update: `bazel run :deploy.replace`
Tips:
**Run the binary without docker**
```bash
$ bazel run src:nodejs_image.binary
```
**What's in the image?**
```bash
$ bazel build src:nodejs_image && file-roller dist/bin/src/nodejs_image-layer.tar
```
**Tear down all running docker containers**
```bash
$ docker rm -f $(docker ps -aq)
```
**Hop into the running image on kubernetes**
```bash
$ kubectl exec angular-bazel-example-prod-3285254973-ncv3g -it -- /bin/bash
```