A few days back, I wrote about ngtsc Angular latest compiler. In that article, I talked about how angular compiles the code in the latest version(v9) but think about it, not all code is compiled at the same time there are libraries, npm modules, there dependencies are precompiled and these may not work well with the way ngtsc compiles Angular code. This may cause failures at runtime, compile-time, etc. Fortunately, the Angular has already solved these problems for us using the Angular compatibility compiler ngcc.

What is ngcc??#

Let us first see it from Angular glossary.

Angular compatibility compiler. If you build your app using Ivy, but it depends on libraries that have not been compiled with Ivy, the CLI uses ngcc to automatically update the dependent libraries to use Ivy.

As some of us may know that Ivy code only works well with there Ivy code. So, if you want to build an application using pre-Ivy libraries we have to compile them into Ivy compatible Libraries. It is the ngcc compiler that compiles the non-Ivy compatible packages into Ivy compatible packages.

How ngcc works?#

One of the first problems that we face while transpiling non-Ivy code into Ivy compiled code is that we now have no decorator info in our files. To solve it ngcc uses the .metadata.json files in the Angular Package Format. These files contain information that was present in the Typescript source but was removed during transpilation to Javascript, and this information is sufficient to generate a patched .js files which add the Ivy static properties to decorated classes.

Metadata files from Angular Package Format#

The .metadata.json files contain the arguments to the angular decorators which are down leveled by ngtsc to static properties on the components, dirrectives etc. These files are there for each .js file and ngcc collects all the info from these files. For example, the .metadata.json file for CommonModule contains the information for its NgModule decorator which was originally present in the Typescript source:

"CommonModule": {
  "__symbolic": "class",
  "decorators": [{
    "__symbolic": "call",
    "expression": {
      "__symbolic": "reference",
      "module": "@angular/core",
      "name": "NgModule",
      "line": 22,
      "character": 1
    },
    "arguments": [{
      "declarations": [...],
      "exports": [...],
      "providers": [...]
    }]
  }]
}

Operations done by ngcc#

  1. Scan the node_modules for packages that are in APF(Angular package format)
  2. Produce Ivy compatible versions for each of these packages

To generate Ivy compatible versions ngcc outputs a directory called ngcc_node_modules its structures mirrors the structure of the node_modules directory. The packages that are converted have the non-transpiled files, copied package.json, only the .js, and .d.ts files are changed, and the .metadata.json files are removed in this output.

An example directory layout would be:

# input
node_modules/
  ng-dep/
    package.json
    index.js (pre-ivy)
    index.d.ts (pre-ivy)
    index.metadata.json
    other.js

# output
ngcc_node_modules
  ng-dep/
    package.json
    index.js (ivy compatible)
    index.d.ts (ivy-compatible)
    other.js

What happens if a package does not follow APF

The Angular Package Format includes more than one copy of a package's code it includes one ESM5 (ES5 code in ES Modules) entry point, one ES2015 entry point, and one UMD entry point. Some libraries not following the package format may still work in the Angular CLI, if they export code that can be loaded by Webpack.

So, ngcc will have to have two approaches for dealing with packages on NPM.

  1. APF Path: libraries following the Angular package format will have their source code updated to contain Ivy definitions.

  2. Compatibility Path: libraries, where ngcc cannot determine how to safely modify the existing code, will have a patching operation applied.

This produces a "wrapper" file for each file containing an Angular entity. This is not compatible with tree-shaking, but will work for libraries which ngcc cannot otherwise understand. A warning will be printed to notify the user they should update the version of the library if possible.

For example, if a library ships with commonjs-only code or a UMD bundle that ngcc isn't able to patch directly, it will generate patching wrappers instead of modifying the input code.

Not using the APF makes it non-compatible with tree shaking so using APF is highly recommended.

Overall ngcc architecture#

Compiling a package in ngcc involves the following steps:

  1. Check if the package is in the APF by looking for the presence of a .metadata.json file alongside the package's module entry point.

  2. Parse the JS files of the package with the Typescript parser.

  3. Invoke the legacy VE @angular/compiler to parse the .metadata.json files using.

  4. Run through each Angular decorator in the Ivy system and compile:

    • Use the JS AST plus the information from the VE compiler data from .metadata.json to construct the input to Compiler.
    • Run the Compiler which will produce a partial class and its type declaration.
    • Extract the static property definition from the partial class.
  5. Combine the compiler outputs with the JS AST to produce the resulting .js and .d.ts files, and write them to disk.

  6. Copy over all other files.

So, that is it for today.