43 |
44 |
45 |
--------------------------------------------------------------------------------
/.circleci/bazel.rc:
--------------------------------------------------------------------------------
1 | # These options are enabled when running on CI
2 | # We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
3 | # See remote cache documentation in /docs/BAZEL.md
4 |
5 | # Don't be spammy in the logs
6 | # TODO: re-enable after we deal with 10m timeout on circleci
7 | #build --noshow_progress
8 |
9 | # Don't run manual tests
10 | test --test_tag_filters=-manual
11 |
12 | # Print all the options that apply to the build.
13 | # This helps us diagnose which options override others
14 | # (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
15 | build --announce_rc
16 |
17 | # Prevent unstable environment variables from tainting cache keys
18 | build --experimental_strict_action_env
19 |
20 | # Save downloaded repositories such as the go toolchain
21 | # This directory can then be included in the CircleCI cache
22 | # It should save time running the first build
23 | build --experimental_repository_cache=/home/circleci/bazel_repository_cache
24 |
25 | # Workaround https://github.com/bazelbuild/bazel/issues/3645
26 | # Bazel doesn't calculate the memory ceiling correctly when running under Docker.
27 | # Limit Bazel to consuming 2048K of RAM
28 | build --local_resources=2048,1.0,1.0
29 | # Also limit Bazel's own JVM heap to stay within our 4G container limit
30 | startup --host_jvm_args=-Xmx1408m
31 |
32 | # Use fixed chunk names for code-split bundles in CI
33 | # so that GitHub buildsize bot reports are accurate
34 | build --define=ROLLUP_BUNDLE_FIXED_CHUNK_NAMES=1
35 |
--------------------------------------------------------------------------------
/src/todos/todos.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {Store} from '@ngrx/store';
3 | import {Observable} from 'rxjs';
4 |
5 | import {ADD_TODO, DELETE_TODO, TOGGLE_DONE, UPDATE_TODO} from '../reducers/reducers';
6 |
7 | @Component({
8 | selector: 'todos',
9 | templateUrl: './todos.component.html',
10 | styleUrls: ['./todos.component.css']
11 | })
12 | export class TodosComponent implements OnInit {
13 | todos$: Observable;
14 | todo: string;
15 | editing = false;
16 | indexToEdit: number|null;
17 |
18 | constructor(private store: Store) {}
19 |
20 | ngOnInit() {
21 | this.todos$ = this.store.select('todoReducer');
22 | }
23 |
24 | addTodo(value) {
25 | this.store.dispatch({type: ADD_TODO, payload: {value, done: false}});
26 | this.todo = '';
27 | }
28 |
29 | deleteTodo(index) {
30 | this.store.dispatch({type: DELETE_TODO, payload: {index}});
31 | }
32 |
33 | editTodo(todo, index) {
34 | this.editing = true;
35 | this.todo = todo.value;
36 | this.indexToEdit = index;
37 | }
38 |
39 | cancelEdit() {
40 | this.editing = false;
41 | this.todo = '';
42 | this.indexToEdit = null;
43 | }
44 |
45 | updateTodo(updatedTodo) {
46 | this.store.dispatch(
47 | {type: UPDATE_TODO, payload: {index: this.indexToEdit, newValue: updatedTodo}});
48 | this.todo = '';
49 | this.indexToEdit = null;
50 | this.editing = false;
51 | }
52 |
53 | toggleDone(todo, index) {
54 | this.store.dispatch({type: TOGGLE_DONE, payload: {index, done: todo.done}});
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/hello-world/hello-world.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2 | import {By} from '@angular/platform-browser';
3 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
4 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
5 |
6 | import {HelloWorldComponent} from './hello-world.component';
7 | import {HelloWorldModuleNgSummary} from './hello-world.module.ngsummary';
8 |
9 | // TODO(alexeagle): this helper should be in @angular/platform-browser-dynamic/testing
10 | try {
11 | TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
12 | } catch {
13 | // Ignore exceptions when calling it multiple times.
14 | }
15 |
16 | describe('BannerComponent (inline template)', () => {
17 | let comp: HelloWorldComponent;
18 | let fixture: ComponentFixture;
19 | let el: HTMLElement;
20 |
21 | beforeEach(async(() => {
22 | TestBed.configureTestingModule({
23 | declarations: [HelloWorldComponent], // declare the test component
24 | aotSummaries: HelloWorldModuleNgSummary,
25 | imports: [BrowserAnimationsModule],
26 | });
27 | TestBed.compileComponents();
28 | }));
29 |
30 | beforeEach(() => {
31 | fixture = TestBed.createComponent(HelloWorldComponent);
32 | comp = fixture.componentInstance;
33 | el = fixture.debugElement.query(By.css('div')).nativeElement;
34 | });
35 |
36 | it('should display original title', () => {
37 | fixture.detectChanges();
38 | expect(el.textContent).toContain(comp.name);
39 | });
40 |
41 | it('should display a different test title', () => {
42 | comp.name = 'Test';
43 | fixture.detectChanges();
44 | expect(el.textContent).toContain('Test');
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/hello-world/BUILD.bazel:
--------------------------------------------------------------------------------
1 | load("@angular//:index.bzl", "ng_module")
2 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test_suite")
3 | load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
4 |
5 | package(default_visibility = ["//visibility:public"])
6 |
7 | sass_binary(
8 | name = "hello-world-styles",
9 | src = "hello-world.component.scss",
10 | deps = ["//src/styles:shared"],
11 | )
12 |
13 | ng_module(
14 | name = "hello-world",
15 | srcs = [
16 | "hello-world.component.ts",
17 | "hello-world.module.ts",
18 | ],
19 | assets = [
20 | ":hello-world.component.html",
21 | ":hello-world-styles",
22 | ],
23 | tsconfig = "//src:tsconfig.json",
24 | deps = [
25 | "//src/lib",
26 | "//src/material",
27 | "@angular//packages/core",
28 | "@angular//packages/forms",
29 | "@angular//packages/router",
30 | ],
31 | )
32 |
33 | ts_library(
34 | name = "test_lib",
35 | testonly = 1,
36 | srcs = glob(["*.spec.ts"]),
37 | deps = [
38 | ":hello-world",
39 | "@angular//packages/core",
40 | "@angular//packages/core/testing",
41 | "@angular//packages/platform-browser",
42 | "@angular//packages/platform-browser-dynamic/testing",
43 | "@angular//packages/platform-browser/animations",
44 | "@npm//@types/jasmine",
45 | "@npm//@types/node",
46 | ],
47 | )
48 |
49 | ts_web_test_suite(
50 | name = "test",
51 | srcs = ["@npm//node_modules/tslib:tslib.js"],
52 | # do not sort
53 | bootstrap = [
54 | "@npm//node_modules/zone.js:dist/zone-testing-bundle.js",
55 | "@npm//node_modules/reflect-metadata:Reflect.js",
56 | "//src:module-id.js",
57 | ],
58 | browsers = [
59 | "@io_bazel_rules_webtesting//browsers:chromium-local",
60 | # TODO(gregmagolan): re-enable firefox testing once fixed
61 | # See https://github.com/bazelbuild/rules_typescript/issues/296
62 | #"@io_bazel_rules_webtesting//browsers:firefox-local",
63 | ],
64 | deps = [
65 | ":test_lib",
66 | ],
67 | )
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.0.1",
4 | "description": "Demo of bazel rules for angular",
5 | "license": "Apache 2.0",
6 | "engines": {
7 | "node": ">=10.9.0 <11.0.0",
8 | "yarn": ">=1.9.2 <2.0.0"
9 | },
10 | "dependencies": {
11 | "@ngrx/store": "7.0.0",
12 | "express": "^4.16.4",
13 | "rxjs": "6.3.3",
14 | "systemjs": "0.21.5",
15 | "tslib": "1.9.3",
16 | "zone.js": "0.8.26",
17 | "compression": "1.7.2"
18 | },
19 | "devDependencies": {
20 | "@angular/animations": "7.1.3",
21 | "@angular/bazel": "7.1.3",
22 | "@angular/cdk": "7.1.1",
23 | "@angular/common": "7.1.3",
24 | "@angular/compiler": "7.1.3",
25 | "@angular/compiler-cli": "7.1.3",
26 | "@angular/core": "7.1.3",
27 | "@angular/material": "7.1.1",
28 | "@bazel/benchmark-runner": "0.1.0",
29 | "@bazel/buildifier": "^0.20.0",
30 | "@bazel/ibazel": "0.9.0",
31 | "@bazel/karma": "0.22.0",
32 | "@bazel/typescript": "0.22.0",
33 | "@types/jasmine": "3.3.5",
34 | "@types/node": "10.12.21",
35 | "clang-format": "1.2.4",
36 | "husky": "0.14.3",
37 | "protractor": "5.4.1",
38 | "typescript": "3.1.1"
39 | },
40 | "scripts": {
41 | "build": "bazel build //src:bundle",
42 | "serve": "ibazel run //src:devserver",
43 | "serve-prod": "bazel run //src:prodserver",
44 | "e2e": "bazel test //e2e:all",
45 | "test": "bazel test //src/...",
46 | "benchmark": "ibazel-benchmark-runner //src:devserver src/hello-world/hello-world.component.ts --url=http://localhost:5432",
47 | "postinstall": "ngc -p postinstall.tsconfig.json",
48 | "bazel:format": "find . -type f \\( -name \"*.bzl\" -or -name WORKSPACE -or -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs buildifier -v --warnings=args-order,attr-cfg,attr-license,attr-non-empty,attr-output-default,attr-single-file,constant-glob,ctx-actions,ctx-args,depset-iteration,depset-union,dict-concatenation,duplicated-name,filetype,git-repository,http-archive,integer-division,load,load-on-top,native-build,native-package,out-of-order-load,output-group,package-name,package-on-top,positional-args,redefined-variable,repository-name,same-origin-load,string-iteration,unsorted-dict-items,unused-variable",
49 | "bazel:lint": "yarn bazel:format --lint=warn",
50 | "bazel:lint-fix": "yarn bazel:format --lint=fix",
51 | "format": "git-clang-format",
52 | "precommit": "check-clang-format \"yarn format\""
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/images/bazel-navbar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
39 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # This file configures the build at https://circleci.com/gh/alexeagle/angular-bazel-example
2 | # Complete documentation is at https://circleci.com/docs/2.0/
3 |
4 | # CircleCI lets us pick the key for storing one or more caches, to speed up subsequent builds.
5 | # We can use this to avoid re-fetching our dependencies from npm on every build.
6 | # To ensure we don't load a stale cache, we invalidate it based on the entries in the key:
7 | # - the checksum of Yarn's lock file
8 | # - the branch we are on, which really shouldn't be needed since the yarn lock file should be hermetic
9 | # - the docker image tag, working around an issue we saw where changing docker images causes permission
10 | # errors when restoring the cache, like when the user we run as changes
11 | var_1: &cache_key angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.8.0
12 |
13 | # Each job will inherit these defaults
14 | var_2: &job_defaults
15 | working_directory: ~/ng
16 | docker:
17 | # Use the -browsers version just to get the right .so files on the disk
18 | # We'll actually download a chromium version under the rules_webtesting
19 | - image: circleci/node:10.12-browsers
20 |
21 | # After checkout, rebase on top of master, because we want to test the proposed merge of a
22 | # onto the target branch, not just test what's on the user's fork.
23 | # Similar to travis behavior, but not quite the same.
24 | # See https://discuss.circleci.com/t/1662
25 | var_3: &post_checkout
26 | post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
27 |
28 | # Opt-in to the new goodness
29 | version: 2
30 |
31 | # These jobs will run in parallel, and report separate statuses to GitHub PRs
32 | jobs:
33 | lint:
34 | docker:
35 | - image: circleci/node:10.12
36 | steps:
37 | - checkout:
38 | <<: *post_checkout
39 |
40 | - restore_cache:
41 | key: *cache_key
42 |
43 | # Install Bazel
44 | - run: yarn install
45 |
46 | # Run the Buildifier to check our Bazel rules for format issues.
47 | - run: 'yarn bazel:format --mode=check ||
48 | (echo "BUILD files not formatted. Please run ''yarn bazel:format --mode=fix''" ; exit 1)'
49 |
50 | # Run the Buildifier to check our Bazel rules for lint issues.
51 | # Note: The `--lint=warn` will auto fixe (re-write) the affected files.
52 | - run: 'yarn bazel:format --lint=warn ||
53 | (echo "BUILD files contain unresolved lint errors. Please fix manually the remaining errors." ; exit 1)'
54 |
55 | build:
56 | <<: *job_defaults
57 | resource_class: xlarge
58 | steps:
59 | - checkout:
60 | <<: *post_checkout
61 |
62 | - restore_cache:
63 | key: *cache_key
64 |
65 | - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
66 |
67 | # Install Bazel from NPM
68 | - run: yarn
69 |
70 | # Build and Test
71 | - run: yarn test
72 | - run: yarn e2e
73 |
74 | - store_artifacts:
75 | path: dist/bin/src/bundle.min.js
76 | destination: bundle.min.js
77 |
78 | - store_artifacts:
79 | path: dist/bin/src/bundle.cs.min
80 | destination: bundle.cs.min
81 |
82 | benchmark:
83 | <<: *job_defaults
84 | resource_class: xlarge
85 | steps:
86 | - checkout:
87 | <<: *post_checkout
88 |
89 | - restore_cache:
90 | key: *cache_key
91 |
92 | # Install Bazel/ibazel from NPM
93 | - run: yarn
94 |
95 | # Run `bazel build` first as a temporary workaround to unexpected
96 | # benchmark failure when entire build runs withing ibazel-benchmark-runner
97 | # ```
98 | # Error running Bazel unexpected EOF
99 | # [ibazel-benchmark-runner] iBazel process exited unexpectedly 4 null
100 | # error Command failed with exit code 1.
101 | # ```
102 | # TODO(gregmagolan): remove this once issue is resolved
103 | - run: ./node_modules/.bin/bazel build ...
104 |
105 | # Run the benchmark
106 | - run: yarn benchmark
107 |
108 | # If we get this far, save the node_modules directory for use next time.
109 | - save_cache:
110 | key: *cache_key
111 | paths:
112 | - "node_modules"
113 |
114 | workflows:
115 | version: 2
116 | default_workflow:
117 | jobs:
118 | - lint
119 | - build
120 | - benchmark
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/alexeagle/angular-bazel-example)
2 |
3 | # Example of building an Angular app with Bazel
4 |
5 | **This is experimental! 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 | See http://g.co/ng/abc for an overview.
10 |
11 | You can read the documentation in the wiki of this repository to understand how
12 | this works.
13 |
14 | Follow https://github.com/angular/angular/issues/19058 for updates.
15 |
16 | ## Installation
17 |
18 | You only need to install one build tool, and which one you choose typically depends on what kind of development you do most often.
19 |
20 | If you're a frontend developer, you should install NodeJS and yarn.
21 | The `package.json` file has an `engines` section which indicates the range of NodeJS and yarn versions that you could use.
22 | You simply run `yarn` commands shown below, and don't need to install Bazel or any other dependencies.
23 |
24 | If you're a full-stack developer, you might be using Bazel for your backend already.
25 | In this case, you should install Bazel following instructions at http://bazel.build.
26 | Also install `ibazel`, which is a watch mode for Bazel not included in the standard distribution. See https://github.com/bazelbuild/bazel-watcher#installation.
27 | The `WORKSPACE` file has a `check_bazel_version` call which will print an error if your Bazel version is not in the supported range.
28 | You simply run `bazel` commands shown below, and don't need to install NodeJS, yarn, or any other dependencies.
29 |
30 | ## Development
31 |
32 | First we'll run the development server:
33 |
34 | ```bash
35 | $ yarn serve
36 | # or
37 | $ ibazel run //src:devserver
38 | ```
39 |
40 | 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.
41 | When the re-build is finished, it will trigger a LiveReload in the browser.
42 |
43 | This command prints a URL on the terminal. Open that page to see the demo app
44 | running. Now you can edit one of the source files (`src/lib/file.ts` is an easy
45 | one to understand and see the effect). As soon as you save a change, the app
46 | should refresh in the browser with the new content. Our intent is that this time
47 | is less than two seconds, even for a large application.
48 |
49 | Control-C twice to kill the devserver.
50 |
51 | ## Testing
52 |
53 | We can also run all the unit tests:
54 |
55 | ```bash
56 | $ yarn test
57 | # or
58 | $ bazel test //src/...
59 | ```
60 |
61 | Or run the end-to-end tests:
62 |
63 | ```bash
64 | $ yarn e2e
65 | # or
66 | $ bazel test //e2e/...
67 | ```
68 |
69 | In this example, there is a unit test for the `hello-world` component which uses
70 | the `ts_web_test_suite` rule. There are also protractor e2e tests for both the
71 | `prodserver` and `devserver` which use the `protractor_web_test_suite` rule.
72 |
73 | Note that Bazel will only re-run the tests whose inputs changed since the last run.
74 |
75 | ## Production
76 |
77 | We can run the application in production mode, where the code has been bundled
78 | and optimized. This can be slower than the development mode, because any change
79 | requires re-optimizing the app. This example uses Rollup and Uglify, but other
80 | bundlers can be integrated with Bazel.
81 |
82 | ```bash
83 | $ yarn serve-prod
84 | # or
85 | $ bazel run //src:prodserver
86 | ```
87 |
88 | ### Code splitting
89 |
90 | The production bundle is code split and the `/` and `/todos` routes
91 | are lazy loaded. Code splitting is handled by the rollup_bundle rule
92 | which now supports the new code splitting feature in rollup.
93 |
94 | Note: code splitting is _not_ supported in development mode yet so the
95 | `//src:devserver` target does not serve a code split bundle. For this
96 | reason, development and production use different main entry points
97 | (`main.dev.ts` and `main.ts`) and different root modules
98 | (`app.module.dev.ts` and `app.module.ts`). The difference in
99 | the entry points and modules is how routes are loaded, with production
100 | lazy loading routes and development using a custom `NgModuleFactoryLoader`
101 | loader to disable lazy loading. `enableProdMode()` is
102 | also called in the production entry point.
103 |
104 | ## Npm dependencies
105 |
106 | Having a local `node_modules` folder setup by `yarn` or `npm` is not
107 | necessary when building this example with Bazel. This example makes use
108 | of Bazel managed npm dependencies (https://github.com/bazelbuild/rules_nodejs#using-bazel-managed-dependencies)
109 | which means Bazel will setup the npm dependencies in your `package.json` for you
110 | outside of your local workspace for use in the build.
111 |
112 | However, you may still want to run `yarn` or `npm` to manually
113 | setup a local `node_modules` folder for editor and tooling support.
114 |
--------------------------------------------------------------------------------
/src/BUILD.bazel:
--------------------------------------------------------------------------------
1 | load("@angular//:index.bzl", "ng_module")
2 | load("@build_bazel_rules_nodejs//:defs.bzl", "history_server", "rollup_bundle")
3 | load("@build_bazel_rules_nodejs//internal/web_package:web_package.bzl", "web_package")
4 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_config", "ts_devserver")
5 |
6 | package(default_visibility = ["//visibility:public"])
7 |
8 | ts_config(
9 | name = "tsconfig-test",
10 | src = "tsconfig-test.json",
11 | deps = [":tsconfig.json"],
12 | )
13 |
14 | ng_module(
15 | name = "src",
16 | srcs = glob(
17 | ["*.ts"],
18 | exclude = ["main.ts"],
19 | ),
20 | assets = [
21 | ":app.component.html",
22 | "//src/styles:main",
23 | ],
24 | tsconfig = ":tsconfig.json",
25 | deps = [
26 | "//src/hello-world",
27 | "//src/material",
28 | "//src/todos",
29 | "@angular//packages/core",
30 | "@angular//packages/platform-browser",
31 | "@angular//packages/platform-browser/animations",
32 | "@angular//packages/router",
33 | "@npm//@ngrx/store",
34 | ],
35 | )
36 |
37 | # We always strip these paths off the front of any assets we serve
38 | _ROOT_DIRS = [
39 | "npm/node_modules/zone.js/dist",
40 | "npm/node_modules/@angular/material/prebuilt-themes",
41 | "npm/node_modules/@ngrx/store/bundles",
42 | ]
43 |
44 | # This devserver is written in Go and is super-fast.
45 | # It doesn't run any bundler or code splitter. Instead, it concatenates
46 | # UMD JavaScript code on-the-fly in-memory.
47 | # This scales really well for massive codebases.
48 | ts_devserver(
49 | name = "devserver",
50 | # serve these files rooted at /
51 | additional_root_paths = _ROOT_DIRS,
52 | # Serve these files but don't inject tags for them into the index file
53 | # This might be because we only want to lazy-load these scripts on-demand,
54 | # or because they aren't compatible with Require.js so we must use a runtime
55 | # loader to load them.
56 | data = [
57 | "//src/images:image_assets",
58 | "@npm//node_modules/@ngrx/store:bundles/store.umd.min.js",
59 | ],
60 | # Start from the development version of the main
61 | entry_module = "angular_bazel_example/src/main.dev",
62 | #