├── .bazelignore ├── .gitignore ├── src ├── lib │ ├── file.ts │ └── BUILD.bazel ├── hello-world │ ├── hello-world.component.scss │ ├── hello-world.component.ts │ ├── hello-world.component.html │ ├── hello-world.module.ts │ ├── hello-world.component.spec.ts │ └── BUILD.bazel ├── styles │ ├── colors.scss │ ├── shared.scss │ ├── fonts.scss │ ├── BUILD │ └── main.scss ├── tsconfig-test.json ├── app.component.ts ├── todos │ ├── todos.component.scss │ ├── todos.module.ts │ ├── BUILD.bazel │ ├── todos.component.html │ └── todos.component.ts ├── images │ ├── BUILD.bazel │ ├── github-circle-white-transparent.svg │ └── bazel-navbar.svg ├── main.dev.ts ├── reducers │ ├── BUILD.bazel │ └── reducers.ts ├── main.prod.ts ├── material │ ├── BUILD.bazel │ └── material.module.ts ├── module-id.js ├── tsconfig.json ├── server │ ├── app-server.ts │ └── BUILD.bazel ├── index.html ├── require.config.js ├── main.ts ├── app-routing.module.ts ├── app.module.ts ├── app.component.html ├── app.module.dev.ts └── BUILD.bazel ├── renovate.json ├── graph.png ├── .clang-format ├── .buildkite └── pipeline.yml ├── Jenkinsfile ├── BUILD.bazel ├── e2e ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts ├── protractor.on-prepare.js └── BUILD.bazel ├── .bazelrc ├── LICENSE ├── postinstall.tsconfig.json ├── .circleci ├── bazel.rc └── config.yml ├── package.json ├── README.md └── WORKSPACE /.bazelignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bazel-* 3 | dist 4 | -------------------------------------------------------------------------------- /src/lib/file.ts: -------------------------------------------------------------------------------- 1 | export const msg: string = 'World'; 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelgevold/angular-bazel-example/HEAD/graph.png -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: JavaScript 2 | BasedOnStyle: Google 3 | ColumnLimit: 100 4 | -------------------------------------------------------------------------------- /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - label: Lint 3 | command: yarn && yarn buildifier -mode=check -------------------------------------------------------------------------------- /src/hello-world/hello-world.component.scss: -------------------------------------------------------------------------------- 1 | @import "src/styles/shared"; 2 | 3 | .mood-icon { 4 | margin: 1rem; 5 | } 6 | -------------------------------------------------------------------------------- /src/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $example-blue: #0000ff; 2 | $example-red: #ff0000; 3 | $theme-primary: #3e236e; 4 | $theme-secondary: #673ab7; 5 | -------------------------------------------------------------------------------- /src/tsconfig-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jasmine"] 5 | } 6 | } -------------------------------------------------------------------------------- /src/styles/shared.scss: -------------------------------------------------------------------------------- 1 | @import "./fonts"; 2 | @import "./colors"; 3 | 4 | h1 { 5 | font-family: $modern-font-stack; 6 | color: $theme-primary; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({selector: 'app-component', templateUrl: 'app.component.html'}) 4 | export class AppComponent { 5 | } 6 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Publish image') { 5 | steps { 6 | sh 'bazel run //src/server:push' 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/todos/todos.component.scss: -------------------------------------------------------------------------------- 1 | @import "src/styles/shared"; 2 | @import "src/styles/colors"; 3 | 4 | .done { 5 | text-decoration: line-through; 6 | } 7 | 8 | .edit-icon { 9 | margin-right: 12px; 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | ts_library( 6 | name = "lib", 7 | srcs = ["file.ts"], 8 | ) 9 | -------------------------------------------------------------------------------- /src/images/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | filegroup( 4 | name = "image_assets", 5 | srcs = [ 6 | "bazel-navbar.svg", 7 | "github-circle-white-transparent.svg", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /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 {AppModuleDevNgFactory} from './app.module.dev.ngfactory'; 6 | 7 | platformBrowser().bootstrapModuleFactory(AppModuleDevNgFactory); 8 | -------------------------------------------------------------------------------- /src/reducers/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | ts_library( 6 | name = "reducers", 7 | srcs = glob(["*.ts"]), 8 | deps = [ 9 | "@npm//@ngrx/store", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | # ts_library and ng_module use the `//:tsconfig.json` target 4 | # by default. This alias allows omitting explicit tsconfig 5 | # attribute. 6 | alias( 7 | name = "tsconfig.json", 8 | actual = "//src:tsconfig.json", 9 | ) 10 | -------------------------------------------------------------------------------- /src/styles/fonts.scss: -------------------------------------------------------------------------------- 1 | $default-font-stack: Cambria, "Hoefler Text", Utopia, "Liberation Serif", "Nimbus Roman No9 L Regular", Times, "Times New Roman", serif; 2 | $modern-font-stack: Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif; 3 | -------------------------------------------------------------------------------- /src/hello-world/hello-world.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { msg } from "../lib/file"; 3 | @Component({ 4 | selector: "hello-world", 5 | templateUrl: "hello-world.component.html", 6 | styleUrls: ["./hello-world.component.css"] 7 | }) 8 | export class HelloWorldComponent { 9 | name: string = msg; 10 | } 11 | -------------------------------------------------------------------------------- /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 {AppModuleNgFactory} from './app.module.ngfactory'; 7 | 8 | enableProdMode(); 9 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); 10 | -------------------------------------------------------------------------------- /src/material/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@angular//:index.bzl", "ng_module") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | ng_module( 6 | name = "material", 7 | srcs = glob(["*.ts"]), 8 | tsconfig = "//src:tsconfig.json", 9 | deps = [ 10 | "@angular//packages/core", 11 | "@angular_material//src/lib:material", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /src/module-id.js: -------------------------------------------------------------------------------- 1 | // Work-around for angular material issue with ts_devserver and ts_web_test_suite. 2 | // Material requires `module.id` to be valid. This symbol is valid in the production 3 | // bundle but not in ts_devserver and ts_web_test_suite. 4 | // See https://github.com/angular/material2/issues/13883. 5 | // TODO(gmagolan): remove this work-around once #13883 is resolved. 6 | var module = {id: ''}; 7 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "es5", 6 | "es2015.collection", 7 | "es2015.iterable", 8 | "es2015.promise" 9 | ], 10 | "rootDirs": [ 11 | ".", 12 | "../dist/bin/src", 13 | ], 14 | "experimentalDecorators": true, 15 | "types": [] 16 | } 17 | } -------------------------------------------------------------------------------- /src/hello-world/hello-world.component.html: -------------------------------------------------------------------------------- 1 |

