Custom Transform (Plugin ) System

Why (Usage)

at this time (2020) the only way to pass compile time type information to run time (e.g. https://github.com/kimamula/ts-transformer-keys)

learn how tool chain works under the hood

"manual code review -> compile time transformation"

How it works

Compiler parse source code into AST (abstract syntax tree)

Transform manipulates AST

Then AST is written to target files ("emitted"), i.e. javascript

AST (Abstract Syntax Tree)

AST Viewer: https://ts-ast-viewer.com/#

This is a very useful tool, paste typescript code, get AST and code to generate that tree

Boilerplate & Examples

Boilerplate with visitor


import * as ts from ‘typescript’
export default function(/*opts?: Opts*/) {
  function visitor(ctx: ts.TransformationContext, sf: ts.SourceFile) {
    const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult => {
      // here we can check each node and potentially return 
      // new nodes if we want to leave the node as is, and 
      // continue searching through child nodes:
      return ts.visitEachChild(node, visitor, ctx)
    }
    return visitor
  }
  return (ctx: ts.TransformationContext): ts.Transformer => {
    return (sf: ts.SourceFile) => ts.visitNode(sf, visitor(ctx, sf))
  }
}

AST node replacement

const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult => {
  if (ts.isCallExpression(node) &&
      node.expression.getText(sf) == 'safely') {
    // get the argument to safely
    const target = node.arguments[0]
    // check to make sure it is a property access, like "a.b"
    if (ts.isPropertyAccessExpression(target) {
      // return a binary expression with a && a.b
      return ts.createBinary(
        target.expression, // the left hand operand is the object
        ts.SyntaxKind.AmpersandAmpersandToken, // the && operator
        target) // the right hand operand is the full expression
    }
  }
  // otherwise continue visiting all the nodes
  return ts.visitEachChild(node, visitor, ctx)
}


https://github.com/microsoft/TypeScript/issues/14419#issuecomment-298814505


Using Transform

At the moment (2020 July) transformers still cannot be invoked through tsconfig.conf. Have to use programatically.

Alternatives:

  • https://github.com/TypeStrong/ts-loader for Webpack
  • https://github.com/TypeStrong/ts-node for REPL
  • https://github.com/cevek/ttypescript for tsc replacement
  • Write your own compiler wrapper

Write own wrapper:

Compiler API: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API

Read this: https://levelup.gitconnected.com/writing-typescript-custom-ast-transformer-part-1-7585d6916819


Patterns of Transform

Use a Visitor to visit each node (see boilerplate)

Screen the nodes to find the one we want to transform.

To manipulate:

  • Return a replacement node

Write own