Caveats of processing TypeScript code with Babel
Tags: typescript, babel, jest
Published: 2020-02-24 14:03:47

---
title: Caveats of processing TypeScript code with Babel
published: true
description: This post is about differences between how TypeScript and Babel transform source code
tags: typescript, babel, jest
series: Speedup Jest test execution
---

**tl;dr** When writing classes/OOP in TypeScript [--strict](https://www.typescriptlang.org/docs/handbook/compiler-options.html) should definitely be enabled, or at minimum use `--strictPropertyInitialization` as it might save hours of debugging in scenarios where `tsc` is replaced with `babel`. Second thing I wish I knew is that **the order of plugins** in `babel.config.js` affects the output.

My initial goal was to decrease unit tests execution of cca. 4500 tests taking 340 seconds. First what came to my mind was to modify jest config  especially how the code is being transformed. 

The idea was to utilize `tsc` only for typechecking and `babel` for transpilation. Why? Because babel was already in place by applying the `emotion-plugin` and therefore doing the transpilation only via `babel` could shave some time off.

Obviously during the process of config upgrade & tuning I've hit a few snags which I'd like to share - as someone after reading might not run into similar problems.

## Changes in jest config

NOTE: there was a shared `babel.config.js` for both **unit** and **functional-browser** tests setup plus they each had s separate `jest` config

*BEFORE* 

1. **typechecked** and **compiled** `.tsx?` using `tsc` + `.jsx?` transpiled using `babel` see [ts-jest/presets/js-with-babel](https://kulshekhar.github.io/ts-jest/user/config/)

*AFTER*

1. **typechecked** in a separate npm script using `tsc --noEmit`
1. **transpiled** by `babel-jest` / `babel.config.js` with plugins processing typescript code

```javascript
  const presets = [
    ['@babel/preset-typescript', { allowNamespaces: true }],
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'entry',
        corejs: 3,
        modules: 'commonjs',
        loose: true,
      },
    ],
  ]
  const plugins = [
    'babel-plugin-const-enum',
    '@babel/plugin-proposal-optional-chaining',
    '@babel/plugin-proposal-nullish-coalescing-operator',
    ["@babel/plugin-proposal-class-properties", { loose: true }],
    ["@babel/plugin-transform-typescript", { allowNamespaces: true }],
  ];
```  

## Oops, there is an error

Everything seemed to be working fine at least for **unit tests** but life's though and the first error appeared during **functional-browser** tests run saying:

> TypeError: Cannot read property 'getElement' of undefined

That particular stack trace was pointing to one of the **Page Object** base classes in which a super-class instance property was accessed from ancestor.

## Process of investigation

My first hypothesis was that this error might be related to the compilation target setting, specifically with regard to how babel transpiles classes. By trying out a couple of different possible values for target, it quickly became apparent that that was not the case.

Next suspect in down the call stack were few `async/await` functions which turned in a "small sync test reproduction" this hypothesis out of play as well. 

Finally after putting explicit `console.log` into constructors all the way up the prototype chain as well as passing different init arguments, it  pointed me to the right direction. Also a good move was to remove TypeScript's class member declarations - that just confirmed the suspicion -that these `properties` with type annotation were incorrectly transpiled (the "how" will be explained in following section). 

> The final puzzle-piece was **found during verification of transpiled output** as it was clearly visible that the child's class constructor did override/initialized the parent's `property` with [void 0](https://stackoverflow.com/questions/1291942/what-does-javascriptvoid0-mean) 

## Root cause breakdown

A trivial example replicating the scenario - a parent class + a child class that relies on parent's property but it's `undefined` when transpiling with `babel`
![ts-source](https://dev-to-uploads.s3.amazonaws.com/i/1inu27du0kwo2cylnovw.png)

The output transpiled by babel

![babel-out](https://dev-to-uploads.s3.amazonaws.com/i/l42xs3bec0ab6nl4uc7e.png)

For comparison the same code compiled with TypeScript works just fine

![working-ts-example](https://dev-to-uploads.s3.amazonaws.com/i/7lggr62cn0w4o3mnfd6p.png)

Finally the culprit busted - incorrect plugin order

![babel-config-incorrect](https://dev-to-uploads.s3.amazonaws.com/i/ji8d4bto3dsour72xh06.png)

Confirmation that swapping the order of plugins produces different output

![babel-config-correct](https://dev-to-uploads.s3.amazonaws.com/i/ptmf0tb6gnklkr8y5434.png)


Finally the problem could be easily prevent with `strict` mode or `strictPropertyInitialization` enabled.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mxvkf7zrd8p7pfr5lh9e.png)


## Complexity reactor

> is a sequence of related and non-related events contributing individually or combined to raise overall complexity. 

* permutations
  * unit vs functional-browser tests
  * babel config - presets, plugins, settings, env, targets
* causation fallacy - thx [indiegate](https://twitter.com/indiegate)
  
    With `tsc` everything compiles fine, with `babel` a class instance property in prototype chain is `undefined` AND therefore the different compiler implementation `typescript` -> `babel` is the cause.

    But the real cause was a **wrong order of plugins in babel config** FACT: one of the plugins isn't TypeScript related at all. FACT: with a strict TypeScript config / [Strict Property Initialization](https://mariusschulz.com/blog/strict-property-initialization-in-typescript) this error would not occur in first place. 

* dead-end exploration
  * suspect: constructor augmentation using 3rd party mixin
* tedious debugging (try/catch approach), long turn-arounds 
* OOP / multi-level inheritance used in Page Object implementation
* ES6 classes/this/super()
* non-strict `tsconfig.json` mode / important flags not enabled
* problems where ordering matters see [Typescript plugin overwrites parent class properties when the child narrows that property's type](https://github.com/babel/babel/issues/9105#issuecomment-443820095)

## Conclusions

* over raw OOP in JS/TS prefer a solution with more explicit semantic e.g. [github.com/stampit-org/stampit](https://github.com/stampit-org/stampit) -> [Classes vs. Stamps](https://stampit.js.org/#differences-from-classes)

* when debugging transpiler issues it's good to start with the simplest possible test scenario involving as few dependencies as possible. Otherwise tracing errors in the "real" setup is way more time consuming and contains a lot "moving parts" that can lead to dead end.

* verify quickly the transpilation output using online tools like [www.typescriptlang.org/play](https://www.typescriptlang.org/play/) or [babeljs.io/repl](https://babeljs.io/repl) - see [not working scenario](https://babeljs.io/repl#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=MYGwhgzhAECCB2BLAtmE0DeAoa15mQFMAuaCAFwCdF4BzAbh2jFpLwFdkAjQyxp4AHt4FSu2DlBlABQY8BQgBpmraAF9Sc_EVKiaDFW3iceldQEpMTXOQAWiCADpthaAF55RRrhv2nLVw8A73UsNSwsUEgYABFBWmhCAA9yQngAExgEFDQrXBddKn1-XCERKnFJGXT4zU82PTp6Q1Jjbl4LPJ8IdgAHXmka2nMQ8KYuMEoAa2lLbB9oMohBEEJHEHjpAAMAdXYAMwBCaABJAHJkaAASDDsHZwU1LZGmcLGl8mhKZPc8QgB3aBxWiyeqkABE3yS4OUAVIAGYLPwlis1hsQVDHBNprMRkA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ctypescript%2Cenv&prettier=false&targets=Node-10&version=7.8.4&externalPlugins=%40babel%2Fplugin-proposal-class-properties%407.8.3%2C%40babel%2Fplugin-transform-typescript%407.8.3) and [working scenario](https://babeljs.io/repl#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=MYGwhgzhAECCB2BLAtmE0DeAoa15mQFMAuaCAFwCdF4BzAbh2jFpLwFdkAjQyxp4AHt4FSu2DlBlABQY8BQgBpmraAF9Sc_EVKiaDFW3iceldQEpMTXOQAWiCADpthaAF55RRrhv2nLVw8A73UsNSwsUEgYABFBWmhCAA9yQngAExgEFDQrXBddKn1-XCERKnFJGXT4zU82PTp6Q1Jjbl4LPJ8IdgAHXmka2nMQ8KYuMEoAa2lLbB9oMohBEEJHEHjpAAMAdXYAMwBCaABJAHJkaAASDDsHZwU1LZGmcLGl8mhKZPc8QgB3aBxWiyeqkABE3yS4OUAVIAGYLPwlis1hsQVDHBNprMRkA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ctypescript%2Cenv&prettier=false&targets=Node-10&version=7.8.4&externalPlugins=%40babel%2Fplugin-transform-typescript%407.8.3%2C%40babel%2Fplugin-proposal-class-properties%407.8.3)