├── .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 |
16 | Create
17 |
18 |
19 |
22 | Update
23 |
24 |
25 |
28 | Cancel
29 |
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 |
2 |
3 |
4 | menu
5 |
6 |
7 |
8 |
9 | +
10 |
11 |
12 |
13 |
14 |
15 | Documentation
16 |
17 |
18 |
19 |
20 |
21 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
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 |
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 |