├── .bazelversion ├── src ├── robots.txt ├── app │ ├── hello-world │ │ ├── secondary-styles.scss │ │ ├── hello-world.component.scss │ │ ├── hello-world.component.html │ │ ├── hello-world.component.ts │ │ ├── hello-world.module.ts │ │ ├── hello-world.component.spec.ts │ │ └── BUILD.bazel │ ├── todos │ │ ├── todos.component.scss │ │ ├── reducers │ │ │ ├── BUILD.bazel │ │ │ └── reducers.ts │ │ ├── todos.module.ts │ │ ├── BUILD.bazel │ │ ├── todos.component.html │ │ └── todos.component.ts │ ├── app.server.module.ts │ ├── app.component.ts │ ├── home │ │ ├── BUILD.bazel │ │ ├── home.ts │ │ └── home.html │ ├── BUILD.bazel │ ├── app.module.ts │ ├── app-routing.module.ts │ ├── service-worker.service.ts │ └── app.component.html ├── assets │ ├── icons │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png │ ├── BUILD.bazel │ ├── github-circle-white-transparent.svg │ ├── angular-white-transparent.svg │ ├── bazel-white-transparent.svg │ ├── bazel-navbar.svg │ ├── angular-logo-with-text.svg │ └── landing.css ├── lib │ └── shorten │ │ ├── README.md │ │ ├── index.ts │ │ └── BUILD.bazel ├── tsconfig-test.json ├── main.dev.ts ├── main.prod.ts ├── rollup.config.js ├── tsconfig.server.json ├── shared │ └── material │ │ ├── BUILD.bazel │ │ └── material.module.ts ├── initialize_testbed.ts ├── example │ ├── index.html │ └── index.prod.html ├── main.ts ├── tsconfig.json ├── server.ts ├── ngsw-config.json ├── prerender-spec.ts ├── styles.scss ├── rxjs_shims.js ├── manifest.webmanifest ├── prerender.ts ├── BUILD.bazel └── index.html ├── .bazelignore ├── .gitignore ├── .firebaserc ├── favicon.ico ├── dummy_test.sh ├── firebase.json ├── tools ├── angular_ts_library.bzl ├── BUILD.bazel ├── ngsw_config.bzl └── angular_prerender.bzl ├── deployment.yaml ├── e2e ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts ├── BUILD.bazel └── protractor.on-prepare.js ├── BUILD.bazel ├── patches └── com_github_bazelbuild_bazel_toolchains.diff ├── angular.json ├── package.json ├── .bazelrc ├── WORKSPACE └── README.md /.bazelversion: -------------------------------------------------------------------------------- 1 | 3.7.1 -------------------------------------------------------------------------------- /src/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /.bazelignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | bazel-out 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | bazel-* 3 | node_modules 4 | .bazelrc.user -------------------------------------------------------------------------------- /src/app/hello-world/secondary-styles.scss: -------------------------------------------------------------------------------- 1 | #greeting { 2 | color: darkgreen; 3 | } 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "bazel-angular-io" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/favicon.ico -------------------------------------------------------------------------------- /src/app/hello-world/hello-world.component.scss: -------------------------------------------------------------------------------- 1 | .mood-icon { 2 | margin: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgechev/angular-bazel-hello-world/HEAD/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/lib/shorten/README.md: -------------------------------------------------------------------------------- 1 | # A simple example TS-only library 2 | 3 | This shows how a TypeScript library looks in a Bazel monorepo. 4 | -------------------------------------------------------------------------------- /src/app/todos/todos.component.scss: -------------------------------------------------------------------------------- 1 | .done { 2 | text-decoration: line-through; 3 | } 4 | 5 | .edit-icon { 6 | margin-right: 12px; 7 | } 8 | -------------------------------------------------------------------------------- /src/tsconfig-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jasmine"] 5 | } 6 | } -------------------------------------------------------------------------------- /dummy_test.sh: -------------------------------------------------------------------------------- 1 | echo "Just a dummy test so that we have a test target for //... on certain bazelci platforms with bazel_integration_test" 2 | exit 0 -------------------------------------------------------------------------------- /src/lib/shorten/index.ts: -------------------------------------------------------------------------------- 1 | export function shorten(s: string, length: number) { 2 | if (s.length < length) return s; 3 | return s.substr(0, length - 3) + '...'; 4 | } -------------------------------------------------------------------------------- /src/assets/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | filegroup( 4 | name = "assets", 5 | srcs = glob([ 6 | "*.svg", 7 | "**/*.png", 8 | "*.css", 9 | ]), 10 | ) 11 | -------------------------------------------------------------------------------- /src/main.dev.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to launch the application under Bazel development mode. 3 | */ 4 | import {platformBrowser} from '@angular/platform-browser'; 5 | import {AppModule} from './app/app.module'; 6 | 7 | platformBrowser().bootstrapModule(AppModule); 8 | -------------------------------------------------------------------------------- /src/app/todos/reducers/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/typescript:index.bzl", "ts_library") 2 | 3 | package(default_visibility = ["//:__subpackages__"]) 4 | 5 | ts_library( 6 | name = "reducers", 7 | srcs = glob(["*.ts"]), 8 | deps = [ 9 | "@npm//@ngrx/store", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to launch the application under Bazel production mode. 3 | */ 4 | import {enableProdMode} from '@angular/core'; 5 | import {platformBrowser} from '@angular/platform-browser'; 6 | import {AppModule} from './app/app.module'; 7 | 8 | enableProdMode(); 9 | platformBrowser().bootstrapModule(AppModule); 10 | -------------------------------------------------------------------------------- /src/rollup.config.js: -------------------------------------------------------------------------------- 1 | const {nodeResolve} = require('@rollup/plugin-node-resolve'); 2 | const commonjs = require('@rollup/plugin-commonjs'); 3 | 4 | module.exports = { 5 | plugins: [ 6 | nodeResolve({ 7 | mainFields: ['browser', 'es2015', 'module', 'jsnext:main', 'main'], 8 | }), 9 | commonjs(), 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist/bin/src/prodapp", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "/example/**", 12 | "destination": "/example/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/app.server.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {ServerModule} from '@angular/platform-server'; 3 | 4 | import {AppModule} from './app.module'; 5 | import {AppComponent} from './app.component'; 6 | 7 | @NgModule({ 8 | imports: [AppModule, ServerModule], 9 | bootstrap: [AppComponent] 10 | }) 11 | export class AppServerModule {} -------------------------------------------------------------------------------- /src/tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app-server", 5 | "module": "commonjs", 6 | "types": ["node"] 7 | }, 8 | "files": ["src/main.server.ts", "server.ts"], 9 | "angularCompilerOptions": { 10 | "entryModule": "./src/app/app.server.module#AppServerModule" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | import { ServiceWorkerService } from './service-worker.service'; 4 | 5 | @Component({selector: 'app-component', templateUrl: 'app.component.html'}) 6 | export class AppComponent { 7 | constructor(private swService: ServiceWorkerService) { 8 | this.swService.launchUpdateCheckingRoutine() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/material/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:angular_ts_library.bzl", "ng_ts_library") 2 | 3 | package(default_visibility = ["//:__subpackages__"]) 4 | 5 | ng_ts_library( 6 | name = "material", 7 | srcs = glob(["*.ts"]), 8 | tsconfig = "//src:tsconfig.json", 9 | deps = [ 10 | "@npm//@angular/core", 11 | "@npm//@angular/material", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /src/app/home/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:angular_ts_library.bzl", "ng_ts_library") 2 | 3 | package(default_visibility = ["//:__subpackages__"]) 4 | 5 | ng_ts_library( 6 | name = "home", 7 | srcs = ["home.ts"], 8 | angular_assets = ["home.html"], 9 | tsconfig = "//src:tsconfig.json", 10 | deps = [ 11 | "@npm//@angular/core", 12 | "@npm//@angular/router", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /src/app/home/home.ts: -------------------------------------------------------------------------------- 1 | import {Component, NgModule} from '@angular/core'; 2 | import {RouterModule} from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'home', 6 | templateUrl: './home.html', 7 | }) 8 | export class Home { 9 | } 10 | 11 | @NgModule({ 12 | declarations: [Home], 13 | imports: [ 14 | RouterModule.forChild([{path: '', component: Home}]), 15 | ], 16 | }) 17 | export class HomeModule { 18 | } -------------------------------------------------------------------------------- /tools/angular_ts_library.bzl: -------------------------------------------------------------------------------- 1 | "Shows how to enable both worker mode and use_angular_plugin to make a drop-in replacement for ng_module" 2 | 3 | load("@npm//@bazel/typescript:index.bzl", "ts_library") 4 | 5 | def ng_ts_library(**kwargs): 6 | ts_library( 7 | compiler = "//tools:tsc_wrapped_with_angular", 8 | supports_workers = True, 9 | use_angular_plugin = True, 10 | **kwargs 11 | ) 12 | -------------------------------------------------------------------------------- /src/app/hello-world/hello-world.component.html: -------------------------------------------------------------------------------- 1 |

Home

2 | 3 | 4 | 5 |
Hello {{ name }}
6 |
7 | 8 | 9 | 10 |

Today is {{ date }}

