From Code to Build: The Angular Development Pipeline
An Angular application is written in TypeScript and structured around decorators, modules, and templates. None of this is directly executable by a browser. Here is what ng build actually does.
An Angular application is written in TypeScript and structured around decorators, modules, and templates. None of this is directly executable by a browser. The Angular build pipeline, managed by the Angular CLI, transforms this source code through several stages before producing output a browser can load. Understanding each stage removes the ambiguity around what ng build is actually doing.
Project Creation and Structure
The Angular CLI creates a project with a defined structure and a configuration file, angular.json, which controls every aspect of the build process.
my-app/
src/
app/
app.component.ts
app.component.html
app.component.css
app.module.ts
main.ts
index.html
polyfills.ts
styles.css
angular.json
tsconfig.json
package.json
angular.json defines the entry point, the output path, which assets to include, global stylesheets, polyfill files, and the build configuration for development and production environments. The CLI reads this file before doing anything.
TypeScript Compilation
Angular applications are written in TypeScript. TypeScript is a superset of JavaScript that adds static type checking and features that do not exist in standard JavaScript, such as decorators. The browser cannot execute TypeScript. It must be compiled to JavaScript first.
The TypeScript compiler, configured via tsconfig.json, performs this compilation. It checks types across the entire application and transforms TypeScript syntax into JavaScript. If there are type errors, the build fails at this stage.
Decorators and Metadata
Angular uses decorators extensively. @Component, @NgModule, @Injectable and @Input are decorators that attach metadata to classes, telling the Angular framework how to construct and use them.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'my-app';
}
The Angular compiler reads this metadata to understand the relationship between components, their templates, and their dependencies. This is the foundation of both compilation modes.
JIT vs AOT Compilation
Angular has two compilation modes that determine when templates are compiled.
Just-in-Time compilation, or JIT, compiles templates inside the browser at runtime. The Angular compiler is included in the bundle shipped to the browser. When the application loads, the compiler reads the component metadata and compiles the templates on the fly. This is faster to build but slower to render, and ships more code to the browser.
Ahead-of-Time compilation, or AOT, compiles templates during the build process, before anything reaches the browser. The Angular Ahead-of-Time compiler converts HTML templates and TypeScript code into efficient JavaScript during the build phase. The browser receives pre-compiled code and does not need the Angular compiler at runtime.
AOT is the default for production builds in Angular as of Angular 9. Running ng build in 2021 uses AOT by default.
AOT provides three advantages over JIT: the browser renders the application faster because no compilation happens at runtime, the bundle is smaller because the Angular compiler is not shipped to the browser, and template errors are caught at build time rather than at runtime.
Webpack and Bundling
The Angular CLI uses webpack internally to bundle the compiled JavaScript into output files. After TypeScript and AOT compilation, webpack builds a dependency graph from src/main.ts as the entry point and bundles all modules into chunks.
The production build applies the following:
Tree shaking removes unused code by analysing the ES module import and export graph. Only code that is actually reachable from the entry point is included in the output. Minification reduces file sizes by removing whitespace and renaming variables to shorter identifiers. The build optimizer, enabled in production builds, removes Angular-specific decorators and constructor parameters from the compiled output, making the code smaller and more amenable to further minification.
Polyfills
polyfills.ts is a dedicated entry point for browser compatibility code. Polyfills implement features that modern JavaScript uses but that older browsers do not support. Angular bundles these separately so they can be loaded independently and cached separately from the application code.
ng serve vs ng build
ng serve compiles the application and serves it from memory using a local development server at http://localhost:4200. It watches for file changes and rebuilds automatically. The build is not optimised and includes source maps for debugging.
ng build compiles the application and writes the output to the dist/ folder. In production configuration, AOT, tree shaking, minification, and the build optimizer are all active.
What Lives in dist/
After ng build, the dist/ folder contains:
index.html, with injected script tags pointing to the compiled bundlesmain.[hash].js, the compiled application codepolyfills.[hash].js, the browser compatibility layerruntime.[hash].js, webpack’s runtime code for loading modulesstyles.[hash].css, the compiled global styles- Any lazy-loaded feature modules compiled into separate chunk files, loaded by the router on demand
Content hashing in the filenames enables cache busting: when file content changes between deployments, the filename changes, and the browser fetches the new version rather than serving a cached file.
What to Do Now
Create an Angular project and run both commands to observe the difference in output:
ng new pipeline-demo
cd pipeline-demo
ng build
Inspect the dist/ folder. Note the separate files for main, polyfills, runtime and styles. Open dist/pipeline-demo/index.html and observe the script tags that were injected automatically. Then compare the file sizes in the terminal output. The hash in each filename is the fingerprint webpack assigns based on the file contents. Change any component, run ng build again, and observe that the hash on main.[hash].js has changed while polyfills.[hash].js remains the same, because the polyfills file was not modified.