Skip to content
This repository has been archived by the owner on Mar 20, 2024. It is now read-only.

Getting Started

Alex Eagle edited this page Jun 1, 2018 · 2 revisions

We assume you have an existing Angular application. If not, you can use the Angular CLI to create one.

Your application needs to work with Angular's Ahead-of-time compiler, which is a requirement for using Closure Compiler.

You'll need recent versions of the dependencies:

  • Angular 4.1
  • Zone.js 0.8.10
  • rxjs 5.3.1
  • google-closure-compiler 20170409 (20170423 is broken)

Installation

You'll need to have Java installed on your computer. Zulu's OpenJDK is one option. We hope to remove the need for this step in the future.

Add the google-closure-compiler package as a devDependency in your project, using your package tool (eg. yarn or npm). Note that the google-closure-compiler-js project does not work due to a number of open issues.

Configuring Closure Compiler

We'll store command-line flags in the closure.conf file. These flags are documented in various places on https://github.com/google/closure-compiler/wiki and can also be seen by running Closure Compiler with the --help option.

$ java -jar node_modules/google-closure-compiler/compiler.jar --help

The full set of required options is in the example closure.conf. Below is a description of the options required:

Essential options

  • --compilation_level=ADVANCED_OPTIMIZATIONS: Enable property renaming and other optimizations. Using any other value is missing the point: you won't get a smaller bundle than with other tools.
  • --language_out=ES5: We want ES2015-to-ES5 transpilation (like Babel)
  • --js_output_file=dist/bundle.js: where to write the bundle. Make sure your development server serves this so that the <script> tag in the HTML can load it.

Tree-shaking and pruning

  • --entry_point=./built/bootstrap: points to the location of the bootstrap .js file.
  • --dependency_mode=STRICT: turn on dead-file elimination. Any file that's not in the transitive imports of your entry point will be dropped, even if it has some side-effects.

Input files and module loading

Unlike most tools, Closure Compiler does not locate input files on disk (eg. in the node_modules folder) which are not specified. So we exhaustively list the inputs, using a glob syntax

Internally, Closure compiler converts our ES2015 modules (which are file-based) into an internal module system (goog.module). Later when it encounters an import statement, the module name must match. This requires trimming a prefix off the file paths, so that a file at prefix/path is imported by import 'path`` rather than import 'prefix$path'`.

node_modules/@angular/core/@angular/core.js
--js_module_root=node_modules/@angular/core
node_modules/@angular/common/@angular/common.js
--js_module_root=node_modules/@angular/common
node_modules/@angular/platform-browser/@angular/platform-browser.js
--js_module_root=node_modules/@angular/platform-browser

Also point to the .js files that were written by the build:

--js built/**.js

Finally, we need to avoid Closure Compiler renaming anything that will be loaded externally, since such renaming would not be applied to all usage sites. This includes scripts we load separately (like zone.js) and interactions we'll have with the page at runtime (testing with protractor).

Closure Compiler uses "externs" files to specify these external, non-renamable identifiers. We can just pass the bare file paths as flags:

node_modules/zone.js/dist/zone_externs.js
node_modules/@angular/core/src/testability/testability.externs.js

Developer convenience

The following options produce some extra files to help you understand which inputs were retained, how the properties were renamed, and enable sourcemap for easier debugging.

--output_manifest=dist/manifest.MF
--variable_renaming_report=dist/variable_renaming_report
--property_renaming_report=dist/property_renaming_report
--create_source_map=%outname%.map

Finally, we turn off the warnings produced by the compiler. It expects to type-check your code, but TypeScript has already done that.

--warning_level=QUIET

Configuring the Angular and TypeScript compilers

Closure compiler has some restrictions on the input JS files, so we tell Angular to transform all the TypeScript files to conform. For example, we add JSDoc comments to transport some type information. Add this to your tsconfig.json file at the top-level (a sibling of the "compilerOptions" property):

"angularCompilerOptions": {
    "annotateForClosureCompiler": true
}

Building the bundle

First, we must run the Angular and TypeScript compilers to convert .ts and .html inputs into JavaScript. We can run the ngc command.

$ ./node_modules/.bin/ngc -p path/to/tsconfig.json

Now we can run Closure Compiler, passing the file of flags:

$ java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf

For convenience, you can wrap these in a package.json script:

"scripts": {
    "build": "ngc -p src && yarn run closure",
    "closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf",
  },