11 |
12 | 13 | 14 |
mood
15 |
16 |
17 | 18 | -------------------------------------------------------------------------------- /src/initialize_testbed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Provides a script to initialize TestBed before tests are run. 3 | * This file should be included in the "runtime_deps" of a "karma_web_test_suite" 4 | * rule. 5 | */ 6 | import {TestBed} from '@angular/core/testing'; 7 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 8 | 9 | TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 10 | -------------------------------------------------------------------------------- /src/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Bazel Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/lib/shorten/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/typescript:index.bzl", "ts_library") 2 | 3 | package(default_visibility = ["//:__subpackages__"]) 4 | 5 | # TODO: change entry to something other than index.js & add a package.json here 6 | # with a main field to show how to create a first-party npm lib with a package.json 7 | ts_library( 8 | name = "shorten", 9 | srcs = ["index.ts"], 10 | module_name = "@bazel/shorten", 11 | ) 12 | 13 | # TODO(alexeagle): show how it can be deployed to npm 14 | -------------------------------------------------------------------------------- /deployment.yaml: -------------------------------------------------------------------------------- 1 | # Configuration for deployment to kubernetes 2 | 3 | apiVersion: apps/v1beta1 4 | kind: Deployment 5 | metadata: 6 | name: angular-bazel-example-prod 7 | spec: 8 | replicas: 1 9 | template: 10 | metadata: 11 | labels: 12 | app: angular-bazel-example-prod 13 | spec: 14 | containers: 15 | - name: angular-bazel-example 16 | image: gcr.io/internal-200822/src:nodejs_image 17 | imagePullPolicy: Always 18 | ports: 19 | - containerPort: 8080 20 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import {AppPage} from './app.po'; 2 | 3 | describe('angular example application', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display: Hello World!', async () => { 11 | await page.navigateTo(); 12 | expect(await page.getParagraphText()).toEqual(`Hello Adolph Blain...`); 13 | await page.typeInInput('!'); 14 | expect(await page.getParagraphText()).toEqual(`Hello Adolph Blain...!`); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/hello-world/hello-world.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {shorten} from '@bazel/shorten'; 3 | import {format} from 'date-fns'; 4 | 5 | @Component({ 6 | selector: 'hello-world', 7 | templateUrl: 'hello-world.component.html', 8 | styleUrls: ['./hello-world.component.scss', './secondary-styles.scss'] 9 | }) 10 | export class HelloWorldComponent { 11 | name: string = shorten('Adolph Blaine Wolfeschlegelsteinhausenbergerdorff, Senior ', 15); 12 | date: string = format(new Date(), 'MMMM D, YYYY'); 13 | } 14 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This main entry point is used to launch the app under the 3 | * @angular-devkit/build-angular, which is the default CLI 4 | * builder. Note that for AOT, the CLI will magically replace 5 | * the bootstrap by switching platform-browser-dynamic with 6 | * platform-browser. 7 | * This file is completely unused in the Bazel build. 8 | */ 9 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 10 | 11 | import {AppModule} from './app.module'; 12 | 13 | platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.log(err)); -------------------------------------------------------------------------------- /src/app/todos/todos.module.ts: -------------------------------------------------------------------------------- 1 | import {CommonModule} from '@angular/common'; 2 | import {NgModule} from '@angular/core'; 3 | import {FormsModule} from '@angular/forms'; 4 | import {RouterModule} from '@angular/router'; 5 | import {MaterialModule} from '../../shared/material/material.module'; 6 | 7 | import {TodosComponent} from './todos.component'; 8 | 9 | @NgModule({ 10 | declarations: [TodosComponent], 11 | imports: [ 12 | CommonModule, FormsModule, RouterModule, MaterialModule, 13 | RouterModule.forChild([{path: '', component: TodosComponent}]) 14 | ], 15 | 16 | }) 17 | export class TodosModule { 18 | } 19 | -------------------------------------------------------------------------------- /src/app/hello-world/hello-world.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {FormsModule} from '@angular/forms'; 3 | import {RouterModule} from '@angular/router'; 4 | import {MaterialModule} from '../../shared/material/material.module'; 5 | 6 | import {HelloWorldComponent} from './hello-world.component'; 7 | 8 | @NgModule({ 9 | declarations: [HelloWorldComponent], 10 | imports: [ 11 | FormsModule, RouterModule, MaterialModule, 12 | RouterModule.forChild([{path: '', component: HelloWorldComponent}]) 13 | ], 14 | exports: [HelloWorldComponent], 15 | }) 16 | export class HelloWorldModule { 17 | } 18 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import {browser, by, element} from 'protractor'; 2 | 3 | export class AppPage { 4 | async navigateTo() { 5 | await browser.get(browser.baseUrl + '/hello'); 6 | return browser.waitForAngular(); 7 | } 8 | 9 | async waitForElement(el, timeout = 10000) { 10 | await browser.wait(() => el.isPresent(), timeout); 11 | await browser.wait(() => el.isDisplayed(), timeout); 12 | return el; 13 | } 14 | 15 | async getParagraphText() { 16 | return (await this.waitForElement(element(by.css('div#greeting')))).getText(); 17 | } 18 | 19 | async typeInInput(s: string) { 20 | return (await this.waitForElement(element(by.css('input')))).sendKeys(s); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tools/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") 2 | 3 | # Custom ts_library compiler that runs tsc_wrapped with angular/compiler-cli statically linked 4 | # This can be used with worker mode because we don't need the linker at runtime to make 5 | # the angular plugin loadable 6 | # Just a clone of @npm//@bazel/typescript/bin:tsc_wrapped with added deps 7 | nodejs_binary( 8 | name = "tsc_wrapped_with_angular", 9 | data = [ 10 | "@npm//@angular/compiler-cli", 11 | "@npm//@bazel/typescript", 12 | ], 13 | entry_point = "@npm//:node_modules/@bazel/typescript/internal/tsc_wrapped/tsc_wrapped.js", 14 | visibility = ["//:__subpackages__"], 15 | ) 16 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Allow uses of these JS APIs 4 | "lib": [ 5 | "dom", 6 | "es5", 7 | "es2015.collection", 8 | "es2015.iterable", 9 | "es2015.promise" 10 | ], 11 | // Permit decorator syntax 12 | "experimentalDecorators": true, 13 | // Don't scan the node_modules/@types folder for ambient types. 14 | // This would force us to have all the types in the dependencies of 15 | // each library. 16 | // Instead we'll be explicit about declaring ambient type dependencies 17 | // using the /// syntax. 18 | "types": [] 19 | } 20 | } -------------------------------------------------------------------------------- /src/assets/github-circle-white-transparent.svg: -------------------------------------------------------------------------------- 1 | github-circle-white-transparent -------------------------------------------------------------------------------- /src/app/home/home.html: -------------------------------------------------------------------------------- 1 |

Angular Bazel Example

2 | 3 | This is an example of building an Angular app at scale. 4 | It uses BUILD.bazel files to customize the configuration of Bazel. 5 | This means the application is compiled in many small libraries, giving us incremental builds. 6 | 7 | Read more about the example on the GitHub README 8 | 9 |

10 | Navigating the example 11 |