Home

2 | 3 | 4 | 5 |
Hello {{ name }}
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
mood
14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /src/server/app-server.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import * as compression from "compression"; 3 | 4 | const app = express(); 5 | 6 | app.use(compression()); 7 | 8 | app.use("/", express.static("./src/prodapp")); 9 | 10 | app.get("/", (_req, res) => { 11 | res.sendFile("index.html", { root: "./src/prodapp" }); 12 | }); 13 | 14 | const port = 3000; 15 | app.listen(port, () => { 16 | console.log(`Application Started`); 17 | }); 18 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Bazel Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 World`); 13 | await page.typeInInput('!'); 14 | expect(await page.getParagraphText()).toEqual(`Hello World!`); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatToolbarModule} from '@angular/material'; 3 | 4 | const matModules = [ 5 | MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, 6 | MatToolbarModule 7 | ]; 8 | 9 | @NgModule({ 10 | imports: matModules, 11 | exports: matModules, 12 | }) 13 | export class MaterialModule { 14 | } -------------------------------------------------------------------------------- /src/require.config.js: -------------------------------------------------------------------------------- 1 | // This file adds extra configuration for RequireJS in the 2 | // devserver. For external libs, such as @ngrx, that ship with 3 | // unamed AMD modules, RequireJS needs to be configured so that 4 | // it can load the approriate script from the server for the 5 | // external instead of having that script included in the bundle. 6 | // 7 | // For example, we configure RequireJS to load /store.umd.min.js 8 | // from the server when it encounters require('@ngrx/store'). 9 | require.config({paths: {'@ngrx/store': 'store.umd.min'}}); 10 | -------------------------------------------------------------------------------- /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) 14 | .catch(err => console.log(err)); -------------------------------------------------------------------------------- /src/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 '../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/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 '../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 | -------------------------------------------------------------------------------- /src/styles/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | sass_library( 6 | name = "colors", 7 | srcs = ["colors.scss"], 8 | ) 9 | 10 | sass_library( 11 | name = "fonts", 12 | srcs = ["fonts.scss"], 13 | ) 14 | 15 | sass_library( 16 | name = "shared", 17 | srcs = ["shared.scss"], 18 | deps = [ 19 | ":colors", 20 | ":fonts", 21 | ], 22 | ) 23 | 24 | sass_binary( 25 | name = "main", 26 | src = "main.scss", 27 | deps = [ 28 | ":colors", 29 | ":fonts", 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | async navigateTo() { 5 | await browser.get('/'); 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 | -------------------------------------------------------------------------------- /src/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {Routes, RouterModule} from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | export const helloModuleId = './hello-world/hello-world.module#HelloWorldModule'; 5 | export const todosModuleId = './todos/todos.module#TodosModule'; 6 | 7 | // These are lazy-loaded routes - note that we don't import the modules here 8 | // to avoid having an eager dependency on them. 9 | const routes: Routes = [ 10 | {path: '', pathMatch: 'full', loadChildren: helloModuleId}, 11 | {path: 'todos', pathMatch: 'full', loadChildren: todosModuleId}, 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forRoot(routes)], 16 | exports: [RouterModule], 17 | }) 18 | export class AppRoutingModule { } 19 | -------------------------------------------------------------------------------- /src/images/github-circle-white-transparent.svg: -------------------------------------------------------------------------------- 1 | github-circle-white-transparent -------------------------------------------------------------------------------- /src/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 | 7 | import {AppRoutingModule} from './app-routing.module'; 8 | import {AppComponent} from './app.component'; 9 | import {MaterialModule} from './material/material.module'; 10 | import {todoReducer} from './reducers/reducers'; 11 | 12 | @NgModule({ 13 | declarations: [AppComponent], 14 | imports: [ 15 | AppRoutingModule, BrowserModule, BrowserAnimationsModule, MaterialModule, 16 | StoreModule.forRoot({todoReducer}) 17 | ], 18 | exports: [AppComponent], 19 | bootstrap: [AppComponent], 20 | }) 21 | export class AppModule { 22 | } 23 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "./fonts"; 2 | @import "./colors"; 3 | 4 | html, body { 5 | font-family: $default-font-stack; 6 | padding: 0; 7 | margin: 0; 8 | 9 | .content { 10 | margin: 2rem; 11 | } 12 | } 13 | 14 | nav > .mat-toolbar { 15 | background: $theme-secondary; 16 | color: #fff; 17 | 18 | .nav-logo { 19 | padding-top: 12px; 20 | height: 28px; 21 | } 22 | 23 | .flex-spacer { 24 | flex: 1 1 auto; 25 | } 26 | 27 | a { 28 | text-decoration: none; 29 | } 30 | } 31 | 32 | .mobile-nav > a { 33 | flex: 1 1 auto; 34 | } 35 | 36 | .hide-small { 37 | @media screen and (max-width: 720px) { 38 | display: none !important; 39 | } 40 | } 41 | 42 | .show-small { 43 | @media screen and (min-width: 720px) { 44 | display: none !important; 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/server/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image") 2 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") 3 | 4 | ts_library( 5 | name = "app_server", 6 | srcs = [":app-server.ts"], 7 | deps = ["@npm//express", "@npm//compression"] 8 | ) 9 | 10 | nodejs_image( 11 | name = "app_image", 12 | entry_point = "angular_bazel_example/src/server/app-server.js", 13 | node_modules = "@npm//:node_modules", 14 | data = ["//src:prodapp", ":app_server"] 15 | ) 16 | 17 | load( 18 | "@io_bazel_rules_docker//container:container.bzl", 19 | "container_push" 20 | ) 21 | 22 | container_push( 23 | name = "push", 24 | image = ":app_image", 25 | format = "Docker", 26 | registry = "index.docker.io", 27 | repository = "[TODO: ADD YOUR OWN REPO]", 28 | tag = "1" 29 | ) -------------------------------------------------------------------------------- /src/app.component.html: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Make TypeScript and Angular compilation fast, by keeping a few copies of the 2 | # compiler running as daemons, and cache SourceFile AST's to reduce parse time. 3 | build --strategy=TypeScriptCompile=worker 4 | build --strategy=AngularTemplateCompile=worker 5 | 6 | # Don't create bazel-* symlinks in the WORKSPACE directory. 7 | # These require .gitignore and may scare users. 8 | # Also, it's a workaround for https://github.com/bazelbuild/rules_typescript/issues/12 9 | # which affects the common case of having `tsconfig.json` in the WORKSPACE directory. 10 | # 11 | # Instead, you should run `bazel info bazel-bin` to find out where the outputs went. 12 | build --symlink_prefix=dist/ 13 | 14 | test --test_output=errors 15 | 16 | # Use the Angular 6 compiler 17 | build --define=compile=legacy 18 | 19 | # Turn off legacy external runfiles 20 | run --nolegacy_external_runfiles 21 | test --nolegacy_external_runfiles 22 | -------------------------------------------------------------------------------- /src/todos/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@angular//:index.bzl", "ng_module") 2 | load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | sass_binary( 7 | name = "todos-styles", 8 | src = "todos.component.scss", 9 | deps = ["//src/styles:shared"], 10 | ) 11 | 12 | ng_module( 13 | name = "todos", 14 | srcs = [ 15 | "todos.component.ts", 16 | "todos.module.ts", 17 | ], 18 | assets = [ 19 | "todos.component.html", 20 | ":todos-styles", 21 | ], 22 | tsconfig = "//src:tsconfig.json", 23 | deps = [ 24 | "//src/lib", 25 | "//src/material", 26 | "//src/reducers", 27 | "@angular//packages/common", 28 | "@angular//packages/core", 29 | "@angular//packages/forms", 30 | "@angular//packages/router", 31 | "@npm//@ngrx/store", 32 | "@rxjs", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /src/app.module.dev.ts: -------------------------------------------------------------------------------- 1 | 2 | import {NgModule, NgModuleFactoryLoader} from '@angular/core'; 3 | 4 | import * as routes from './app-routing.module'; 5 | import {AppComponent} from './app.component'; 6 | import {AppModule} from './app.module'; 7 | import {HelloWorldModuleNgFactory} from './hello-world/hello-world.module.ngfactory'; 8 | import {TodosModuleNgFactory} from './todos/todos.module.ngfactory'; 9 | 10 | export class MyLoader extends NgModuleFactoryLoader { 11 | load(id: string) { 12 | switch (id) { 13 | case routes.helloModuleId: 14 | return Promise.resolve(HelloWorldModuleNgFactory); 15 | case routes.todosModuleId: 16 | return Promise.resolve(TodosModuleNgFactory); 17 | default: 18 | throw new Error(`Unrecognized route id ${id}`); 19 | } 20 | } 21 | } 22 | 23 | @NgModule({ 24 | imports: [AppModule], 25 | bootstrap: [AppComponent], 26 | providers: [{provide: NgModuleFactoryLoader, useClass: MyLoader}] 27 | }) 28 | export class AppModuleDev { 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2017 Google, Inc. http://angular.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /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('@angular/bazel/protractor-utils'); 9 | const protractor = require('protractor'); 10 | 11 | module.exports = function(config) { 12 | // In this example, `@angular/bazel/protractor-utils` is used to run 13 | // the server. protractorUtils.runServer() runs the server on a randomly 14 | // selected port (given a port flag to pass to the server as an argument). 15 | // The port used is returned in serverSpec and the protractor serverUrl 16 | // is the configured. 17 | const portFlag = config.server.endsWith('prodserver') ? '-p' : '-port'; 18 | return protractorUtils.runServer(config.workspace, config.server, portFlag, []) 19 | .then(serverSpec => { 20 | const serverUrl = `http://localhost:${serverSpec.port}`; 21 | protractor.browser.baseUrl = serverUrl; 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /e2e/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@angular//:index.bzl", "protractor_web_test_suite") 2 | load("@build_bazel_rules_typescript//:defs.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 | # The @npm//@angular/bazel npm dependency provides 17 | # the @angular/bazel/protractor-utils 18 | protractor_web_test_suite( 19 | name = "prodserver_test", 20 | data = [ 21 | "@npm//@angular/bazel", 22 | "@npm//protractor", 23 | "@npm//zone.js", 24 | ], 25 | on_prepare = ":protractor.on-prepare.js", 26 | server = "//src:prodserver", 27 | deps = [":e2e"], 28 | ) 29 | 30 | # The @npm//@angular/bazel npm dependency provides 31 | # the @angular/bazel/protractor-utils 32 | protractor_web_test_suite( 33 | name = "devserver_test", 34 | data = [ 35 | "@npm//@angular/bazel", 36 | "@npm//protractor", 37 | "@npm//zone.js", 38 | ], 39 | on_prepare = ":protractor.on-prepare.js", 40 | server = "//src:devserver", 41 | deps = [":e2e"], 42 | ) 43 | -------------------------------------------------------------------------------- /postinstall.tsconfig.json: -------------------------------------------------------------------------------- 1 | // WORKAROUND https://github.com/angular/angular/issues/18810 2 | // 3 | // This file is required to run ngc on 3rd party libraries such as @ngrx, 4 | // to write files like node_modules/@ngrx/store/store.ngsummary.json. 5 | // 6 | // Using ngc to create generated files is sufficient as long 7 | // as there are no components or directives in the 3rd party library 8 | // that are used. If there are, then there is an existing issue where 9 | // components & modules that use these 3rd party components or directives 10 | // can't be tested with ts_web_test_suite(). 11 | // See https://github.com/bazelbuild/rules_typescript/issues/192 12 | // 13 | // The work-around for this right now is to compile the 3rd party 14 | // from source using Bazel so that Bazel is used to create 15 | // its generated files. 16 | { 17 | "compilerOptions": { 18 | "lib": [ 19 | "dom", 20 | "es2015" 21 | ], 22 | "experimentalDecorators": true, 23 | "types": [] 24 | }, 25 | "include": [ 26 | "node_modules/@ngrx/**/*" 27 | ], 28 | "exclude": [ 29 | "node_modules/@ngrx/store/migrations/**", 30 | "node_modules/@ngrx/store/schematics/**", 31 | "node_modules/@ngrx/store/schematics-core/**" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /.circleci/bazel.rc: -------------------------------------------------------------------------------- 1 | # These options are enabled when running on CI 2 | # We do this by copying this file to /etc/bazel.bazelrc at the start of the build. 3 | # See remote cache documentation in /docs/BAZEL.md 4 | 5 | # Don't be spammy in the logs 6 | # TODO: re-enable after we deal with 10m timeout on circleci 7 | #build --noshow_progress 8 | 9 | # Don't run manual tests 10 | test --test_tag_filters=-manual 11 | 12 | # Print all the options that apply to the build. 13 | # This helps us diagnose which options override others 14 | # (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc) 15 | build --announce_rc 16 | 17 | # Prevent unstable environment variables from tainting cache keys 18 | build --experimental_strict_action_env 19 | 20 | # Save downloaded repositories such as the go toolchain 21 | # This directory can then be included in the CircleCI cache 22 | # It should save time running the first build 23 | build --experimental_repository_cache=/home/circleci/bazel_repository_cache 24 | 25 | # Workaround https://github.com/bazelbuild/bazel/issues/3645 26 | # Bazel doesn't calculate the memory ceiling correctly when running under Docker. 27 | # Limit Bazel to consuming 2048K of RAM 28 | build --local_resources=2048,1.0,1.0 29 | # Also limit Bazel's own JVM heap to stay within our 4G container limit 30 | startup --host_jvm_args=-Xmx1408m 31 | 32 | # Use fixed chunk names for code-split bundles in CI 33 | # so that GitHub buildsize bot reports are accurate 34 | build --define=ROLLUP_BUNDLE_FIXED_CHUNK_NAMES=1 35 | -------------------------------------------------------------------------------- /src/todos/todos.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Store} from '@ngrx/store'; 3 | import {Observable} from 'rxjs'; 4 | 5 | import {ADD_TODO, DELETE_TODO, TOGGLE_DONE, UPDATE_TODO} from '../reducers/reducers'; 6 | 7 | @Component({ 8 | selector: 'todos', 9 | templateUrl: './todos.component.html', 10 | styleUrls: ['./todos.component.css'] 11 | }) 12 | export class TodosComponent implements OnInit { 13 | todos$: Observable; 14 | todo: string; 15 | editing = false; 16 | indexToEdit: number|null; 17 | 18 | constructor(private store: Store) {} 19 | 20 | ngOnInit() { 21 | this.todos$ = this.store.select('todoReducer'); 22 | } 23 | 24 | addTodo(value) { 25 | this.store.dispatch({type: ADD_TODO, payload: {value, done: false}}); 26 | this.todo = ''; 27 | } 28 | 29 | deleteTodo(index) { 30 | this.store.dispatch({type: DELETE_TODO, payload: {index}}); 31 | } 32 | 33 | editTodo(todo, index) { 34 | this.editing = true; 35 | this.todo = todo.value; 36 | this.indexToEdit = index; 37 | } 38 | 39 | cancelEdit() { 40 | this.editing = false; 41 | this.todo = ''; 42 | this.indexToEdit = null; 43 | } 44 | 45 | updateTodo(updatedTodo) { 46 | this.store.dispatch( 47 | {type: UPDATE_TODO, payload: {index: this.indexToEdit, newValue: updatedTodo}}); 48 | this.todo = ''; 49 | this.indexToEdit = null; 50 | this.editing = false; 51 | } 52 | 53 | toggleDone(todo, index) { 54 | this.store.dispatch({type: TOGGLE_DONE, payload: {index, done: todo.done}}); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/hello-world/hello-world.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {By} from '@angular/platform-browser'; 3 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 4 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 5 | 6 | import {HelloWorldComponent} from './hello-world.component'; 7 | import {HelloWorldModuleNgSummary} from './hello-world.module.ngsummary'; 8 | 9 | // TODO(alexeagle): this helper should be in @angular/platform-browser-dynamic/testing 10 | try { 11 | TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 12 | } catch { 13 | // Ignore exceptions when calling it multiple times. 14 | } 15 | 16 | describe('BannerComponent (inline template)', () => { 17 | let comp: HelloWorldComponent; 18 | let fixture: ComponentFixture; 19 | let el: HTMLElement; 20 | 21 | beforeEach(async(() => { 22 | TestBed.configureTestingModule({ 23 | declarations: [HelloWorldComponent], // declare the test component 24 | aotSummaries: HelloWorldModuleNgSummary, 25 | imports: [BrowserAnimationsModule], 26 | }); 27 | TestBed.compileComponents(); 28 | })); 29 | 30 | beforeEach(() => { 31 | fixture = TestBed.createComponent(HelloWorldComponent); 32 | comp = fixture.componentInstance; 33 | el = fixture.debugElement.query(By.css('div')).nativeElement; 34 | }); 35 | 36 | it('should display original title', () => { 37 | fixture.detectChanges(); 38 | expect(el.textContent).toContain(comp.name); 39 | }); 40 | 41 | it('should display a different test title', () => { 42 | comp.name = 'Test'; 43 | fixture.detectChanges(); 44 | expect(el.textContent).toContain('Test'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/hello-world/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@angular//:index.bzl", "ng_module") 2 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test_suite") 3 | load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") 4 | 5 | package(default_visibility = ["//visibility:public"]) 6 | 7 | sass_binary( 8 | name = "hello-world-styles", 9 | src = "hello-world.component.scss", 10 | deps = ["//src/styles:shared"], 11 | ) 12 | 13 | ng_module( 14 | name = "hello-world", 15 | srcs = [ 16 | "hello-world.component.ts", 17 | "hello-world.module.ts", 18 | ], 19 | assets = [ 20 | ":hello-world.component.html", 21 | ":hello-world-styles", 22 | ], 23 | tsconfig = "//src:tsconfig.json", 24 | deps = [ 25 | "//src/lib", 26 | "//src/material", 27 | "@angular//packages/core", 28 | "@angular//packages/forms", 29 | "@angular//packages/router", 30 | ], 31 | ) 32 | 33 | ts_library( 34 | name = "test_lib", 35 | testonly = 1, 36 | srcs = glob(["*.spec.ts"]), 37 | deps = [ 38 | ":hello-world", 39 | "@angular//packages/core", 40 | "@angular//packages/core/testing", 41 | "@angular//packages/platform-browser", 42 | "@angular//packages/platform-browser-dynamic/testing", 43 | "@angular//packages/platform-browser/animations", 44 | "@npm//@types/jasmine", 45 | "@npm//@types/node", 46 | ], 47 | ) 48 | 49 | ts_web_test_suite( 50 | name = "test", 51 | srcs = ["@npm//node_modules/tslib:tslib.js"], 52 | # do not sort 53 | bootstrap = [ 54 | "@npm//node_modules/zone.js:dist/zone-testing-bundle.js", 55 | "@npm//node_modules/reflect-metadata:Reflect.js", 56 | "//src:module-id.js", 57 | ], 58 | browsers = [ 59 | "@io_bazel_rules_webtesting//browsers:chromium-local", 60 | # TODO(gregmagolan): re-enable firefox testing once fixed 61 | # See https://github.com/bazelbuild/rules_typescript/issues/296 62 | #"@io_bazel_rules_webtesting//browsers:firefox-local", 63 | ], 64 | deps = [ 65 | ":test_lib", 66 | ], 67 | ) 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.1", 4 | "description": "Demo of bazel rules for angular", 5 | "license": "Apache 2.0", 6 | "engines": { 7 | "node": ">=10.9.0 <11.0.0", 8 | "yarn": ">=1.9.2 <2.0.0" 9 | }, 10 | "dependencies": { 11 | "@ngrx/store": "7.0.0", 12 | "express": "^4.16.4", 13 | "rxjs": "6.3.3", 14 | "systemjs": "0.21.5", 15 | "tslib": "1.9.3", 16 | "zone.js": "0.8.26", 17 | "compression": "1.7.2" 18 | }, 19 | "devDependencies": { 20 | "@angular/animations": "7.1.3", 21 | "@angular/bazel": "7.1.3", 22 | "@angular/cdk": "7.1.1", 23 | "@angular/common": "7.1.3", 24 | "@angular/compiler": "7.1.3", 25 | "@angular/compiler-cli": "7.1.3", 26 | "@angular/core": "7.1.3", 27 | "@angular/material": "7.1.1", 28 | "@bazel/benchmark-runner": "0.1.0", 29 | "@bazel/buildifier": "^0.20.0", 30 | "@bazel/ibazel": "0.9.0", 31 | "@bazel/karma": "0.22.0", 32 | "@bazel/typescript": "0.22.0", 33 | "@types/jasmine": "3.3.5", 34 | "@types/node": "10.12.21", 35 | "clang-format": "1.2.4", 36 | "husky": "0.14.3", 37 | "protractor": "5.4.1", 38 | "typescript": "3.1.1" 39 | }, 40 | "scripts": { 41 | "build": "bazel build //src:bundle", 42 | "serve": "ibazel run //src:devserver", 43 | "serve-prod": "bazel run //src:prodserver", 44 | "e2e": "bazel test //e2e:all", 45 | "test": "bazel test //src/...", 46 | "benchmark": "ibazel-benchmark-runner //src:devserver src/hello-world/hello-world.component.ts --url=http://localhost:5432", 47 | "postinstall": "ngc -p postinstall.tsconfig.json", 48 | "bazel:format": "find . -type f \\( -name \"*.bzl\" -or -name WORKSPACE -or -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs buildifier -v --warnings=args-order,attr-cfg,attr-license,attr-non-empty,attr-output-default,attr-single-file,constant-glob,ctx-actions,ctx-args,depset-iteration,depset-union,dict-concatenation,duplicated-name,filetype,git-repository,http-archive,integer-division,load,load-on-top,native-build,native-package,out-of-order-load,output-group,package-name,package-on-top,positional-args,redefined-variable,repository-name,same-origin-load,string-iteration,unsorted-dict-items,unused-variable", 49 | "bazel:lint": "yarn bazel:format --lint=warn", 50 | "bazel:lint-fix": "yarn bazel:format --lint=fix", 51 | "format": "git-clang-format", 52 | "precommit": "check-clang-format \"yarn format\"" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/images/bazel-navbar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 31 | 32 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # This file configures the build at https://circleci.com/gh/alexeagle/angular-bazel-example 2 | # Complete documentation is at https://circleci.com/docs/2.0/ 3 | 4 | # CircleCI lets us pick the key for storing one or more caches, to speed up subsequent builds. 5 | # We can use this to avoid re-fetching our dependencies from npm on every build. 6 | # To ensure we don't load a stale cache, we invalidate it based on the entries in the key: 7 | # - the checksum of Yarn's lock file 8 | # - the branch we are on, which really shouldn't be needed since the yarn lock file should be hermetic 9 | # - the docker image tag, working around an issue we saw where changing docker images causes permission 10 | # errors when restoring the cache, like when the user we run as changes 11 | var_1: &cache_key angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.8.0 12 | 13 | # Each job will inherit these defaults 14 | var_2: &job_defaults 15 | working_directory: ~/ng 16 | docker: 17 | # Use the -browsers version just to get the right .so files on the disk 18 | # We'll actually download a chromium version under the rules_webtesting 19 | - image: circleci/node:10.12-browsers 20 | 21 | # After checkout, rebase on top of master, because we want to test the proposed merge of a 22 | # onto the target branch, not just test what's on the user's fork. 23 | # Similar to travis behavior, but not quite the same. 24 | # See https://discuss.circleci.com/t/1662 25 | var_3: &post_checkout 26 | post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge" 27 | 28 | # Opt-in to the new goodness 29 | version: 2 30 | 31 | # These jobs will run in parallel, and report separate statuses to GitHub PRs 32 | jobs: 33 | lint: 34 | docker: 35 | - image: circleci/node:10.12 36 | steps: 37 | - checkout: 38 | <<: *post_checkout 39 | 40 | - restore_cache: 41 | key: *cache_key 42 | 43 | # Install Bazel 44 | - run: yarn install 45 | 46 | # Run the Buildifier to check our Bazel rules for format issues. 47 | - run: 'yarn bazel:format --mode=check || 48 | (echo "BUILD files not formatted. Please run ''yarn bazel:format --mode=fix''" ; exit 1)' 49 | 50 | # Run the Buildifier to check our Bazel rules for lint issues. 51 | # Note: The `--lint=warn` will auto fixe (re-write) the affected files. 52 | - run: 'yarn bazel:format --lint=warn || 53 | (echo "BUILD files contain unresolved lint errors. Please fix manually the remaining errors." ; exit 1)' 54 | 55 | build: 56 | <<: *job_defaults 57 | resource_class: xlarge 58 | steps: 59 | - checkout: 60 | <<: *post_checkout 61 | 62 | - restore_cache: 63 | key: *cache_key 64 | 65 | - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc 66 | 67 | # Install Bazel from NPM 68 | - run: yarn 69 | 70 | # Build and Test 71 | - run: yarn test 72 | - run: yarn e2e 73 | 74 | - store_artifacts: 75 | path: dist/bin/src/bundle.min.js 76 | destination: bundle.min.js 77 | 78 | - store_artifacts: 79 | path: dist/bin/src/bundle.cs.min 80 | destination: bundle.cs.min 81 | 82 | benchmark: 83 | <<: *job_defaults 84 | resource_class: xlarge 85 | steps: 86 | - checkout: 87 | <<: *post_checkout 88 | 89 | - restore_cache: 90 | key: *cache_key 91 | 92 | # Install Bazel/ibazel from NPM 93 | - run: yarn 94 | 95 | # Run `bazel build` first as a temporary workaround to unexpected 96 | # benchmark failure when entire build runs withing ibazel-benchmark-runner 97 | # ``` 98 | # Error running Bazel unexpected EOF 99 | # [ibazel-benchmark-runner] iBazel process exited unexpectedly 4 null 100 | # error Command failed with exit code 1. 101 | # ``` 102 | # TODO(gregmagolan): remove this once issue is resolved 103 | - run: ./node_modules/.bin/bazel build ... 104 | 105 | # Run the benchmark 106 | - run: yarn benchmark 107 | 108 | # If we get this far, save the node_modules directory for use next time. 109 | - save_cache: 110 | key: *cache_key 111 | paths: 112 | - "node_modules" 113 | 114 | workflows: 115 | version: 2 116 | default_workflow: 117 | jobs: 118 | - lint 119 | - build 120 | - benchmark 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/alexeagle/angular-bazel-example.svg?style=svg)](https://circleci.com/gh/alexeagle/angular-bazel-example) 2 | 3 | # Example of building an Angular app with Bazel 4 | 5 | **This is experimental! There may be breaking changes.** 6 | 7 | This is part of the ABC project. The overall goal is to make it possible to 8 | develop Angular applications the same way we do at Google. 9 | See http://g.co/ng/abc for an overview. 10 | 11 | You can read the documentation in the wiki of this repository to understand how 12 | this works. 13 | 14 | Follow https://github.com/angular/angular/issues/19058 for updates. 15 | 16 | ## Installation 17 | 18 | You only need to install one build tool, and which one you choose typically depends on what kind of development you do most often. 19 | 20 | If you're a frontend developer, you should install NodeJS and yarn. 21 | The `package.json` file has an `engines` section which indicates the range of NodeJS and yarn versions that you could use. 22 | You simply run `yarn` commands shown below, and don't need to install Bazel or any other dependencies. 23 | 24 | If you're a full-stack developer, you might be using Bazel for your backend already. 25 | In this case, you should install Bazel following instructions at http://bazel.build. 26 | Also install `ibazel`, which is a watch mode for Bazel not included in the standard distribution. See https://github.com/bazelbuild/bazel-watcher#installation. 27 | The `WORKSPACE` file has a `check_bazel_version` call which will print an error if your Bazel version is not in the supported range. 28 | You simply run `bazel` commands shown below, and don't need to install NodeJS, yarn, or any other dependencies. 29 | 30 | ## Development 31 | 32 | First we'll run the development server: 33 | 34 | ```bash 35 | $ yarn serve 36 | # or 37 | $ ibazel run //src:devserver 38 | ``` 39 | 40 | This runs in "watch mode", which means it will watch any files that are inputs to the devserver, and when they change it will ask Bazel to re-build them. 41 | When the re-build is finished, it will trigger a LiveReload in the browser. 42 | 43 | This command prints a URL on the terminal. Open that page to see the demo app 44 | running. Now you can edit one of the source files (`src/lib/file.ts` is an easy 45 | one to understand and see the effect). As soon as you save a change, the app 46 | should refresh in the browser with the new content. Our intent is that this time 47 | is less than two seconds, even for a large application. 48 | 49 | Control-C twice to kill the devserver. 50 | 51 | ## Testing 52 | 53 | We can also run all the unit tests: 54 | 55 | ```bash 56 | $ yarn test 57 | # or 58 | $ bazel test //src/... 59 | ``` 60 | 61 | Or run the end-to-end tests: 62 | 63 | ```bash 64 | $ yarn e2e 65 | # or 66 | $ bazel test //e2e/... 67 | ``` 68 | 69 | In this example, there is a unit test for the `hello-world` component which uses 70 | the `ts_web_test_suite` rule. There are also protractor e2e tests for both the 71 | `prodserver` and `devserver` which use the `protractor_web_test_suite` rule. 72 | 73 | Note that Bazel will only re-run the tests whose inputs changed since the last run. 74 | 75 | ## Production 76 | 77 | We can run the application in production mode, where the code has been bundled 78 | and optimized. This can be slower than the development mode, because any change 79 | requires re-optimizing the app. This example uses Rollup and Uglify, but other 80 | bundlers can be integrated with Bazel. 81 | 82 | ```bash 83 | $ yarn serve-prod 84 | # or 85 | $ bazel run //src:prodserver 86 | ``` 87 | 88 | ### Code splitting 89 | 90 | The production bundle is code split and the `/` and `/todos` routes 91 | are lazy loaded. Code splitting is handled by the rollup_bundle rule 92 | which now supports the new code splitting feature in rollup. 93 | 94 | Note: code splitting is _not_ supported in development mode yet so the 95 | `//src:devserver` target does not serve a code split bundle. For this 96 | reason, development and production use different main entry points 97 | (`main.dev.ts` and `main.ts`) and different root modules 98 | (`app.module.dev.ts` and `app.module.ts`). The difference in 99 | the entry points and modules is how routes are loaded, with production 100 | lazy loading routes and development using a custom `NgModuleFactoryLoader` 101 | loader to disable lazy loading. `enableProdMode()` is 102 | also called in the production entry point. 103 | 104 | ## Npm dependencies 105 | 106 | Having a local `node_modules` folder setup by `yarn` or `npm` is not 107 | necessary when building this example with Bazel. This example makes use 108 | of Bazel managed npm dependencies (https://github.com/bazelbuild/rules_nodejs#using-bazel-managed-dependencies) 109 | which means Bazel will setup the npm dependencies in your `package.json` for you 110 | outside of your local workspace for use in the build. 111 | 112 | However, you may still want to run `yarn` or `npm` to manually 113 | setup a local `node_modules` folder for editor and tooling support. 114 | -------------------------------------------------------------------------------- /src/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@angular//:index.bzl", "ng_module") 2 | load("@build_bazel_rules_nodejs//:defs.bzl", "history_server", "rollup_bundle") 3 | load("@build_bazel_rules_nodejs//internal/web_package:web_package.bzl", "web_package") 4 | load("@build_bazel_rules_typescript//:defs.bzl", "ts_config", "ts_devserver") 5 | 6 | package(default_visibility = ["//visibility:public"]) 7 | 8 | ts_config( 9 | name = "tsconfig-test", 10 | src = "tsconfig-test.json", 11 | deps = [":tsconfig.json"], 12 | ) 13 | 14 | ng_module( 15 | name = "src", 16 | srcs = glob( 17 | ["*.ts"], 18 | exclude = ["main.ts"], 19 | ), 20 | assets = [ 21 | ":app.component.html", 22 | "//src/styles:main", 23 | ], 24 | tsconfig = ":tsconfig.json", 25 | deps = [ 26 | "//src/hello-world", 27 | "//src/material", 28 | "//src/todos", 29 | "@angular//packages/core", 30 | "@angular//packages/platform-browser", 31 | "@angular//packages/platform-browser/animations", 32 | "@angular//packages/router", 33 | "@npm//@ngrx/store", 34 | ], 35 | ) 36 | 37 | # We always strip these paths off the front of any assets we serve 38 | _ROOT_DIRS = [ 39 | "npm/node_modules/zone.js/dist", 40 | "npm/node_modules/@angular/material/prebuilt-themes", 41 | "npm/node_modules/@ngrx/store/bundles", 42 | ] 43 | 44 | # This devserver is written in Go and is super-fast. 45 | # It doesn't run any bundler or code splitter. Instead, it concatenates 46 | # UMD JavaScript code on-the-fly in-memory. 47 | # This scales really well for massive codebases. 48 | ts_devserver( 49 | name = "devserver", 50 | # serve these files rooted at / 51 | additional_root_paths = _ROOT_DIRS, 52 | # Serve these files but don't inject tags for them into the index file 53 | # This might be because we only want to lazy-load these scripts on-demand, 54 | # or because they aren't compatible with Require.js so we must use a runtime 55 | # loader to load them. 56 | data = [ 57 | "//src/images:image_assets", 58 | "@npm//node_modules/@ngrx/store:bundles/store.umd.min.js", 59 | ], 60 | # Start from the development version of the main 61 | entry_module = "angular_bazel_example/src/main.dev", 62 | #