Techblog: Custom Typescript Transformers with Angular CLI

Techblog: Custom Typescript Transformers with Angular CLI

Techblog: Custom Typescript Transformers with Angular CLI

We will be hacking on the Angular CLI build using private API’s. Keep in mind that these could break without prior notice!

The Angular CLI uses the AngularCompilerPlugin to transpile typescript. It is a webpack plugin that uses the typescript compiler together with various Typescript transformers to transpile the typescript to workable code for the browser. See this article from Alexey Zuev on how Angular uses code tranformations.

Looking for a way to add custom typescript transformations to an Angular project I found out that the ngx-build-plus project by Manfred Steyer supports plugins that allows you to add or manipulate plugins to the Webpack configuration. The plugin interface has methods for pre/post hooks and a method for the config manipulation.

export default {
  pre() {
  },

  // This hook is used to manipulate the webpack configuration
  config(cfg) {
    return cfg;
  },

  post() {
  }
};

 

Inside the webpack configuration that is passed to the config method you will find a webpack plugins array that (amongst others) will contain the AngularCompilerPlugin. The AngularCompilerPlugin has a _transformersproperty that contains all the typescript transformers that will be passed to the Typescript compiler.

import { dummyTransformer } from './ng-ts-dummy-transformer';
import { AngularCompilerPlugin } from '@ngtools/webpack';

function findAngularCompilerPlugin(webpackCfg): AngularCompilerPlugin | null {
  return webpackCfg.plugins.find(plugin =>  plugin instanceof AngularCompilerPlugin);
}

// The AngularCompilerPlugin has nog public API to add transformations, user private API _transformers instead.
function addTransformerToAngularCompilerPlugin(acp, transformer): void {
  acp._transformers = [transformer, ...acp._transformers];
}

export default {
  pre() {
    // This hook is not used in our example
  },

  // This hook is used to manipulate the webpack configuration
  config(cfg) {
    // Find the AngularCompilerPlugin in the webpack configuration
    const angularCompilerPlugin = findAngularCompilerPlugin(cfg);

    if (!angularCompilerPlugin) {
      console.error('Could not inject the typescript transformer: Webpack AngularCompilerPlugin not found');
      return;
    }

    addTransformerToAngularCompilerPlugin(angularCompilerPlugin, dummyTransformer);
    return cfg;
  },

  post() {
    // This hook is not used in our example
  }
};

 

For this example we create a dummy Typescript transformer that logs the filenames to the console.

import * as ts from 'typescript';
export const dummyTransformer = <T extends ts.Node>(context: ts.TransformationContext) => {
  return (rootNode: ts.SourceFile) => {
    console.log('Transforming file: ' + rootNode.fileName);
    function visit(node: ts.Node): ts.Node {
      return ts.visitEachChild(node, visit, context);
    }
    return ts.visitNode(rootNode, visit);
  };
};

 

First we need to compile the plugin, as it is written in typescript:

tsc --skipLibCheck --module umd -w

 

And now we can run a normal Angular CLI build with the plugin:

ng build --aot --plugin ~dist/out-tsc/ng-ts-register-transformer.js

 

Output looks like the following:

Console output when applying the dummy transformer

 

As you can see it outputs not only your sourcefiles, but also generated files like .ngfactory.ts and .ngstyle.ts files. This is due to the fact that the AOT compiler generates these files before the typescript transformers are executed. This might change when Ivy is introduced in Angular 8.0.

An example repository can be found at https://github.com/Joolnl/ng-ts-transformations.

 

References

  1. https://blog.angularindepth.com/do-you-know-how-angular-transforms-your-code-7943b9d32829
  2. https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943
  3. https://github.com/manfredsteyer/ngx-build-plus