12 | 13 | This application mimics a developer console for a cloud provider. 14 | There are ten sections in the left nav, which represent ten different teams that contribute their functionality to the single-page application. 15 | This is similar to how Google Cloud Console is developed with Bazel. 16 | 17 | -------------------------------------------------------------------------------- /e2e/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/protractor:index.bzl", "protractor_web_test_suite") 2 | load("@npm//@bazel/typescript:index.bzl", "ts_library") 3 | 4 | ts_library( 5 | name = "e2e", 6 | testonly = 1, 7 | srcs = glob(["src/*.ts"]), 8 | tsconfig = "//src:tsconfig-test", 9 | deps = [ 10 | "@npm//@types/jasmine", 11 | "@npm//jasmine", 12 | "@npm//protractor", 13 | ], 14 | ) 15 | 16 | protractor_web_test_suite( 17 | name = "prodserver_test", 18 | on_prepare = ":protractor.on-prepare.js", 19 | server = "//src:prodserver", 20 | deps = [":e2e"], 21 | ) 22 | 23 | protractor_web_test_suite( 24 | name = "devserver_test", 25 | on_prepare = ":protractor.on-prepare.js", 26 | server = "//src:devserver", 27 | deps = [":e2e"], 28 | ) 29 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import 'zone.js/dist/zone-node'; 3 | 4 | import { ngExpressEngine } from '@nguniversal/express-engine'; 5 | import * as express from 'express'; 6 | import { join } from 'path'; 7 | 8 | import { AppServerModule } from './app/app.server.module'; 9 | 10 | const app = express(); 11 | const port = process.env.PORT || 4000; 12 | const DIST_FOLDER = join(process.cwd(), 'src/pwa'); 13 | 14 | app.engine('html', ngExpressEngine({ bootstrap: AppServerModule }) as any); 15 | app.set('view engine', 'html'); 16 | app.set('views', DIST_FOLDER); 17 | 18 | app.get('*.*', express.static(DIST_FOLDER, { maxAge: '1y' })); 19 | app.get('*', (req, res) => res.render('example/index', { req })); 20 | app.listen(port, () => console.log(`Server listening http://localhost:${port}`)); 21 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@k8s_deploy//:defaults.bzl", "k8s_deploy") 2 | 3 | package(default_visibility = ["//:__subpackages__"]) 4 | 5 | exports_files(["favicon.ico"]) 6 | 7 | # ts_library uses the `//:tsconfig.json` target 8 | # by default. This alias allows omitting explicit tsconfig 9 | # attribute. 10 | alias( 11 | name = "tsconfig.json", 12 | actual = "//src:tsconfig.json", 13 | ) 14 | 15 | k8s_deploy( 16 | name = "deploy", 17 | images = { 18 | "gcr.io/internal-200822/src:nodejs_image": "//src:image", 19 | }, 20 | template = ":deployment.yaml", 21 | tags = ["manual"], 22 | ) 23 | 24 | # Just a dummy test so that we have a test target for //... on certain bazelci platforms with bazel_integration_test 25 | sh_test( 26 | name = "dummy_test", 27 | srcs = ["dummy_test.sh"], 28 | ) 29 | -------------------------------------------------------------------------------- /src/app/todos/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") 2 | load("//tools:angular_ts_library.bzl", "ng_ts_library") 3 | 4 | package(default_visibility = ["//:__subpackages__"]) 5 | 6 | sass_binary( 7 | name = "todos-styles", 8 | src = "todos.component.scss", 9 | ) 10 | 11 | ng_ts_library( 12 | name = "todos", 13 | srcs = [ 14 | "todos.component.ts", 15 | "todos.module.ts", 16 | ], 17 | angular_assets = [ 18 | "todos.component.html", 19 | ":todos-styles", 20 | ], 21 | tsconfig = "//src:tsconfig.json", 22 | deps = [ 23 | "//src/app/todos/reducers", 24 | "//src/shared/material", 25 | "@npm//@angular/common", 26 | "@npm//@angular/core", 27 | "@npm//@angular/forms", 28 | "@npm//@angular/router", 29 | "@npm//@ngrx/store", 30 | "@npm//rxjs", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /src/ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "updateMode": "prefetch", 9 | "resources": { 10 | "files": [ 11 | "favicon.ico", 12 | "index.html", 13 | "manifest.webmanifest", 14 | "/**/*.css", 15 | "/**/*.js" 16 | ], 17 | "urls": [ 18 | "https://fonts.googleapis.com/**", 19 | "https://fonts.gstatic.com/s/**" 20 | ] 21 | } 22 | }, 23 | { 24 | "name": "assets", 25 | "installMode": "lazy", 26 | "updateMode": "prefetch", 27 | "resources": { 28 | "files": [ 29 | "/assets/**", 30 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 31 | ] 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /src/app/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:angular_ts_library.bzl", "ng_ts_library") 2 | 3 | package(default_visibility = ["//:__subpackages__"]) 4 | 5 | ng_ts_library( 6 | name = "app", 7 | srcs = glob( 8 | include = ["*.ts"], 9 | exclude = ["app.server.module.ts"], 10 | ), 11 | angular_assets = ["app.component.html"], 12 | tsconfig = "//src:tsconfig.json", 13 | deps = [ 14 | "//src/app/hello-world", 15 | "//src/app/home", 16 | "//src/app/todos", 17 | "//src/app/todos/reducers", 18 | "//src/shared/material", 19 | "@npm//@angular/common", 20 | "@npm//@angular/core", 21 | "@npm//@angular/platform-browser", 22 | "@npm//@angular/router", 23 | "@npm//@angular/service-worker", 24 | "@npm//@ngrx/store", 25 | "@npm//rxjs", 26 | ], 27 | ) 28 | 29 | ng_ts_library( 30 | name = "app_server", 31 | srcs = ["app.server.module.ts"], 32 | tsconfig = "//src:tsconfig-server", 33 | deps = [ 34 | ":app", 35 | "@npm//@angular/core", 36 | "@npm//@angular/platform-server", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /src/assets/angular-white-transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | 2 | import {NgModule} from '@angular/core'; 3 | import {BrowserModule} from '@angular/platform-browser'; 4 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 5 | import {StoreModule} from '@ngrx/store'; 6 | import { ServiceWorkerModule } from '@angular/service-worker'; 7 | 8 | import {MaterialModule} from '../shared/material/material.module'; 9 | 10 | import {AppRoutingModule} from './app-routing.module'; 11 | import {AppComponent} from './app.component'; 12 | import {HomeModule} from './home/home'; 13 | import {todoReducer} from './todos/reducers/reducers'; 14 | import { ServiceWorkerService } from './service-worker.service'; 15 | 16 | @NgModule({ 17 | declarations: [AppComponent], 18 | imports: [ 19 | AppRoutingModule, BrowserModule, BrowserAnimationsModule, MaterialModule, HomeModule, 20 | StoreModule.forRoot({todoReducer}), 21 | BrowserModule.withServerTransition({ appId: 'angular-bazel-example' }), 22 | ServiceWorkerModule.register('ngsw-worker.js') 23 | ], 24 | providers:[ServiceWorkerService], 25 | exports: [AppComponent], 26 | bootstrap: [AppComponent], 27 | }) 28 | export class AppModule { 29 | } 30 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {PreloadAllModules, RouterModule, Routes} from '@angular/router'; 3 | 4 | // These are lazy-loaded routes - note that we dynamic-import the modules here 5 | // to avoid having an eager dependency on them. 6 | 7 | // IMPORTANT: this array is auto-updated by script/generator 8 | // dont rename the 'routes' variable. 9 | const routes: Routes = [ 10 | { 11 | path: '', 12 | pathMatch: 'full', 13 | loadChildren: () => import('./home/home').then(m => m.HomeModule) 14 | }, 15 | { 16 | path: 'hello', 17 | pathMatch: 'full', 18 | loadChildren: () => 19 | import('./hello-world/hello-world.module').then(m => m.HelloWorldModule) 20 | }, 21 | { 22 | path: 'todos', 23 | pathMatch: 'full', 24 | loadChildren: () => import('./todos/todos.module').then(m => m.TodosModule) 25 | } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [RouterModule.forRoot(routes, { 30 | // TODO: maybe set this based on devmode? 31 | enableTracing: true, 32 | // preloadingStrategy: PreloadAllModules, 33 | })], 34 | exports: [RouterModule], 35 | }) 36 | export class AppRoutingModule { 37 | } 38 | -------------------------------------------------------------------------------- /src/app/todos/reducers/reducers.ts: -------------------------------------------------------------------------------- 1 | import {Action} from '@ngrx/store'; 2 | 3 | export const ADD_TODO = 'ADD_TODO'; 4 | export const DELETE_TODO = 'DELETE_TODO'; 5 | export const UPDATE_TODO = 'UPDATE_TODO'; 6 | export const TOGGLE_DONE = 'TOGGLE_DONE'; 7 | 8 | export interface ActionWithPayload extends Action { 9 | payload: T; 10 | } 11 | 12 | export interface TodoPayload { 13 | index?: number; 14 | done?: boolean; 15 | value?: string; 16 | newValue?: string; 17 | } 18 | 19 | export function todoReducer(state = [], action: ActionWithPayload) { 20 | switch (action.type) { 21 | case ADD_TODO: 22 | return [action.payload, ...state]; 23 | case DELETE_TODO: 24 | return state.filter((item, index) => index !== action.payload.index); 25 | case UPDATE_TODO: 26 | return state.map((item, index) => { 27 | return index === action.payload.index ? {...item, value: action.payload.newValue} : item; 28 | }); 29 | case TOGGLE_DONE: 30 | return state.map((item, index) => { 31 | return index === action.payload.index ? {...item, done: !action.payload.done} : item; 32 | }); 33 | default: 34 | return state; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/service-worker.service.ts: -------------------------------------------------------------------------------- 1 | import {ApplicationRef, Injectable, Inject, PLATFORM_ID} from '@angular/core'; 2 | import {SwUpdate} from '@angular/service-worker'; 3 | import {concat, interval} from 'rxjs'; 4 | import {first} from 'rxjs/operators'; 5 | import {isPlatformBrowser} from '@angular/common'; 6 | 7 | @Injectable() 8 | export class ServiceWorkerService { 9 | constructor( 10 | private appRef: ApplicationRef, 11 | private swUpdate: SwUpdate, 12 | @Inject(PLATFORM_ID) private platform: string 13 | ) {} 14 | 15 | launchUpdateCheckingRoutine(checkIntervaSeconds: number = 6 * 60 * 60) { 16 | if (!this.isAvailable()) return; 17 | 18 | const timeInterval$ = concat( 19 | this.appRef.isStable.pipe(first((isStable) => !!isStable)), 20 | interval(checkIntervaSeconds * 1000) 21 | ); 22 | 23 | timeInterval$.subscribe(() => this.swUpdate.checkForUpdate()); 24 | this.swUpdate.available.subscribe(() => this.forceUpdateNow()); 25 | } 26 | 27 | private forceUpdateNow() { 28 | this.swUpdate.activateUpdate().then(() => document.location.reload()); 29 | } 30 | 31 | private isAvailable() { 32 | return isPlatformBrowser(this.platform) && this.swUpdate.isEnabled; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tools/ngsw_config.bzl: -------------------------------------------------------------------------------- 1 | "Angular service worker support (credits: https://github.com/marcus-sa)" 2 | 3 | load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") 4 | 5 | def ngsw_config(name, config, index_html, src, out = None, **kwargs): 6 | "Creates ngsw.json with service worker configuration and hashes for all source files" 7 | if not out: 8 | out = name 9 | 10 | ngsw_config_name = "%s_bin" % name 11 | 12 | nodejs_binary( 13 | name = ngsw_config_name, 14 | data = ["@npm//@angular/service-worker", index_html, config, src], 15 | visibility = ["//visibility:private"], 16 | entry_point = "@npm//:node_modules/@angular/service-worker/ngsw-config.js", 17 | ) 18 | 19 | cmd = """ 20 | mkdir -p $@ 21 | cp -R $(locations {TMPL_src})/. $@/ 22 | cp $(location {TMPL_index}) $@/index.html 23 | $(location :{TMPL_bin}) $@ $(location {TMPL_conf}) 24 | """.format( 25 | TMPL_src = src, 26 | TMPL_bin = ngsw_config_name, 27 | TMPL_index = index_html, 28 | TMPL_conf = config, 29 | ) 30 | 31 | native.genrule( 32 | name = name, 33 | outs = [out], 34 | srcs = [src, config, index_html], 35 | tools = [":" + ngsw_config_name], 36 | cmd = cmd, 37 | **kwargs 38 | ) 39 | -------------------------------------------------------------------------------- /src/prerender-spec.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | 3 | const indexPathFlagIdx = process.argv.findIndex(arg => arg === '--index'); 4 | const indexPath = process.argv[indexPathFlagIdx + 1]; 5 | 6 | const routeFlagIdx = process.argv.findIndex(arg => arg === '--route'); 7 | const route = process.argv[routeFlagIdx + 1]; 8 | 9 | const expectedElementsFlagIdx = process.argv.findIndex(arg => arg === '--expected'); 10 | const elements = process.argv.slice(expectedElementsFlagIdx + 1); 11 | 12 | const index = readFileSync(indexPath.substring(indexPath.indexOf('/') + 1), { encoding: 'utf8' }); 13 | 14 | // check the index has the route stamped as a comment at the start 15 | if (!index.trim().startsWith(``)) { 16 | console.error(`Expected index ${indexPath} to start with '', but this was not found`); 17 | process.exit(1); 18 | } 19 | 20 | if (!elements) { 21 | // no elements to check for, and no errors above, all is good 22 | process.exit(0); 23 | } 24 | 25 | // check index contains the expected elements 26 | elements 27 | .forEach(element => { 28 | const position = index.indexOf(`<${element}`); 29 | if (position === -1) { 30 | console.error(`Expected index to contain element ${element}, but this was not found`); 31 | process.exit(1); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/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 {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 4 | 5 | import {MaterialModule} from '../../shared/material/material.module'; 6 | 7 | import {HelloWorldComponent} from './hello-world.component'; 8 | 9 | describe('BannerComponent (inline template)', () => { 10 | let comp: HelloWorldComponent; 11 | let fixture: ComponentFixture; 12 | let el: HTMLElement; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [HelloWorldComponent], // declare the test component 17 | imports: [BrowserAnimationsModule, MaterialModule], 18 | }); 19 | TestBed.compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(HelloWorldComponent); 24 | comp = fixture.componentInstance; 25 | el = fixture.debugElement.query(By.css('div')).nativeElement; 26 | }); 27 | 28 | it('should display original title', () => { 29 | fixture.detectChanges(); 30 | expect(el.textContent).toContain(comp.name); 31 | }); 32 | 33 | it('should display a different test title', () => { 34 | comp.name = 'Test'; 35 | fixture.detectChanges(); 36 | expect(el.textContent).toContain('Test'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/todos/todos.component.html: -------------------------------------------------------------------------------- 1 |

