|
| 1 | +[](https://circleci.com/gh/angular/angular-bazel-example) |
| 2 | + |
| 3 | +# Example Angular monorepo using Bazel |
| 4 | + |
| 5 | +**This is experimental, as part of Angular Labs! There may be breaking changes.** |
| 6 | + |
| 7 | +This is part of the ABC project. The overall goal is to make it possible to |
| 8 | +develop Angular applications the same way we do at Google. |
| 9 | + |
| 10 | +Learn more about Bazel and Angular at https://bazel.angular.io |
| 11 | + |
| 12 | +This example is deployed at https://bazel.angular.io/example |
| 13 | + |
| 14 | +## Guide to the example |
| 15 | + |
| 16 | +This example is a monorepo, meant to show many different features and integrations that we expect are generally useful for enterprise use cases. |
| 17 | + |
| 18 | +- **Angular CLI**: you can use the `ng` command to run build, serve, test, and e2e |
| 19 | +- **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 `ng_module` rule in the `BUILD.bazel` file. |
| 20 | +- **TypeScript Libraries**: see `src/lib` for a trivial example of a pure-TS library that's consumed in the application, using the `ts_library` rule in the `BUILD.bazel` file. |
| 21 | +- **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). |
| 22 | +- **Material design**: see `src/material` where we collect the material modules we use. |
| 23 | +- **Redux-style state management**: see `src/reducers` where we use the [NgRx Store](https://ngrx.io/guide/store). |
| 24 | +- **Lazy loading**: in production mode, the application is served in chunks. Run `ng serve --prod` |
| 25 | +- **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. |
| 26 | +- **Docker**: see below where we package up the production app for deployment on Kubernetes. |
| 27 | + |
| 28 | +## Installation |
| 29 | + |
| 30 | +You only need to install one build tool, and which one you choose typically depends on what kind of development you do most often. |
| 31 | + |
| 32 | +If you're a frontend developer, you should install NodeJS and yarn. |
| 33 | +The `package.json` file has an `engines` section which indicates the range of NodeJS and yarn versions that you could use. |
| 34 | +You simply run `yarn` commands shown below, and don't need to install Bazel or any other dependencies. |
| 35 | + |
| 36 | +If you're a full-stack developer, you might be using Bazel for your backend already. |
| 37 | +In this case, you should install Bazel following instructions at http://bazel.build. |
| 38 | +Also install `ibazel`, which is a watch mode for Bazel not included in the standard distribution. See https://github.com/bazelbuild/bazel-watcher#installation. |
| 39 | +The `WORKSPACE` file has a `check_bazel_version` call which will print an error if your Bazel version is not in the supported range. |
| 40 | +You simply run `bazel` commands shown below, and don't need to install NodeJS, yarn, or any other dependencies. |
| 41 | + |
| 42 | +## Development |
| 43 | + |
| 44 | +First we'll run the development server: |
| 45 | + |
| 46 | +```bash |
| 47 | +$ ng serve |
| 48 | +# or |
| 49 | +$ ibazel run //src:devserver |
| 50 | +``` |
| 51 | + |
| 52 | +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. |
| 53 | +When the re-build is finished, it will trigger a LiveReload in the browser. |
| 54 | + |
| 55 | +This command prints a URL on the terminal. Open that page to see the demo app |
| 56 | +running. Now you can edit one of the source files (`src/lib/file.ts` is an easy |
| 57 | +one to understand and see the effect). As soon as you save a change, the app |
| 58 | +should refresh in the browser with the new content. Our intent is that this time |
| 59 | +is less than two seconds, even for a large application. |
| 60 | + |
| 61 | +Control-C twice to kill the devserver. |
| 62 | + |
| 63 | +## Testing |
| 64 | + |
| 65 | +We can also run all the unit tests: |
| 66 | + |
| 67 | +```bash |
| 68 | +$ ng test |
| 69 | +# or |
| 70 | +$ bazel test //src/... |
| 71 | +``` |
| 72 | + |
| 73 | +Or run the end-to-end tests: |
| 74 | + |
| 75 | +```bash |
| 76 | +$ ng e2e |
| 77 | +# or |
| 78 | +$ bazel test //e2e/... |
| 79 | +``` |
| 80 | + |
| 81 | +In this example, there is a unit test for the `hello-world` component which uses |
| 82 | +the `ts_web_test_suite` rule. There are also protractor e2e tests for both the |
| 83 | +`prodserver` and `devserver` which use the `protractor_web_test_suite` rule. |
| 84 | + |
| 85 | +Note that Bazel will only re-run the tests whose inputs changed since the last run. |
| 86 | + |
| 87 | +## Production |
| 88 | + |
| 89 | +We can run the application in production mode, where the code has been bundled |
| 90 | +and optimized. This can be slower than the development mode, because any change |
| 91 | +requires re-optimizing the app. This example uses Rollup and Uglify, but other |
| 92 | +bundlers can be integrated with Bazel. |
| 93 | + |
| 94 | +```bash |
| 95 | +$ ng serve --prod |
| 96 | +# or |
| 97 | +$ bazel run //src:prodserver |
| 98 | +``` |
| 99 | + |
| 100 | +### Code splitting |
| 101 | + |
| 102 | +The production bundle is code split and routes such as `/` and `/todos` |
| 103 | +are lazy loaded. Code splitting is handled by the rollup_bundle rule |
| 104 | +which now supports the new code splitting feature in rollup. |
| 105 | + |
| 106 | +Note: code splitting is _not_ supported in development mode yet so the |
| 107 | +`//src:devserver` target does not serve a code split bundle. The dynamic |
| 108 | +`import()` statements will resolve to modules that are served in the initial |
| 109 | +JS payload. |
| 110 | + |
| 111 | +## Npm dependencies |
| 112 | + |
| 113 | +Having a local `node_modules` folder setup by `yarn` or `npm` is not |
| 114 | +necessary when building this example with Bazel. This example makes use |
| 115 | +of Bazel managed npm dependencies (https://github.com/bazelbuild/rules_nodejs#using-bazel-managed-dependencies) |
| 116 | +which means Bazel will setup the npm dependencies in your `package.json` for you |
| 117 | +outside of your local workspace for use in the build. |
| 118 | + |
| 119 | +However, you may still want to run `yarn` or `npm` to manually |
| 120 | +setup a local `node_modules` folder for editor and tooling support. |
| 121 | + |
| 122 | +## Deployment |
| 123 | + |
| 124 | +### Firebase |
| 125 | + |
| 126 | +We use the standard firebase deploy command. |
| 127 | + |
| 128 | +Run `yarn deploy` to release changes to bazel.angular.io. |
| 129 | + |
| 130 | +### Kubernetes Engine |
| 131 | +We use Bazel's docker support to package up our production server for deployment. |
| 132 | +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. |
| 133 | +This example is configured to run on Google Kubernetes Engine, so we can have an elastic pool of backend machines behind a load balancer. |
| 134 | +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. |
| 135 | + |
| 136 | +The application is currently live at http://35.197.115.230/ |
| 137 | + |
| 138 | +To run it under docker: |
| 139 | + |
| 140 | +``` |
| 141 | +$ bazel run src:nodejs_image -- --norun |
| 142 | +$ docker run --rm -p 8080:8080 bazel/src:nodejs_image |
| 143 | +``` |
| 144 | + |
| 145 | +Deploy to production: |
| 146 | + |
| 147 | +1. Install gcloud and kubectl |
| 148 | +1. Authenticate to the Google Container Registry |
| 149 | + `gcloud auth configure-docker` |
| 150 | +1. Authenticate to Kubernetes Engine |
| 151 | + `gcloud container clusters get-credentials angular-bazel-example --zone=us-west1-a` |
| 152 | +1. For the first deployment: `bazel run :deploy.create` |
| 153 | +1. To update: `bazel run :deploy.replace` |
| 154 | + |
| 155 | +Tips: |
| 156 | + |
| 157 | +``` |
| 158 | +# Run the binary without docker |
| 159 | +$ bazel run src:nodejs_image.binary |
| 160 | + # What's in the image? |
| 161 | +$ bazel build src:nodejs_image && file-roller dist/bin/src/nodejs_image-layer.tar |
| 162 | + # Tear down all running docker containers |
| 163 | +$ docker rm -f $(docker ps -aq) |
| 164 | + # Hop into the running image on kubernetes |
| 165 | +$ kubectl exec angular-bazel-example-prod-3285254973-ncv3g -it -- /bin/bash |
| 166 | +``` |
0 commit comments