DEV Community

Cover image for EsNext features in TypeScript with Babel
Franciszek Krasnowski
Franciszek Krasnowski

Posted on

EsNext features in TypeScript with Babel

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 post or cloning this repo and setting our Babel/TypeScript environment I'll install @babel/plugin-proposal-pipeline-operator plugin:

npm i --save-dev @babel/plugin-proposal-pipeline-operator
Enter fullscreen mode Exit fullscreen mode

and add it to .babelrc:

{
  "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" }]
  ]
}
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

The compilation with Babel goes successfully 🥳; however, TypeScript got a couple of errors:

src/index.ts:5:59 - error TS1109: Expression expected.
...
Enter fullscreen mode Exit fullscreen mode

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:

npm i --save-dev @babel/plugin-syntax-class-properties @babel/plugin-syntax-object-rest-spread @babel/plugin-syntax-typescript
Enter fullscreen mode Exit fullscreen mode

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

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

"build:babel": "babel ./src --out-dir lib --extensions \".ts,.tsx\"",
Enter fullscreen mode Exit fullscreen mode

and type in terminal:

npm run build:babel
Enter fullscreen mode Exit fullscreen mode

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

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)
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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}`)
    })
}
Enter fullscreen mode Exit fullscreen mode

Save it as rename.js then run:

node rename lib
Enter fullscreen mode Exit fullscreen mode

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

Time for TypeScript to rock!

Change your tsconfing.json to match this:

{
  "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"]
}
Enter fullscreen mode Exit fullscreen mode

Run

tsc
Enter fullscreen mode Exit fullscreen mode

TypeScript processed file successfully!

Linting

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

npm i --save-dev eslint babel-eslint @babel/eslint-plugin
Enter fullscreen mode Exit fullscreen mode

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

{
  "parser": "babel-eslint"
}
Enter fullscreen mode Exit fullscreen mode

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

npm i --save-dev @typescript-eslint/eslint-plugin
Enter fullscreen mode Exit fullscreen mode

And update .eslintrc:

{
  "parser": "babel-eslint",
  "plugins": ["@babel"],
  "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended"]
}
Enter fullscreen mode Exit fullscreen mode

Putting it all together

Make sure to have script for everything in package.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"
  },
Enter fullscreen mode Exit fullscreen mode

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:

{
  // This makes ts files are recognized as javascript ones:
  "files.associations": {
    "*.ts": "javascript"
  },
  "javascript.validate.enable": false
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "overrides": [
    {
      "files": "*.ts",
      "options": {
        "parser": "babel"
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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

Editor Support

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:

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);
Enter fullscreen mode Exit fullscreen mode

but that's only for non-typescript syntax.

What other features do you miss in JavaScript/TypeScript?

Discussion (0)