Todos

2 | 3 | 4 | 5 |
{{ editing ? "Edit" : "Add" }} your todo
6 |
7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 24 | 25 | 30 | 31 |
32 | 33 | 34 | 35 | {{ todo.done ? "check_box" : "check_box_outline_blank" }} 36 |
{{ todo.value }}
37 |
38 | edit 39 |
40 |
41 | delete 42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /src/shared/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {MatButtonModule} from '@angular/material/button'; 3 | import {MatCardModule} from '@angular/material/card'; 4 | import {MatFormFieldModule} from '@angular/material/form-field'; 5 | import {MatGridListModule} from '@angular/material/grid-list'; 6 | import {MatIconModule} from '@angular/material/icon'; 7 | import {MatInputModule} from '@angular/material/input'; 8 | import {MatListModule} from '@angular/material/list'; 9 | import {MatMenuModule} from '@angular/material/menu'; 10 | import {MatPaginatorModule} from '@angular/material/paginator'; 11 | import {MatRadioModule} from '@angular/material/radio'; 12 | import {MatSelectModule} from '@angular/material/select'; 13 | import {MatSidenavModule} from '@angular/material/sidenav'; 14 | import {MatTableModule} from '@angular/material/table'; 15 | import {MatToolbarModule} from '@angular/material/toolbar'; 16 | import {MatTooltipModule} from '@angular/material/tooltip'; 17 | 18 | const matModules = [ 19 | MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, 20 | MatToolbarModule, MatSidenavModule, MatRadioModule, MatSelectModule, MatGridListModule, 21 | MatMenuModule, MatTableModule, MatPaginatorModule, MatTooltipModule 22 | ]; 23 | 24 | @NgModule({ 25 | imports: matModules, 26 | exports: matModules, 27 | }) 28 | export class MaterialModule { 29 | } -------------------------------------------------------------------------------- /e2e/protractor.on-prepare.js: -------------------------------------------------------------------------------- 1 | // The function exported from this file is used by the protractor_web_test_suite. 2 | // It is passed to the `onPrepare` configuration setting in protractor and executed 3 | // before running tests. 4 | // 5 | // If the function returns a promise, as it does here, protractor will wait 6 | // for the promise to resolve before running tests. 7 | 8 | const protractorUtils = require('@bazel/protractor/protractor-utils'); 9 | const protractor = require('protractor'); 10 | const path = require('path'); 11 | 12 | module.exports = function(config) { 13 | // In this example, `@bazel/protractor/protractor-utils` is used to run 14 | // the server. protractorUtils.runServer() runs the server on a randomly 15 | // selected port (given a port flag to pass to the server as an argument). 16 | // The port used is returned in serverSpec and the protractor serverUrl 17 | // is the configured. 18 | const isProdserver = path.basename(config.server, path.extname(config.server)) === 'prodserver'; 19 | return protractorUtils 20 | .runServer(config.workspace, config.server, isProdserver ? '-p' : '-port', []) 21 | .then(serverSpec => { 22 | // Example app is hosted under `/example` in the prodserver and under `/` in devserver 23 | const serverUrl = `http://localhost:${serverSpec.port}` + (isProdserver ? '/example' : ''); 24 | protractor.browser.baseUrl = serverUrl; 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/example/index.prod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Bazel Example 5 | 6 | 7 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/assets/bazel-white-transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | $theme-primary: #3e236e; 2 | $theme-secondary: #43a047; 3 | 4 | $default-font-stack: Cambria, "Hoefler Text", Utopia, "Liberation Serif", "Nimbus Roman No9 L Regular", Times, "Times New Roman", serif; 5 | $modern-font-stack: Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif; 6 | 7 | html, body { 8 | font-family: $default-font-stack; 9 | padding: 0; 10 | margin: 0; 11 | 12 | .content { 13 | margin: 2rem; 14 | } 15 | } 16 | 17 | app-component { 18 | display: flex; 19 | flex-direction: column; 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | left: 0; 24 | bottom: 0; 25 | } 26 | 27 | mat-drawer-container { 28 | flex: 1; 29 | } 30 | 31 | nav > .mat-toolbar { 32 | background: $theme-secondary; 33 | color: #fff; 34 | 35 | .nav-logo { 36 | height: 28px; 37 | } 38 | 39 | .flex-spacer { 40 | flex: 1 1 auto; 41 | } 42 | 43 | a { 44 | text-decoration: none; 45 | } 46 | } 47 | 48 | .mobile-nav > a { 49 | flex: 1 1 auto; 50 | } 51 | 52 | .hide-small { 53 | @media screen and (max-width: 720px) { 54 | display: none !important; 55 | } 56 | } 57 | 58 | .show-small { 59 | @media screen and (min-width: 720px) { 60 | display: none !important; 61 | } 62 | } 63 | 64 | .mat-card { 65 | margin: 1em; 66 | } 67 | 68 | mat-drawer mat-nav-list .mat-icon { 69 | margin-right: 0.5em; 70 | } 71 | 72 | h1 { 73 | font-family: $modern-font-stack; 74 | color: $theme-primary; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/rxjs_shims.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | /** 10 | * @fileoverview these provide named UMD modules so that we can bundle 11 | * the application along with rxjs using the concatjs bundler. 12 | */ 13 | 14 | // rxjs/operators 15 | (function(factory) { 16 | if (typeof module === 'object' && typeof module.exports === 'object') { 17 | var v = factory(require, exports); 18 | if (v !== undefined) module.exports = v; 19 | } else if (typeof define === 'function' && define.amd) { 20 | define('rxjs/operators', ['exports', 'rxjs'], factory); 21 | } 22 | })(function(exports, rxjs) { 23 | 'use strict'; 24 | Object.keys(rxjs.operators).forEach(function(key) { 25 | exports[key] = rxjs.operators[key]; 26 | }); 27 | Object.defineProperty(exports, '__esModule', {value: true}); 28 | }); 29 | 30 | // rxjs/testing 31 | (function(factory) { 32 | if (typeof module === 'object' && typeof module.exports === 'object') { 33 | var v = factory(require, exports); 34 | if (v !== undefined) module.exports = v; 35 | } else if (typeof define === 'function' && define.amd) { 36 | define('rxjs/testing', ['exports', 'rxjs'], factory); 37 | } 38 | })(function(exports, rxjs) { 39 | 'use strict'; 40 | Object.keys(rxjs.testing).forEach(function(key) { 41 | exports[key] = rxjs.testing[key]; 42 | }); 43 | Object.defineProperty(exports, '__esModule', {value: true}); 44 | }); 45 | -------------------------------------------------------------------------------- /src/app/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.scss'] 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/app/app.component.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | Home 27 | Hello World 28 | Todos 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 |
56 |
57 |
-------------------------------------------------------------------------------- /src/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Angular Bazel Example", 3 | "short_name": "Angular Bazel", 4 | "theme_color": "#43a047", 5 | "background_color": "#fff", 6 | "display": "standalone", 7 | "scope": "./", 8 | "start_url": "./", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png", 14 | "purpose": "maskable any" 15 | }, 16 | { 17 | "src": "assets/icons/icon-96x96.png", 18 | "sizes": "96x96", 19 | "type": "image/png", 20 | "purpose": "maskable any" 21 | }, 22 | { 23 | "src": "assets/icons/icon-128x128.png", 24 | "sizes": "128x128", 25 | "type": "image/png", 26 | "purpose": "maskable any" 27 | }, 28 | { 29 | "src": "assets/icons/icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image/png", 32 | "purpose": "maskable any" 33 | }, 34 | { 35 | "src": "assets/icons/icon-152x152.png", 36 | "sizes": "152x152", 37 | "type": "image/png", 38 | "purpose": "maskable any" 39 | }, 40 | { 41 | "src": "assets/icons/icon-192x192.png", 42 | "sizes": "192x192", 43 | "type": "image/png", 44 | "purpose": "maskable any" 45 | }, 46 | { 47 | "src": "assets/icons/icon-384x384.png", 48 | "sizes": "384x384", 49 | "type": "image/png", 50 | "purpose": "maskable any" 51 | }, 52 | { 53 | "src": "assets/icons/icon-512x512.png", 54 | "sizes": "512x512", 55 | "type": "image/png", 56 | "purpose": "maskable any" 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /src/prerender.ts: -------------------------------------------------------------------------------- 1 | import 'zone.js/dist/zone-node'; 2 | import 'reflect-metadata'; 3 | 4 | import { readFileSync, writeFileSync } from 'fs'; 5 | import { renderModule } from '@angular/platform-server'; 6 | import * as domino from 'domino'; 7 | 8 | const rootIndexPathFlagIdx = process.argv.findIndex(arg => arg === '--index'); 9 | const rootIndexPath = process.argv[rootIndexPathFlagIdx + 1]; 10 | 11 | const routesFlagIdx = process.argv.findIndex(arg => arg === '--routes'); 12 | const routes = process.argv.slice(routesFlagIdx + 1, process.argv.length); 13 | 14 | const outsFlagIdx = process.argv.findIndex(arg => arg === '--outs'); 15 | const outs = process.argv.slice(outsFlagIdx + 1, routesFlagIdx); 16 | 17 | const document = readFileSync(rootIndexPath, { encoding: 'utf8' }); 18 | 19 | const win: any = domino.createWindow(document); 20 | declare const global: any; 21 | 22 | global.window = win; 23 | global.Node = win.Node; 24 | global.navigator = win.navigator; 25 | global.Event = win.Event; 26 | global.KeyboardEvent = win.Event; 27 | global.MouseEvent = win.Event; 28 | global.Event.prototype = win.Event.prototype; 29 | global.document = win.document; 30 | global.HTMLInputElement = win.HTMLInputElement; 31 | global.HTMLDocument = win.HTMLDocument; 32 | 33 | import { AppServerModule } from './app/app.server.module'; 34 | 35 | const renderProcesses: Array> = routes.map((url, i) => { 36 | return renderModule(AppServerModule, { url, document }) 37 | .then((html: string) => writeFileSync(outs[i], `\n${html}`, { encoding: 'utf8' })) 38 | .catch((err: Error) => { 39 | console.error(err); 40 | process.exit(1); 41 | }); 42 | }); 43 | 44 | Promise.all(renderProcesses) 45 | .then(() => process.exit()); 46 | -------------------------------------------------------------------------------- /patches/com_github_bazelbuild_bazel_toolchains.diff: -------------------------------------------------------------------------------- 1 | diff --git rules/rbe_repo.bzl rules/rbe_repo.bzl 2 | index de8501f..b78c241 100644 3 | --- rules/rbe_repo.bzl 4 | +++ rules/rbe_repo.bzl 5 | @@ -811,6 +811,9 @@ _rbe_autoconfig = repository_rule( 6 | "JAVA_HOME env var from the container. If that is not set, the rule " + 7 | "will fail."), 8 | ), 9 | + "os_family": attr.string( 10 | + doc = ("Optional. The OS for which generate configuration."), 11 | + ), 12 | "registry": attr.string( 13 | doc = ("Optional. The registry to pull the container from. For example, " + 14 | "marketplace.gcr.io. The default is the value for the selected " + 15 | @@ -901,6 +904,7 @@ def rbe_autoconfig( 16 | java_home = None, 17 | tag = None, 18 | toolchain_config_suite_spec = default_toolchain_config_suite_spec(), 19 | + os_family = None, 20 | registry = None, 21 | repository = None, 22 | target_compatible_with = None, 23 | @@ -1198,6 +1202,7 @@ def rbe_autoconfig( 24 | toolchain_config_suite_spec = toolchain_config_suite_spec_stripped, 25 | registry = registry, 26 | repository = repository, 27 | + os_family = os_family, 28 | tag = tag, 29 | target_compatible_with = target_compatible_with, 30 | use_checked_in_confs = use_checked_in_confs, 31 | diff --git rules/rbe_repo/util.bzl rules/rbe_repo/util.bzl 32 | index 71a0e9e..42a23ea 100644 33 | --- rules/rbe_repo/util.bzl 34 | +++ rules/rbe_repo/util.bzl 35 | @@ -144,6 +144,8 @@ def os_family(ctx): 36 | Returns: 37 | Returns the name of the OS Family 38 | """ 39 | + if ctx.attr.os_family: 40 | + return ctx.attr.os_family 41 | os_name = ctx.os.name.lower() 42 | if os_name.find("windows") != -1: 43 | return "Windows" 44 | -------------------------------------------------------------------------------- /src/app/hello-world/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_sass//:defs.bzl", "multi_sass_binary") 2 | load("@npm//@bazel/karma:index.bzl", "karma_web_test_suite") 3 | load("@npm//@bazel/typescript:index.bzl", "ts_library") 4 | load("//tools:angular_ts_library.bzl", "ng_ts_library") 5 | 6 | package(default_visibility = ["//:__subpackages__"]) 7 | 8 | # multi_sass_binary can be used to build multiple scss files within one rule 9 | multi_sass_binary( 10 | name = "hello-world-styles", 11 | srcs = [ 12 | "hello-world.component.scss", 13 | "secondary-styles.scss", 14 | ], 15 | ) 16 | 17 | ng_ts_library( 18 | name = "hello-world", 19 | srcs = [ 20 | "hello-world.component.ts", 21 | "hello-world.module.ts", 22 | ], 23 | angular_assets = [ 24 | ":hello-world.component.html", 25 | ":hello-world-styles", 26 | ], 27 | tsconfig = "//src:tsconfig.json", 28 | deps = [ 29 | "//src/lib/shorten", 30 | "//src/shared/material", 31 | "@npm//@angular/core", 32 | "@npm//@angular/forms", 33 | "@npm//@angular/router", 34 | "@npm//date-fns", 35 | ], 36 | ) 37 | 38 | ts_library( 39 | name = "test_lib", 40 | testonly = 1, 41 | srcs = glob(["*.spec.ts"]), 42 | tsconfig = "//src:tsconfig-test", 43 | deps = [ 44 | ":hello-world", 45 | "//src/shared/material", 46 | "@npm//@angular/core", 47 | "@npm//@angular/platform-browser", 48 | "@npm//@types/jasmine", 49 | ], 50 | ) 51 | 52 | karma_web_test_suite( 53 | name = "test", 54 | srcs = [ 55 | # We are manaully adding the bazel generated named-UMD date-fns bundle here as 56 | # named-UMD bundles for non-APF npm packages are not yet automatically added. 57 | # This file is generated by the npm_umd_bundle @npm//date-fns:date-fns__umd 58 | # rule that is setup by yarn_install. 59 | "@npm//date-fns:date-fns.umd.js", 60 | ], 61 | # do not sort 62 | bootstrap = [ 63 | "@npm//:node_modules/zone.js/dist/zone-testing-bundle.js", 64 | "@npm//:node_modules/reflect-metadata/Reflect.js", 65 | ], 66 | browsers = [ 67 | "@io_bazel_rules_webtesting//browsers:chromium-local", 68 | "@io_bazel_rules_webtesting//browsers:firefox-local", 69 | ], 70 | tags = ["native"], 71 | runtime_deps = [ 72 | "//src:initialize_testbed", 73 | ], 74 | deps = [ 75 | ":test_lib", 76 | "//src:rxjs_umd_modules", 77 | # explicit tslib dependency comes from @angular/material 78 | "@npm//:node_modules/tslib/tslib.js", 79 | ], 80 | ) 81 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-bazel-example": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@bazel/angular:build", 19 | "options": { 20 | "targetLabel": "//src:prodapp", 21 | "bazelCommand": "build" 22 | } 23 | }, 24 | "serve": { 25 | "builder": "@bazel/angular:build", 26 | "options": { 27 | "targetLabel": "//src:devserver", 28 | "bazelCommand": "run", 29 | "watch": true 30 | }, 31 | "configurations": { 32 | "production": { 33 | "targetLabel": "//src:prodserver" 34 | } 35 | } 36 | }, 37 | "extract-i18n": { 38 | "builder": "@angular-devkit/build-angular:extract-i18n", 39 | "options": { 40 | "browserTarget": "ngbazel:build" 41 | } 42 | }, 43 | "test": { 44 | "builder": "@bazel/angular:build", 45 | "options": { 46 | "bazelCommand": "test", 47 | "targetLabel": "//src/app/..." 48 | } 49 | }, 50 | "lint": { 51 | "builder": "@angular-devkit/build-angular:tslint", 52 | "options": { 53 | "tsConfig": [ 54 | "src/tsconfig.app.json", 55 | "src/tsconfig.spec.json" 56 | ], 57 | "exclude": [ 58 | "**/node_modules/**" 59 | ] 60 | } 61 | } 62 | } 63 | }, 64 | "ngbazel-e2e": { 65 | "root": "e2e/", 66 | "projectType": "application", 67 | "prefix": "", 68 | "architect": { 69 | "e2e": { 70 | "builder": "@bazel/angular:build", 71 | "options": { 72 | "bazelCommand": "test", 73 | "targetLabel": "//e2e:devserver_test" 74 | }, 75 | "configurations": { 76 | "production": { 77 | "targetLabel": "//e2e:prodserver_test" 78 | } 79 | } 80 | }, 81 | "lint": { 82 | "builder": "@angular-devkit/build-angular:tslint", 83 | "options": { 84 | "tsConfig": "e2e/tsconfig.e2e.json", 85 | "exclude": [ 86 | "**/node_modules/**" 87 | ] 88 | } 89 | } 90 | } 91 | } 92 | }, 93 | "defaultProject": "ngbazel" 94 | 95 | } 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bazel-example", 3 | "private": true, 4 | "description": "Demo of building Angular apps with Bazel", 5 | "license": "Apache-2.0", 6 | "engines": { 7 | "node": ">=10.9.0 <=13.8.0", 8 | "yarn": ">=1.9.2 <2.0.0" 9 | }, 10 | "dependencies": { 11 | "@angular/animations": "10.0.2", 12 | "@angular/cdk": "10.0.1", 13 | "@angular/common": "10.0.2", 14 | "@angular/core": "10.0.2", 15 | "@angular/forms": "10.0.2", 16 | "@angular/material": "10.0.1", 17 | "@angular/platform-browser": "10.0.2", 18 | "@angular/platform-browser-dynamic": "10.0.2", 19 | "@angular/platform-server": "10.0.2", 20 | "@angular/router": "10.0.2", 21 | "@angular/service-worker": "10.0.2", 22 | "@ngrx/store": "9.2.0", 23 | "@nguniversal/express-engine": "^9.0.0", 24 | "date-fns": "1.30.1", 25 | "rxjs": "6.5.3", 26 | "systemjs": "6.1.2", 27 | "tslib": "2.0.0", 28 | "zone.js": "0.10.3" 29 | }, 30 | "devDependencies": { 31 | "@angular/cli": "10.0.0", 32 | "@angular/compiler": "10.0.2", 33 | "@angular/compiler-cli": "10.0.2", 34 | "@babel/cli": "^7.6.0", 35 | "@babel/core": "^7.6.0", 36 | "@babel/preset-env": "^7.6.0", 37 | "@bazel/angular": "^2.3.1", 38 | "@bazel/bazelisk": "^1.7.2", 39 | "@bazel/benchmark-runner": "^0.1.0", 40 | "@bazel/buildifier": "^3.5.0", 41 | "@bazel/ibazel": "^0.14.0", 42 | "@bazel/karma": "^2.3.1", 43 | "@bazel/protractor": "^2.3.1", 44 | "@bazel/rollup": "^2.3.1", 45 | "@bazel/terser": "^2.3.1", 46 | "@bazel/typescript": "^2.3.1", 47 | "@rollup/plugin-commonjs": "^14.0.0", 48 | "@rollup/plugin-node-resolve": "^8.4.0", 49 | "@types/jasmine": "3.4.0", 50 | "@types/node": "6.14.6", 51 | "core-js": "2.6.9", 52 | "firebase-tools": "7.1.0", 53 | "history-server": "^1.3.1", 54 | "html-insert-assets": "^0.6.0", 55 | "karma": "~4.1.0", 56 | "karma-chrome-launcher": "2.2.0", 57 | "karma-firefox-launcher": "1.1.0", 58 | "karma-jasmine": "2.0.1", 59 | "karma-requirejs": "1.1.0", 60 | "karma-sourcemap-loader": "0.3.7", 61 | "protractor": "^5.4.2", 62 | "requirejs": "2.3.6", 63 | "rollup": "^2.3.4", 64 | "terser": "4.3.1", 65 | "typescript": "^3.9.0" 66 | }, 67 | "scripts": { 68 | "build": "bazel build //src:prodapp", 69 | "serve": "ibazel run //src:devserver", 70 | "deploy": "ng build && firebase deploy", 71 | "serve-prod": "bazel run //src:prodserver", 72 | "serve-ssr": "bazel run //src:universal_server", 73 | "e2e": "bazel test //e2e:all", 74 | "test": "bazel test //src/...", 75 | "benchmark": "ibazel-benchmark-runner //src:devserver src/app/hello-world/hello-world.component.ts --url=http://localhost:5432", 76 | "postinstall": "ngcc", 77 | "generate": "node tools/generator/index.js", 78 | "generate:clean": "node tools/generator/index.js --clean" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/assets/bazel-navbar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 31 | 32 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tools/angular_prerender.bzl: -------------------------------------------------------------------------------- 1 | """Helper macros for rendering parts of an Angular application at build time""" 2 | 3 | load("@build_bazel_rules_nodejs//:index.bzl", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test") 4 | load("@npm//@bazel/typescript:index.bzl", _ts_library = "ts_library") 5 | 6 | def _get_output_path(route, root_at): 7 | return root_at + "/" + route + "/index.html" 8 | 9 | def ng_prerender(name, index, prerender_roots = [], **kwargs): 10 | """ 11 | Helper macro for prerendering Angular routes to index files as part of the build 12 | 13 | The outputs of this macro are: 14 | %name% - all the rendered roots, plus the root route / 15 | %name%.root - an alias referencing just the root index file 16 | %name%.%route% - an alias referencing each rendered route, with / replaced by underscores 17 | 18 | Args: 19 | name: Rule name for the main output genrule 20 | index: Label for the production index.html file with which to render into 21 | prerender_roots: A list of roots that will be prerendered as part of this macro, the root route / is always rendered 22 | """ 23 | 24 | renderer_lib = "%s_renderer_lib" % name 25 | _ts_library( 26 | name = renderer_lib, 27 | srcs = ["//src:prerender.ts"], 28 | deps = [ 29 | "//src/app:app_server", 30 | "@npm//@angular/platform-server", 31 | "@npm//zone.js", 32 | "@npm//domino", 33 | "@npm//reflect-metadata", 34 | "@npm//@types/node", 35 | ], 36 | ) 37 | 38 | bin = "%s_bin" % renderer_lib 39 | _nodejs_binary( 40 | name = bin, 41 | data = [ 42 | ":%s" % renderer_lib, 43 | "@npm//@angular/platform-server", 44 | "@npm//zone.js", 45 | "@npm//domino", 46 | "@npm//reflect-metadata", 47 | ], 48 | entry_point = "//src:prerender.ts", 49 | templated_args = ["--nobazel_run_linker"], 50 | ) 51 | 52 | root_at = "_prerender/" + native.package_name() 53 | 54 | # we can't output "foo/index.html" since that collides with source files and will likely cross a package boundary 55 | # so we output "_prerender/pkg_name/route/index.html" 56 | prerender_root_outs = [_get_output_path(route, root_at) for route in prerender_roots] 57 | root_index = "%s/index.html" % root_at 58 | 59 | visibility = kwargs.pop("visibility", []) 60 | 61 | native.genrule( 62 | name = name, 63 | srcs = [index], 64 | outs = [root_index] + prerender_root_outs, 65 | cmd = "$(location :%s) --index $(location %s) --outs $(OUTS) --routes / %s" % (bin, index, " ".join(prerender_roots)), 66 | tools = [":%s" % bin], 67 | message = "Prerendering Angular", 68 | visibility = visibility, 69 | tags = kwargs.pop("tags", []), 70 | ) 71 | 72 | # convenience "output groups" from macro 73 | native.alias( 74 | name = "%s.root" % name, 75 | actual = root_index, 76 | visibility = visibility, 77 | ) 78 | 79 | [ 80 | native.alias( 81 | name = "%s.%s" % (name, route.replace("/", "_")), 82 | actual = _get_output_path(route, root_at), 83 | visibility = visibility, 84 | ) 85 | for route in prerender_roots 86 | ] 87 | 88 | def ng_prerender_test(name, index, route, expected_elements = [], **kwargs): 89 | """ 90 | Simple smoke test for a prerendered index file, as generated by ng_prerender 91 | 92 | Args: 93 | name: Rule name for the test 94 | index: Label of the index file under test 95 | route: The route that this index file belongs to 96 | expected_elements: An optional array of expected elements that should appear in the index file 97 | """ 98 | 99 | _ts_library( 100 | name = "%s_render_spec" % name, 101 | srcs = ["//src:prerender-spec.ts"], 102 | deps = [ 103 | "@npm//@types/node", 104 | ], 105 | testonly = 1, 106 | ) 107 | 108 | _nodejs_test( 109 | name = name, 110 | data = [ 111 | ":%s_render_spec" % name, 112 | index, 113 | ], 114 | templated_args = ["--index $(location %s)" % index, "--route %s" % route, "--expected %s" % (" ".join(expected_elements))], 115 | entry_point = "//src:prerender-spec.ts", 116 | tags = kwargs.pop("tags", []), 117 | ) 118 | -------------------------------------------------------------------------------- /src/assets/angular-logo-with-text.svg: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Common Bazel settings for JavaScript/NodeJS workspaces 2 | # This rc file is automatically discovered when Bazel is run in this workspace, 3 | # see https://docs.bazel.build/versions/master/guide.html#bazelrc 4 | # 5 | # The full list of Bazel options: https://docs.bazel.build/versions/master/command-line-reference.html 6 | 7 | # Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) 8 | build --disk_cache=~/.cache/bazel-disk-cache 9 | 10 | # Bazel will create symlinks from the workspace directory to output artifacts. 11 | # Build results will be placed in a directory called "dist/bin" 12 | # Other directories will be created like "dist/testlogs" 13 | # Be aware that this will still create a bazel-out symlink in 14 | # your project directory, which you must exclude from version control and your 15 | # editor's search path. 16 | build --symlink_prefix=dist/ 17 | # To disable the symlinks altogether (including bazel-out) you can use 18 | # build --symlink_prefix=/ 19 | # however this makes it harder to find outputs. 20 | 21 | # Specifies desired output mode for running tests. 22 | # Valid values are 23 | # 'summary' to output only test status summary 24 | # 'errors' to also print test logs for failed tests 25 | # 'all' to print logs for all tests 26 | # 'streamed' to output logs for all tests in real time 27 | # (this will force tests to be executed locally one at a time regardless of --test_strategy value). 28 | test --test_output=errors 29 | 30 | # Support for debugging NodeJS tests 31 | # Add the Bazel option `--config=debug` to enable this 32 | # --test_output=streamed 33 | # Stream stdout/stderr output from each test in real-time. 34 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--test_output for more details. 35 | # --test_strategy=exclusive 36 | # Run one test at a time. 37 | # --test_timeout=9999 38 | # Prevent long running tests from timing out 39 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--test_timeout for more details. 40 | # --nocache_test_results 41 | # Always run tests 42 | # --node_options=--inspect-brk 43 | # Pass the --inspect-brk option to all tests which enables the node inspector agent. 44 | # See https://nodejs.org/de/docs/guides/debugging-getting-started/#command-line-options for more details. 45 | # --define=VERBOSE_LOGS=1 46 | # Rules will output verbose logs if the VERBOSE_LOGS environment variable is set. `VERBOSE_LOGS` will be passed to 47 | # `nodejs_binary` and `nodejs_test` via the default value of the `default_env_vars` attribute of those rules. 48 | # --compilation_mode=dbg 49 | # Rules may change their build outputs if the compilation mode is set to dbg. For example, 50 | # mininfiers such as terser may make their output more human readable when this is set. Rules will pass `COMPILATION_MODE` 51 | # to `nodejs_binary` executables via the actions.run env attribute. 52 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode for more details. 53 | test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --define=VERBOSE_LOGS=1 54 | # Use bazel run with `--config=debug` to turn on the NodeJS inspector agent. 55 | # The node process will break before user code starts and wait for the debugger to connect. 56 | run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk 57 | # The following option will change the build output of certain rules such as terser and may not be desirable in all cases 58 | build:debug --compilation_mode=dbg 59 | 60 | # Turn off legacy external runfiles 61 | # This prevents accidentally depending on this feature, which Bazel will remove. 62 | build --nolegacy_external_runfiles 63 | 64 | # Turn on --incompatible_strict_action_env which was on by default 65 | # in Bazel 0.21.0 but turned off again in 0.22.0. Follow 66 | # https://github.com/bazelbuild/bazel/issues/7026 for more details. 67 | # This flag is needed to so that the bazel cache is not invalidated 68 | # when running bazel via `yarn bazel`. 69 | # See https://github.com/angular/angular/issues/27514. 70 | build --incompatible_strict_action_env 71 | run --incompatible_strict_action_env 72 | 73 | # When running `bazel coverage` --instrument_test_targets needs to be set in order to 74 | # collect coverage information from test targets 75 | coverage --instrument_test_targets 76 | 77 | # Use the Angular Ivy compiler 78 | # See https://github.com/angular/angular/blob/master/docs/BAZEL.md#various-flags-used-for-tests 79 | build --define=angular_ivy_enabled=True 80 | 81 | ################################## 82 | # Remote Build Execution support # 83 | # Turn on these settings with # 84 | # --config=remote # 85 | ################################## 86 | 87 | # The following --define=EXECUTOR=remote will be able to be removed 88 | # once https://github.com/bazelbuild/bazel/issues/7254 is fixed 89 | build:remote_base --define=EXECUTOR=remote 90 | 91 | # Set a higher timeout value, just in case. 92 | build:remote_base --remote_timeout=600 93 | 94 | # Increase the default number of jobs by 50% because our build has lots of 95 | # parallelism 96 | build:remote_base --jobs=150 97 | 98 | # we could optionally use this flag to grab GCP creds if users invoking this were added to the remote 99 | # exec cluster. For example purposes, we'll just share a remote header which allows access. 100 | # build:remote --google_default_credentials 101 | 102 | # Force remote exeuctions to consider the entire run as linux 103 | build:remote_base --cpu=k8 104 | build:remote_base --host_cpu=k8 105 | 106 | # current flare config 107 | build:remote_base --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 108 | build:remote_base --host_javabase=@ubuntu2004-rbe//java:jdk 109 | build:remote_base --javabase=@ubuntu2004-rbe//java:jdk 110 | build:remote_base --crosstool_top=@ubuntu2004-rbe//cc:toolchain 111 | build:remote_base --extra_toolchains=@ubuntu2004-rbe//config:cc-toolchain 112 | build:remote_base --extra_execution_platforms=@ubuntu2004-rbe//config:platform 113 | build:remote_base --host_platform=@ubuntu2004-rbe//config:platform 114 | build:remote_base --platforms=@ubuntu2004-rbe//config:platform 115 | build:remote_base --disk_cache= 116 | build:remote_base --spawn_strategy=remote,local 117 | build:remote_base --experimental_inmemory_jdeps_files 118 | build:remote_base --experimental_inmemory_dotd_files 119 | 120 | # Remote instance and caching 121 | build:remote --config=remote_base 122 | build:remote --remote_executor=grpcs://cache.stg.flare.build 123 | #build:remote --remote_cache=grpcs://cache.stg.flare.build 124 | 125 | build:remote_local --config=remote_base 126 | #build:remote_local --remote_cache=grpc://localhost:8002 127 | build:remote_local --remote_executor=grpc://localhost:8002 128 | build:remote_local --remote_header=Authorization=local_flare 129 | 130 | # Load any settings specific to the current user. 131 | # .bazelrc.user should appear in .gitignore so that settings are not shared with team members 132 | # This needs to be last statement in this 133 | # config, as the user configuration should be able to overwrite flags from this file. 134 | # See https://docs.bazel.build/versions/master/best-practices.html#bazelrc 135 | # (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing, 136 | # rather than user.bazelrc as suggested in the Bazel docs) 137 | try-import %workspace%/.bazelrc.user 138 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | # The WORKSPACE file tells Bazel that this directory is a "workspace", which is like a project root. 2 | # The content of this file specifies all the external dependencies Bazel needs to perform a build. 3 | 4 | #################################### 5 | # ESModule imports (and TypeScript imports) can be absolute starting with the workspace name. 6 | # The name of the workspace should match the npm package where we publish, so that these 7 | # imports also make sense when referencing the published package. 8 | workspace( 9 | name = "examples_angular", 10 | managed_directories = {"@npm": ["node_modules"]}, 11 | ) 12 | 13 | # These rules are built-into Bazel but we need to load them first to download more rules 14 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 15 | 16 | http_archive( 17 | name = "bazel_skylib", 18 | sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c", 19 | urls = [ 20 | "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz", 21 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz", 22 | ], 23 | ) 24 | 25 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 26 | 27 | bazel_skylib_workspace() 28 | 29 | # Fetch rules_nodejs so we can install our npm dependencies 30 | http_archive( 31 | name = "build_bazel_rules_nodejs", 32 | sha256 = "121f17d8b421ce72f3376431c3461cd66bfe14de49059edc7bb008d5aebd16be", 33 | urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.3.1/rules_nodejs-2.3.1.tar.gz"], 34 | ) 35 | 36 | # Fetch sass rules for compiling sass files 37 | http_archive( 38 | name = "io_bazel_rules_sass", 39 | sha256 = "c78be58f5e0a29a04686b628cf54faaee0094322ae0ac99da5a8a8afca59a647", 40 | strip_prefix = "rules_sass-1.25.0", 41 | urls = [ 42 | "https://github.com/bazelbuild/rules_sass/archive/1.25.0.zip", 43 | "https://mirror.bazel.build/github.com/bazelbuild/rules_sass/archive/1.25.0.zip", 44 | ], 45 | ) 46 | 47 | # Check the bazel version and download npm dependencies 48 | load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install") 49 | 50 | # Setup the Node.js toolchain & install our npm dependencies into @npm 51 | yarn_install( 52 | name = "npm", 53 | package_json = "//:package.json", 54 | yarn_lock = "//:yarn.lock", 55 | ) 56 | 57 | # Load @bazel/protractor dependencies 58 | load("@npm//@bazel/protractor:package.bzl", "npm_bazel_protractor_dependencies") 59 | 60 | npm_bazel_protractor_dependencies() 61 | 62 | # Load @bazel/karma dependencies 63 | load("@npm//@bazel/karma:package.bzl", "npm_bazel_karma_dependencies") 64 | 65 | npm_bazel_karma_dependencies() 66 | 67 | # Setup the rules_webtesting toolchain 68 | load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories") 69 | 70 | web_test_repositories() 71 | 72 | load("@io_bazel_rules_webtesting//web/versioned:browsers-0.3.2.bzl", "browser_repositories") 73 | 74 | browser_repositories( 75 | chromium = True, 76 | firefox = True, 77 | ) 78 | 79 | # Setup the rules_sass toolchain 80 | load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories") 81 | 82 | sass_repositories() 83 | 84 | # other deps 85 | http_archive( 86 | name = "io_bazel_rules_go", 87 | sha256 = "207fad3e6689135c5d8713e5a17ba9d1290238f47b9ba545b63d9303406209c6", 88 | urls = [ 89 | "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.24.7/rules_go-v0.24.7.tar.gz", 90 | "https://github.com/bazelbuild/rules_go/releases/download/v0.24.7/rules_go-v0.24.7.tar.gz", 91 | ], 92 | ) 93 | load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") 94 | 95 | go_rules_dependencies() 96 | 97 | go_register_toolchains() 98 | 99 | http_archive( 100 | name = "io_bazel_rules_docker", 101 | sha256 = "4521794f0fba2e20f3bf15846ab5e01d5332e587e9ce81629c7f96c793bb7036", 102 | strip_prefix = "rules_docker-0.14.4", 103 | urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.14.4/rules_docker-v0.14.4.tar.gz"], 104 | ) 105 | 106 | load("@io_bazel_rules_docker//repositories:repositories.bzl", container_repositories = "repositories") 107 | 108 | container_repositories() 109 | 110 | load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") 111 | 112 | container_deps() 113 | 114 | load("@io_bazel_rules_docker//repositories:pip_repositories.bzl", "pip_deps") 115 | 116 | pip_deps() 117 | 118 | # https://github.com/bazelbuild/rules_docker#go_image 119 | load("@io_bazel_rules_docker//go:image.bzl", _go_image_repos = "repositories") 120 | 121 | _go_image_repos() 122 | 123 | load("@io_bazel_rules_docker//nodejs:image.bzl", nodejs_image_repos = "repositories") 124 | 125 | nodejs_image_repos() 126 | 127 | #################################################### 128 | # Kubernetes setup, for deployment to Google Cloud # 129 | #################################################### 130 | 131 | http_archive( 132 | name = "io_bazel_rules_k8s", 133 | sha256 = "cc75cf0d86312e1327d226e980efd3599704e01099b58b3c2fc4efe5e321fcd9", 134 | strip_prefix = "rules_k8s-0.3.1", 135 | urls = ["https://github.com/bazelbuild/rules_k8s/releases/download/v0.3.1/rules_k8s-v0.3.1.tar.gz"], 136 | ) 137 | 138 | load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_defaults", "k8s_repositories") 139 | 140 | k8s_repositories() 141 | 142 | load("@io_bazel_rules_k8s//k8s:k8s_go_deps.bzl", k8s_go_deps = "deps") 143 | 144 | k8s_go_deps() 145 | 146 | k8s_defaults( 147 | # This creates a rule called "k8s_deploy" that we can call later 148 | name = "k8s_deploy", 149 | # This is the name of the cluster as it appears in: 150 | # kubectl config view --minify -o=jsonpath='{.contexts[0].context.cluster}' 151 | cluster = "_".join([ 152 | "gke", 153 | "internal-200822", 154 | "us-west1-a", 155 | "angular-bazel-example", 156 | ]), 157 | kind = "deployment", 158 | ) 159 | 160 | ## begin Flare FBE 161 | 162 | http_archive( 163 | name = "bazel_toolchains", 164 | patches = [ 165 | "//:patches/com_github_bazelbuild_bazel_toolchains.diff" 166 | ], 167 | sha256 = "8c9728dc1bb3e8356b344088dfd10038984be74e1c8d6e92dbb05f21cabbb8e4", 168 | strip_prefix = "bazel-toolchains-3.7.1", 169 | urls = [ 170 | "https://github.com/bazelbuild/bazel-toolchains/releases/download/3.7.1/bazel-toolchains-3.7.1.tar.gz", 171 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/3.7.1/bazel-toolchains-3.7.1.tar.gz", 172 | ], 173 | ) 174 | 175 | load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig") 176 | 177 | flare_toolchains_commit = "b0b4e998c1d1cc3c8d8c3e6534bb96a0d9083714" 178 | 179 | flare_toolchains_sha256 = "8d84612d4d927c4f192798db2f9d4ee68aeacbfdcc4620c11ad6093d8ef247be" 180 | 181 | http_archive( 182 | name = "flare_toolchains", 183 | sha256 = flare_toolchains_sha256, 184 | strip_prefix = "flare-toolchains-" + flare_toolchains_commit, 185 | urls = [ 186 | "https://github.com/flarebuild/flare-toolchains/archive/" + flare_toolchains_commit + ".tar.gz", 187 | ], 188 | ) 189 | 190 | load("@flare_toolchains//configs/ubuntu2004_clang:repo.bzl", "ubuntu2004_clang_toolchain_config_suite_spec") 191 | 192 | rbe_autoconfig( 193 | name = "ubuntu2004-rbe", 194 | toolchain_config_suite_spec = ubuntu2004_clang_toolchain_config_suite_spec, 195 | os_family = "Linux", 196 | ) 197 | 198 | ## end Flare FBE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Angular monorepo using Bazel 2 | 3 | This example is a copy of the canonical Bazel [demo](https://github.com/bazelbuild/rules_nodejs/tree/8e7653629bb843d8b48f94a1e4284936d191c881/examples/angular). 4 | 5 | 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. 6 | 7 | This example demonstrates the Google-internal toolchain, which is fast but not very compatible with existing applications. 8 | 9 | ## Guide to the example 10 | 11 | This example is a monorepo, meant to show many different features and integrations that we expect are generally useful for enterprise use cases. 12 | 13 | - **Angular CLI**: you can use the `ng` command to run build, serve, test, and e2e 14 | - **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_library` rule in the `BUILD.bazel` file. 15 | - **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. 16 | - **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). 17 | - **Material design**: see `src/material` where we collect the material modules we use. 18 | - **Redux-style state management**: see `src/reducers` where we use the [NgRx Store](https://ngrx.io/guide/store). 19 | - **Lazy loading**: in production mode, the application is served in chunks. Run `ng serve --prod` 20 | - **Differential loading**: in production mode, we load a pair of ` 8 | 17 | 18 | 19 | ABC: Angular Buildtools Convergence 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 127 | 128 |
130 |
131 |
132 |

