Migrating a JavaScript project to TypeScript using webpack

  • Post by Nicolas Ramz
  • Sep 24, 2018
Migrating a JavaScript project to TypeScript using webpack

While porting a little project from JavaScript to TypeScript I scratched my head on a lot of small problems. I wrote this article so that you don’t scratch yours ;)

Prerequisite

An existing JavaScript project that’s already using webpack.

Make webpack work with TypeScript

In order for webpack to build a TypeScript project, you have several things to do to make it load and resolve .ts files.

First you need a typescript loader that can be installed using:

npm install -D awesome-typescript-loader

You could also have installed ts-loader.

Then we have to add a new rule for .ts files in webpack.config.js:

 rules: [
      { test: /\.(t|j)sx?$/, use: { loader: 'awesome-typescript-loader' } },
  ]

This will tell webpack to use the newly installed loader for any .ts files.

Let’s try to run webpack:

ERROR in ./js/index.ts
Module not found: Error: Can't resolve './scenes/scene1' in '/Volumes/HDD2/Docs/Dev/samples-ts/js'
 @ ./js/index.ts 4:15-41
 @ multi ./js/index.ts

Not there yet :/

By default, webpack only resolves .js files. In order for it to correctly resolve .ts files, you also need to add the following to your webpack.config.js:

resolve: {
    extensions: ['.ts', '.js']
}

Outdated webpack version

The following error can happen:

Error: It looks like you're using an old webpack version without hooks support.
If you're using awesome-script-loader with React storybooks
consider upgrading @storybook/react to at least version 4.0.0-alpha.3

This one is a bit misleading as I wasn’t using any React or jsx files, but it’s easy to fix. As suggested, updating webpack to version 4.x fixed the problem (I was using webpack 2.3.3).

TypeScript libs

You may have this error:

ERROR in [at-loader] ./js/athenajs/@types/athenajs/index.d.ts:6:40
    TS2304: Cannot find name 'HTMLElement'.

TypeScript can compile code that will run in a variety of environments: from Node.js, to the browser. By default, TypeScript doesn’t have any DOM-related global names.

In order to do that you have to tell him which lib you want to use using the “lib” configuration entry in your tsconfig.json:

    "lib": [
        "dom"
     ]

TypeScript will then load the types description file found here which will make it understand every dom-related global names and that’s one less error!

ERROR in [at-loader] ./js/scenes/scene2.ts:71:27
    TS2304: Cannot find name 'Math'.

Again, it’s standard ES5 JavaScript that’s not by default included by TypeScript. In order to have TypeScript recognize these ES5 global names you have to tell it to include the “es5” lib in your tsconfig.json file:

    "lib": [
        "es5"
     ]

For a list of standard libs that TypeScript can use, check this page.

Complete examples

Here are the configuration files I am using:

webpack.config.js
var path = require('path'),
    WebpackNotifierPlugin = require('webpack-notifier');

module.exports = {
    entry: [
        'webpack-dev-server/client?http://127.0.0.1:8888',
        './src/index.ts'
    ],
    output: {
        path: __dirname,
        filename: "bundle.js",
        pathinfo: true
    },
    devtool: 'source-map',
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.(t|j)sx?$/,
                use: {
                    loader: 'awesome-typescript-loader'
                }
            },
            {
                test: /\.ts$/,
                use: ["source-map-loader"],
                enforce: "pre"
            }
        ]
    },
    devServer: {
        host: '0.0.0.0',
        port: '8888',
        // inline hot-reload
        inline: true
    },
    resolve: {
        modules: [
            'node_modules'
        ],
        extensions: [
            '.ts', '.js'
        ]

    },
    plugins: [
        new WebpackNotifierPlugin({
            alwaysNotify: true,
            skipFirstNotification: true,
            title: 'AthenaJS-Samples'
        })
    ]
};
tsconfig.json
{
  "compilerOptions": {
    "outDir": "./built",
    "noImplicitAny": true,
    "strictNullChecks": true,
    "target": "es5",
    "sourceMap": false,
    "allowJs": true,
    "lib": [
        "es5",
        "es2015.iterable",
        "dom"
    ]
  },
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}