Using TypeScript with React and Webpack


I was following the React & Webpack TypeScript guide but found it to be outdated and broken.

Hence, I documented my own guide in wiring up TypeScript with React and webpack.

Project Layout

First, create a directory for your project:

$ mkdir react-webpack
$ cd react-webpack

Install the dependencies (I’m using yarn but you can use npm):

$ yarn add [email protected] \
           webpack-cli \
           react \
           react-dom \
           @types/react \
           @types/react-dom \
           typescript \
           [email protected]

Note: If you’re using [email protected] (and not [email protected] with webpack-cli), then you’ll need to use [email protected].

The dependencies can be described as follows:

TypeScript Configuration

Create a TypeScript configuration file:

$ touch tsconfig.json

And add the following:

{
  "compilerOptions": {
    "jsx": "react",
    "lib": ["dom", "es2015"],
    "module": "commonjs",
    "noImplicitAny": true,
    "outDir": "./dist/",
    "sourceMap": true,
    "target": "es5"
  },
  "include": ["./src/**/*"]
}

Options

Let’s go over each option:

  • compilerOptions: the compiler options
    • jsx: react: supports JSX in .tsx files
    • lib: includes a list of library files in the compilation (specifying es2015 allows you to use ES6 syntax)
    • module: specifies the module code generation
    • noImplicitAny: raises errors on expressions and declarations with an implied any type (to enable all strict type checking options, use strict instead)
    • outDir: the output directory
    • sourceMap: generates .map, which is useful for debugging
    • target: the target ECMAScript version to transpile down to (pick the version that supports your browser requirements)
    • include: the files to be included (there’s also exclude)

First Component

Let’s create our first component:

$ mkdir -p src/App/
$ touch src/App/index.tsx

And add the code:

// src/App/index.tsx
import * as React from 'react';

interface Props {
  name: string;
}

interface State {}

export default class App extends React.Component<Props, State> {
  render() {
    return <h1>Hello {this.props.name}</h1>;
  }
}

Why are we using import * as React from 'react' instead of import React from 'react'?

Because react uses CommonJS syntax (module.exports = ...) and not ES Module syntax (export default ...).

If you use import React from 'react', you’ll receive the TypeScript compiler error:

$ yarn tsc
error TS1192: Module '"react"' has no default export.

Note: However, if you really want to use import React from 'react', you can enable allowSyntheticDefaultImports in the config:

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    // ...
  }
}

Render App

Let’s create our entry file:

$ touch src/index.tsx

And add the code:

// src/index.tsx
import * as React from 'react';
import { render } from 'react-dom';
import App from './App';

render(<App name="world" />, document.getElementById('app'));

Then create the HTML file:

$ mkdir public
$ touch public/index.html

And add the markup:

<!-- public/index.html -->
<div id="app"></div>
<script src="../dist/bundle.js"></script>

Configure Webpack

Let’s create our webpack configuration file:

$ touch webpack.config.js

And add the following:

// webpack.config.js
const { resolve } = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.tsx',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, './dist/'),
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'awesome-typescript-loader',
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json'],
  },
  devtool: 'source-map',
};

On a high-level, this configuration file tells webpack to start at the entry file, resolve every require/import, and then output a bundle.

When webpack sees a file with the extension .ts or .tsx, it transpiles the file contents using the typescript loader.

Build Bundle

To build the bundle, run:

$ yarn webpack

Open App

To open the app in a browser, run:

$ open public/index.html

Conclusion

You can find the tutorial code here.