EsNext features in TypeScript with Babel
Tags: typescript, babel, esnext, tutorial
Published: 2021-08-26 15:11:00

_TypeScript_ is a statically typed language built on top of _JavaScript_. It works basically like a type layer for JS - it does not introduce new features to the JS language besides types itself and implementing few syntax proposals. But what if we could extend it further? Would you like to add some custom syntax to the _TypeScript_? I do. _Babel_ already allows us to spice up _JavaScript_ with syntax plugins. Let's try to do the same with TypeScript

## Let's do some tests

After reading [this](https://devblogs.microsoft.com/typescript/typescript-and-babel-7/) post or cloning [this](https://github.com/microsoft/TypeScript-Babel-Starter) repo and setting our _Babel/TypeScript_ environment I'll install `@babel/plugin-proposal-pipeline-operator` plugin:

```sh
npm i --save-dev @babel/plugin-proposal-pipeline-operator
```

and add it to `.babelrc`:

```json
{
  "presets": ["@babel/env", "@babel/preset-typescript"],
  "plugins": [
    "@babel/proposal-class-properties",
    "@babel/proposal-object-rest-spread",
    // add this plugin:
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]
  ]
}
```

And we are ready to write some _TypeScript_ code with our fresh `pipeline operator`:

```ts
const triple = (str: string): string => str + str + str;
const kebabify = (str: string): string => str.split('').join('-');
const addQuotes = (str: string): string => `"${str}"`;

export const makeDelicous = (str: string): string => str |> triple |> kebabify |> addQuotes;
```

The compilation with _Babel_ goes successfully 🥳; however, _TypeScript_ got a couple of errors:

```r
src/index.ts:5:59 - error TS1109: Expression expected.
...
```

## The holy moly

So what? Cannot we use the syntax plugin along with type checking goodness and emitting declaration files?...

## Begin again

The key to solving this issue is to change the approach. Instead of compiling source files with _Babel_ down to the desired _ES_ version. We'll use syntax-only plugins:

```sh
npm i --save-dev @babel/plugin-syntax-class-properties @babel/plugin-syntax-object-rest-spread @babel/plugin-syntax-typescript
```

> Those plugins do not `compile` the syntax. They just `parse` it.

Add the following command to `scripts` in `.package.json`:

```json
"build:babel": "babel ./src --out-dir lib --extensions \".ts,.tsx\"",
```

and type in terminal:

```sh
npm run build:babel
```

After this struggle, output `lib/index.js` looks like this:

```ts
const triple = (str: string): string => str + str + str
const kebabify = (str: string): string => str.split('').join('-')
const addQuotes = (str: string): string => `"${str}"`

export const makeDelicous = (str: string): string => {
  var _ref, _ref2, _str

  return (
    (_ref = ((_ref2 = ((_str = str), triple(_str))), kebabify(_ref2))),
    addQuotes(_ref)
  )
}
```

Yes, it's perfectly valid _TypeScript_!

> As of the time of writing this post _TypeScript_ compiler does not support compilation of `.js` files so we'll need a little hack

## Little hack

To allow _TypeScript_ to do what it meant to. We need to change the extension of our output files from `.js` to `.ts`. So we'll write a simple _Node_ script:

```js
const fs = require('fs')
const path = require('path')

// An argument passed when calling script
const dir = process.argv[2]
if (!dir) throw Error('No directory specified!')
const dirPath = path.join('./', dir)

fs.readdir(dirPath, (err, files) => {
  if (err) throw err
  files.forEach(file => rename(path.join(dirPath, file)))
})

const rename = file => {
  // Matches filenames ending with '.js'
  if (file.match(/.*\.js$/))
    fs.rename(file, `${file.slice(0, -2)}ts`, err => {
      if (err) throw err
      console.log(`Renamed ${file}`)
    })
}
```

Save it as `rename.js` then run:

```sh
node rename lib
```

You should see file extension of `/lib/index.js` changed to `.ts`

## Time for TypeScript to rock!

Change your `tsconfing.json` to match this:

```json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["ESNext"],
    // Search under node_modules for non-relative imports.
    "moduleResolution": "node",
    // Enable strictest settings like strictNullChecks & noImplicitAny.
    "strict": true,
    // Disallow features that require cross-file information for emit.
    "esModuleInterop": true,
    // Specify output directory to /dist
    "outDir": "dist",
    "declaration": true
  },
  // Process files from /lib
  "include": ["lib"]
}
```

Run

```sh
tsc
```

_TypeScript_ processed file successfully!

## Linting

You can use _ESlint_ to **lint** `ts` files. Install `eslint`, `babel-eslint` and `@babel/eslint-plugin`:

```sh
npm i --save-dev eslint babel-eslint @babel/eslint-plugin
```

Create a simple `.eslintrc` file in the project root directory:

```json
{
  "parser": "babel-eslint"
}
```

Additionaly you can install `@typescript-eslint/eslint-plugin` for _TypeScript_ specifc rules:

```sh
npm i --save-dev @typescript-eslint/eslint-plugin
```

And update `.eslintrc`:

```json
{
  "parser": "babel-eslint",
  "plugins": ["@babel"],
  "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended"]
}
```

## Putting it all together

Make sure to have `script` for everything in `package.json`:

```json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint --ext .ts,.js src/",
    "build:babel": "babel ./src --out-dir lib --extensions \".ts,.tsx\"",
    "build:ts": "tsc",
    "build": "npm run lint & npm run build:babel && node rename lib && npm run build:ts"
  },
```

## Editor support

That's clear that editor support for such a quirk is poor out of the box. If you're using _VSCode_ and wanna get rid of typescript errors; create `.vscode` directory on the top of your project and put `settings.json` file in with the following content:

```json
{
  // This makes ts files are recognized as javascript ones:
  "files.associations": {
    "*.ts": "javascript"
  },
  "javascript.validate.enable": false
}
```

Use _ESlint_ plugin To highlight linting errors. For formatting, I use _Prettier_ plugin. _Prettier_ needs a little configuration to work properly in this circumstance. That is the `.prettierrc` file:

```json
{
  "overrides": [
    {
      "files": "*.ts",
      "options": {
        "parser": "babel"
      }
    }
  ]
}
```

Now _Prettier_ will know how to parse our insane `ts` files. Time to enjoy:

![Editor Support](https://dev-to-uploads.s3.amazonaws.com/i/r8nsy3ooxf0ugaar7zha.gif)

## Caveats and sum up

I don't find it really useful; therefore, there's such a strange, almost exotic feeling about this. It's like breaking some laws in exchange for little dirty pleasure. The _type-check_ errors ain't pretty neat, cause they show us code snippets pre-compiled with _Babel_ for example:

```r
lib/index.ts:10:3 - error TS2322: Type 'string' is not assignable to type 'number'.

10   return _ref = (_ref2 = (_str = str, triple(_str)), kebabify(_ref2)), addQuotes(_ref);
```

but that's only for _non-typescript_ syntax. 

What other features do you miss in _JavaScript/TypeScript_?