Angular and Bazel

133 |

134 | Build and test Angular applications at scale like Google 135 |

136 |
137 | 150 |
151 |
152 |
153 |
154 |
155 | 166 | 167 | 168 | 169 | 174 | 179 | 184 | 185 | 189 | 190 | 191 | 192 | 197 | 198 | 211 | 224 | 237 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 |
268 |
269 |

Incremental re-builds

270 |

271 | A small change to your app should require a small re-build. 272 |

273 |
274 |
275 |
279 |
280 | 281 | 282 | 293 | 294 | cli 295 | 296 | 297 | 301 | 305 | 309 | 313 | 320 | 321 | 322 | 323 |
324 |
325 |

Works with Angular CLI

326 |

327 | Use the same ng commands you're used to. 328 |

329 |
330 |
331 |
332 |
333 | 344 | 352 | 357 | 365 | 366 | 371 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 |
394 |
395 |

Cloud Ready

396 |

397 | Expand your build and test to run in parallel on a cluster of 398 | workers. 399 |

400 |
401 |
402 |
406 |
407 | 408 | 409 | 423 | 424 | labs 425 | 426 | 427 | 431 | 435 | 439 | 446 | 453 | 460 | 464 | 465 | 466 | 467 |
468 |
469 |

Part of Angular Labs

