-
Notifications
You must be signed in to change notification settings - Fork 84
Home
Welcome to Bazel! This is the build tool used at Google for nearly all projects, including all Frontend work. We like it a lot, so much so that ex-googlers have started clones like Pants and Buck, and probably a bunch of others, before Bazel was open-sourced. Now you don’t need to clone it, and can share the same build tooling as the Angular developers at Google.
We find a few key advantages from using Bazel, and all of them are benefits to engineering productivity.
-
Can parallelize a build across workers in the cloud, without touching the Build definitions
-
Builds are highly incremental - typically the work required to re-build is proportional to what you changed
-
Almost never need to “clean” the output tree, so you can stay incremental
-
A CI system can run all the tests affected by a change across a wide range of interdependent packages, and can also be incremental
-
Plugins, known as rules, are highly composable. You can generally take two different rules written without knowledge of each other, and just plug them together without surprising broken interactions between them
This guide assumes you are already familiar with JS/TypeScript and/or Angular development.
Bazel will install its own toolchain for builds, so you don’t need much installed even on a new computer.
- Mac:
brew install bazel
using Homebrew - Windows:
choco install bazel
using Chocolatey - Others: https://docs.bazel.build/versions/master/install.html
Install it with your package manager, eg. npm install --save-dev @bazel/ibazel
or yarn add -D @bazel/ibazel
Note: we don’t recommend global install, because it’s so non-hermetic: who knows which version you or your teammate are running?
To use it, just replace bazel
with ibazel
in any command. This assumes ibazel
is on your path: see https://github.com/bazelbuild/bazel-watcher/issues/45 for comments about how to set this up.
This is a useful tool for visualizing the dependency graph.
- Mac:
brew install graphviz
- Others: TODO
Bazel takes options on the command line, but any option can be placed in a unix-style RC file instead.
We recommend adding a tools/bazel.rc
file in your workspace root, which will be checked into version control and shared with your team and CI. If you have personal preferences for this project, you can add these to the .bazelrc
file in the project root, and add that file to your .gitignore
. If you have personal preferences that you'd like to apply globally, put them in the .bazelrc
file in your home directory. See bazelrc for the full docs.
Here is a template tools/bazel.rc
file to get you started:
###############################
# Directory structure #
###############################
# Don't create bazel-* symlinks in the WORKSPACE directory.
# These require .gitignore and may scare users.
# Also, it's a workaround for https://github.com/bazelbuild/rules_typescript/issues/12
# which affects the common case of having `tsconfig.json` in the WORKSPACE directory.
#
# Instead, you should run `bazel info bazel-bin` to find out where the outputs went.
build --symlink_prefix=/
# Another good choice is to create a dist/ directory. Then you can use
#
# build --symlink_prefix=dist/
#
# to get folders like dist/bin
# But be aware, this will still create a bazel-out symlink in your project directory.
# You may still need to exclude that, eg. from the editor's search path.
###############################
# Output #
###############################
# A more useful default output mode for bazel query
# Prints eg. "ng_module rule //foo:bar" rather than just "//foo:bar"
query --output=label_kind
# Don't print every dependency in :node_modules, for example
query --noimplicit_deps
# By default, failing tests don't print any output, it goes to the log file
test --test_output=errors
# Show which actions are run under workers,
# and print all the actions running in parallel.
# Helps to demonstrate that bazel uses all the cores on the machine.
build --experimental_ui
test --experimental_ui
###############################
# Typescript / Angular / Sass #
###############################
# Make TypeScript and Angular compilation fast, by keeping a few copies of the compiler
# running as daemons, and cache SourceFile AST's to reduce parse time.
build --strategy=TypeScriptCompile=worker --strategy=AngularTemplateCompile=worker
# Enable debugging tests with --config=debug
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
Using the graphviz optional dependency, you'll have a program dot
which you can use with bazel query:
$ bazel query --output=graph ... | dot -Tpng > graph.png
"Rules" are like plugins for Bazel. Many rule sets are available. This guide documents the ones maintained by the Angular team at Google.
Rules are used in BUILD.bazel
files, which are markers for the packages in your workspace. Each BUILD.bazel
file declares a separate package to Bazel, though you could have more coarse-grained distributions so the packages you publish to eg. npm might be made up of many Bazel packages. Note that Bazel also allows these files to be called BUILD
, though you should be careful not to have collisions with directories named build
since case-insensitive filesystems will do The Wrong Thing.
In the BUILD.bazel
file, each rule must first be imported, using the load
statement. Then the rule is called with some attributes, and the result of calling the rule is that you've declared to Bazel how it can derive some outputs given some inputs and dependencies. Then later, when you run a bazel
command line, Bazel will load all the rules you've declared to determine an absolute ordering of what needs to be run. Note that only the rules needed to produce the requested output will actually be executed.
For these rules to work, Bazel needs to partner with your package manager (Yarn or npm) so that dependencies are available to the toolchain. For example, the @bazel/typescript
package on NPM provides the TypeScript bazel rules used below. That means we need to install the dependencies using the package manager before running the first build.
Then we need to tell Bazel how to find the rules, using a WORKSPACE
file in the root of your workspace (typically that's the root of your repository), and then we need to declare a filegroup
rule that tells Bazel how to find the installed dependencies in the node_modules
directory.
See the Installation instructions for how to set up these prerequisites.
If you want to run
bazel
commands from thepackage.json
scripts, you must take care not to lock the Bazel server in the initialbazel run @yarn...
command. Otherwise the script will hang trying to run Bazel again underneath Bazel -- it is not re-entrant. See https://github.com/angular/tsickle/commit/d5ab3942c99ed991fa51fc4b21f9e4627afefeb8 for a workaround using the--script_path
argument tobazel run
.
The NodeJS rules (rules_nodejs) allow us to run JavaScript under Bazel, either as a tool that runs as part of the toolchain, or as a test or a binary program the users asks Bazel to execute.
nodejs_binary
makes a runnable program based on some JS files and an entry point path, relative to the output root. To provide extra inputs which must be available to be read at runtime, put these in the data
attribute. You can call this rule to produce a target that the user can run:
BUILD.bazel
nodejs_binary(
name = "hello_world",
...
)
$ bazel run :hello_world
Hello, world!
jasmine_node_test
runs some JavaScript spec files through the Jasmine test framework. This is used with the bazel test
command.
Details about these rules can be found in the README of the rules_nodejs repository.
TypeScript is a popular typed language that is a superset of the JavaScript you already know, and compiles down to plain JavaScript. The TypeScript rules live at rules_typescript.
To set up the TypeScript rules, add them to your WORKSPACE
file:
git_repository(
name = "build_bazel_rules_typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
tag = "0.6.1",
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
ts_repositories()
Finally, be sure you added the --strategy
settings above to your bazelrc file to make the TypeScript compiler run faster.
ts_library
compiles one package of TypeScript code. Each library is compiled independently, using the .d.ts
declaration files from the dependencies. That means a package is only re-built when an API it depends on is changed.
Attributes:
-
srcs
are some TypeScript files (.ts
and.d.ts
) -
deps
are any rules that produce.d.ts
outputs, typically otherts_library
rules. -
tsconfig
points to your tsconfig.json file, which is important so the editor uses the same TypeScript settings as Bazel.
Outputs:
- default:
.d.ts
file for each input.ts
file. - in the same output directory with the
.d.ts
outputs, there is an ES5 (devmode).js
file, intended for consumption by rules that depend (maybe transitively) on this one.
We recommend few tsconfig.json files, ideally just one for your whole repository, or even your whole company if it’s not too late (we do that at Google).
Note that Bazel controls parts of the tsconfig, namely those related to locating inputs and outputs, managing dependencies on typings, and producing JavaScript output that’s readable by downstream tooling. (Currently this format is un-bundled UMD modules, wrapping both named (non-anonymous) AMD modules and commonjs modules. Bazel may introduce new requirements on your TS code, for example, we always use –declarations
to produce .d.ts
outputs needed by dependent rules, and your code may have some errors which only manifest under that rule.
If you find this rule is running slowly, consider breaking it into multiple rules, then declare the dependencies between them (you may have to do some refactoring to find a non-cyclical partitioning of the code, otherwise Bazel errors)
The ts_devserver
rule brings up a development server from your application sources. It's intended to be used with bazel run
(or ibazel run
in watch mode).
Attributes:
-
deps
are your application sources, typicallyts_library
orng_module
targets -
scripts
are other sources which are not dependencies of yourdeps
, but which should be included in the bundled JavaScript -
serving_path
is what URL the server exposes for the bundled JavaScript. This is optional; if not specified the bundle is served at/_/ts_scripts.js
-
static_files
are other files, such as images or HTML, which should be served. Note that the devserver only serves files from the package where thets_devserver
rule is declared. -
entry_module
is the AMD module name of the application entry point. It should start with your workspace name, eg.my_workspace/path/to/main
for an inputpath/to/main.js
Notes:
- In development mode, the
ts_library
rule produces JavaScript in a UMD format, with named AMD modules inside. This allows us to bundle them with a simplecat
command, which is fast and simple. - The
ts_devserver
relies onibazel
to expose a livereload server. It will inject a livereload script into the page so you don't need to reload when the app is re-built.
The Angular rules run the Angular compiler (ngc
) on individual packages. These live in the Angular bazel package. Issues can be filed in the main issue tracker, https://github.com/angular/angular/issues where we'll add a comp: bazel
label.
As with the TypeScript rules, we first install the npm dependency, eg.
$ bazel run @yarn//:yarn -- add -dev @angular/bazel
and install the Bazel dependency in WORKSPACE
:
local_repository(
name="angular",
path="node_modules/@angular/bazel"
)
We must also do an extra step to generate code for our dependencies. Since Angular libraries (including @angular/common
) are distributed only with JavaScript and Angular Metadata, they are incomplete. In a non-Bazel setup, the ngc
command compiles the whole world, including the libraries in node_modules
. Since we only compile one package at a time in Bazel, we must bootstrap this by compiling the libraries first before any ng_module
rules execute.
Currently we recommend doing this in your package.json
as a postinstall
script. We'll just run ngc
like any standard non-Bazel application does, but with zero application sources. This will just leave the generated code in the library directories under node_modules
. Any time you re-install your dependencies, this postinstall
script makes sure they have generated code too.
Around the time of Angular 6 we expect this step can be removed, follow https://github.com/angular/angular/issues/18810
{
"scripts": {
"postinstall": "ngc -p angular.tsconfig.json",
}
}
This requires an extra angular.tsconfig.json
file inside your project, which can be short:
{
"compilerOptions": {
"lib": [
"dom",
"es2015"
],
"experimentalDecorators": true,
"types": []
},
"include": [
"node_modules/@angular/**/*"
],
"exclude": [
"node_modules/@angular/bazel/**",
"node_modules/@angular/compiler-cli/**",
"node_modules/@angular/tsc-wrapped/**"
]
}
ng_module
runs the Angular compiler on one package of Angular code. At Google, we make our packages small, typically 1:1 with an @NgModule declaration in the code, so the rule is named to be synonymous with this convention.
ng_module
behaves exactly the same as ts_library
, except that it accepts additional attributes:
-
assets
point to .html and .css inputs. If you use SASS, the asset should be the label of the sass_binary rule. The angular compiler produces extra JavaScript output corresponding with these inputs.
Outputs:
- same as for ts_library, with the addition of
.ngsummary.json
files which are forngc
what.d.ts
files are fortsc
- also produces
.ngfactory.d.ts/.ngfactory.js
files, but we plan to stop producing these for Angular 6
If you build the code using the rules above, you'll have .d.ts
and .js
files, but don't know how to use them.
Soon we'll have
- a karma rule that runs unit tests
- a protractor rule that runs end-to-end tests against real running server(s)
- a production bundling/optimizing rule that generates a distribution of the app (or library)
The repository https://github.com/alexeagle/angular-bazel-example contains the canonical example maintained by the Angular team at Google.
The master
branch reflects what parts we suggest trying today. Everything in that branch is supported for registered early adopters of ABC, and is roughly "Beta" quality software (still subject to design changes, and not well documented).
Other branches of this repository demonstrate work-in-progress, such as a devserver, karma testing, and production bundling. These bits are experimental and not supported for use by anyone. During 2017Q4 we expect that these will mature to "beta" status, at which time usage will be demonstrated on the master
branch.