470 |

471 | Not a stable, supported API, so use Bazel with caution in 472 | production applications. We expect to promote it to stable for 473 | Angular version 9. 474 |

475 |
476 |
477 |
482 |

📚 Resources

483 |

Projects

484 | 503 | 504 |

Talks

505 |
    506 |
  • 507 | Building Large Angular applications with Bazel 508 | Slides 509 | Video 510 | (AngularToronto, Feb 2020) 511 |
  • 512 |
  • 513 | The Bazel Opt-in Preview is Here! 514 | Slides 515 | Video 516 | (NgConf, May 2019) 517 |
  • 518 |
  • 519 | Building Apps Like Google with Angular, Bazel, and GCP 520 | Video 521 | (Google Cloud Next, April 2019) 522 |
  • 523 | 524 |
  • 525 | Layering in JS tooling 526 | Slides 527 | Video 531 | (ModernWeb Meetup, March 2019) 532 |
  • 533 | 534 |
  • 535 | Angular, Bazel, and CLI 536 | Slides 540 | (AngularSF Meetup, January 2019) 541 |
  • 542 | 543 |
  • 544 | Bazel in Angular CLI 545 | Video 546 | Slides 550 | (AngularNYC Meetup, January 2019) 551 |
  • 552 | 553 |
  • 554 | The CLI Roadmap 555 | Video 556 | (AngularConnect, November 2018) 557 |
  • 558 | 559 |
  • 560 | Develop Angular like Google Does 561 | Slides 565 | (AngularMIX, October 2018) 566 |
  • 567 | 568 |
  • 569 | Building large Angular apps with Bazel 570 | Slides 574 | Video 575 | (BazelCon, October 2018) 576 |
  • 577 | 578 |
  • 579 | How I love being ejected 580 | Slides 581 | Video 582 | (ng-conf, April 2018) 583 |
  • 584 |
  • 585 | Angular Libraries in Bazel 586 | Slides 590 | (AngularSF Meetup, April 2018) 591 |
  • 592 | 593 |
  • 594 | Bazel for building Angular Applications 595 | Video 596 | (St. Louis Angular Lunch, February 2018) 597 |
  • 598 | 599 |
  • 600 | Building Angular Applications like Google 601 | Slides 605 | Video 606 | (AngularConnect, November 2017) 607 |
  • 608 | 609 |
  • 610 | Bazel for Web Frontends 611 | Slides 612 | Video 613 | (BazelCon November 2017) 614 |
  • 615 | 616 |
  • 617 | Angular with Bazel and Closure 618 | Slides 622 | Video 623 | (AngularMIX October 2017) 624 |
  • 625 |
626 |

Articles

627 | 680 | 681 |

Trainings

682 |
    683 |
  • 684 | LiveCoding a Bazel rule for Stylus 685 | Video 689 | (July 2019) 690 |
  • 691 | 692 |
  • 693 | Bazel Training for Angular Team 694 | Slides 698 | (January 2019) 699 |
  • 700 | 701 |
  • 702 | Full Stack development with Nx and Bazel 703 | Slides 707 | Video 708 | (ng-conf, April 2018) 709 |
  • 710 | 711 |
  • 712 | ABC Deep Dive tech talk 713 | Slides 714 | Video 715 | (February 2018) 716 |
  • 717 |
718 | 719 |

Examples

720 | 734 |
735 |
736 |
766 | 767 | 768 | --------------------------------------------------------------------------------