Hot loading React Components in TypeScript

Olav Nybø

Hot loading of React Redux components makes for a absolutly fantastic developer experience. The ability to get immediately feedback in the browser when any part of my javascript application is changed and saved is priceless.

It is simply awesome!

I have been using Angular for a long time, but watching Dan Abramovs Live React: Hot Reloading with Time Travel at react-europe 2015 got me sold on React and Redux.
The wonderful functional simplicity of Redux really makes this alternative shine. Combined with the Webpack bundler which in many ways is the glue that binds all the tools together we have a complete and very efficient platform.

Still, I am a believer in strong typing and I love that the compiler helps me and guide me towards writing correct code. Be it in C#, F# or in TypeScript. Sure there is Facebook Flow which may be closer to the React world (both being developed by Facebook), but being both a .Net developer and a frontend developer I am used to and like a lot of Microsoft products. In particular I like writing my javascript as TypeScript and I really like the Visual Studio Code Editor.

Most examples that I could find where written in ECMAScript 2015 (ES6) using Babel to transpile to ES5.
But I wanted to use TypeScript and as TypeScript has supported compiling JSX since version 1.6 from september 2015 I figured this should be easy. This blogpost by the author of the Webpack loader ts-loader James Brantly also led me to believe that this should be easy.
And yes it is easy, but there are quite many moving parts involved and it is easy to make mistakes, well at least I did make quite a few before succeeding so here follows my write up of the steps to get it all working.

The complete sample project can be found on github here

Node and npm

The first thing we need is to get node and npm installed.

Node and chocolatey

Get the latest version of Node, I used version 5.2.0.
If you already have node installed you can check your version by typing on the command prompt:
node -v

To install node I prefer to use chocolatey
choco install nodejs.install

npm

Then check your version of npm using:
npm -v

This should be at least version 3.5.2.
To update (may have to use sudo if you are on osx or linux):
npm install npm -g

Now you could either clone my demo app on github go to the cloned webpack-react-typescript-demo folder and type:
npm install

This will install all the packages listed in the packages.json folder.
If you would like to start from scratch and try for yourself the you should create an empty package.json file by issuing:
mkdir <folder>
cd <folder>
npm init -y

TypeScript

If you haven't got the typescript compiler installed now is a good time to do so.
Install it both globally:
npm install typescript -g
and into your projects dev dependencies:
npm install typescript --save-dev Make sure that you have at least 1.6, I had version 1.7.3 at the time of writing.
Check the version using:
tsc -v
Note that if you have had Visual Studio on your machine for some time it may have added "C:\Program Files (x86)\Microsoft SDKs\1.0" to your path which could cause problems. If this is the case change your path and remember to restart your command prompt.

TypeScript definition manager (tsd)

Next we want type definition files (typescript definitions) for react. The best way to get these are using the typescript definition manager node application.
So we start installing that first.
npm install tsd -g
npm install tsd --save-dev

Then we install the typedefinitions:
tsd init
tsd install react react-dom --save

The tsd init creates the tsd.json file that contains the typings that has been installed. The tsd install ... downloads the typings for the two react modules that we are going to use, make sure to add the --save switch to persist the changes to your tsd.json file.

TypeScript project file (tsconfig.json)

The tsconfig.json is a very simple project file for TypeScript projects. It contains the compiler settings that you want to use and it can list the files that you want to include in the TypeScript compilation.

{
  "compilerOptions": {
    "target": "es6", // we compile to ES6
    "module": "es6", // this is not really needed, es6 modules are the default for es6 target
    "sourceMap": true, // create sourceMaps
    "jsx": "react"  // important for JSX support
},
//if files list is empty, then all files are included. 
// If we list files, only those files are included.
"files": [ 
  "app/index.ts",
  "app/components/App.tsx",    
  "app/components/Item.tsx",
  "app/components/ItemList.tsx",
  "typings/tsd.d.ts"
]
}

The example above shows the contents of the tsconfig.json file included in my demo repo. Your compilerOptions should be as mine, but your files section will of course depend on the files in your project.

Note that we select target for the compiler to be ES6. This is because we want to use the full ES6 syntax for importing modules, so we transpile in two steps.
First we use typescript to do strong typing and converting JSX to ES6. Before we can use this in current browsers we convert it to ES5 using babel. The Babel will insert a compatibility layer as described here. If we go directly from TypeScript to ES5 we will not get that layer and imports of the default module of this type will fail:
import something from './something'

React components

Now we are ready to start writing some react components in TypeScript.

import * as React from 'react'; 
import {default as Item, ItemProps} from './Item';

export interface ItemListProps {
  items: ItemProps[];
}

export default class ItemList extends React.Component<ItemListProps, any> {
    constructor(props: ItemListProps) {
      super(props);
    } 
     render() {      
        var items = this.props.items.map(function(item) {
               return (
                 <Item name={item.name} />
               );
         });        
         return (<div>{items}</div>);
     }
 }

As we can see we can use ES6 import module syntax which I think is quite nice.
And we have strongly typed react components using TypeScript generics. As Visual Studio Code understands both the JSX and the typescript we get immediately feedback in the editor if we misspell a React component attribute which all makes for a very good developer experience.
Just note that if the file contains any JSX then the file extension of the file should be tsx so that Visual Studio Code understands that this is in fact a file that contains JSX. Otherwise you will get red squiggles on the JSX markup.

I like that the top level file is a empty as possible, because as we will see when we introduce the hot loader the root level module can't be reloaded. I also prefer to keep this file as a regular TypeScript file (not tsx), but this can easily be done by using the regular javascript syntax to include the top level node as shown here:

import * as React from 'react'; 
import * as ReactDOM from 'react-dom';

import App from './components/App';

ReactDOM.render(React.createElement(App),document.getElementById('content'));

Webpack

Webpack is a module loader for frontend javascript that has support for loading all kinds of web assets. It can be used to bundle, minify and load all that is needed for a web application. In addition to javascript modules it can be used to load images, fonts, css and probably more.
The features I will focus on here is how to configure it for hot loading of modules and react typescript code.

Hot loading

When we are using the web pack dev server with Hot loading enabled, webpack will monitor our source files and detect when we change a file and save it. When a change is detected, webpack will compile just that part and trigger a refresh of just that part of the page that is affected. If we use redux and stateless react components this will even make it possible to do live updates of our page without loosing the state on the page.
Lets look at how this is configured.

webpack.config.js

This file is used to configure Webpack.

module.exports = {
  entry: [
    './app/index.ts',
    'webpack-dev-server/client?http://localhost:8080',
    'webpack/hot/only-dev-server'
  ]),
  output: {
    publicPath: 'http://localhost:8080/',
    filename: 'dist/bundle.js'
  },
  devtool: 'source-map',
  ....

The configuration starts with a module.exports object that contains an entry. The entry is the entry of the application which in this case is the index.ts file in the app folder.
The two additional items in the entry array are two special "files" which are required for hot loading. They should not be included in the production build (see the github repo for an example of how we can exclude these two from production).
The next section is the output where we list the URL of the development server and the filename of the bundled output. This bundle.js should be included in our index.html file, it will contain all the required assets.
devtool is where we can choose if we want source-maps to be generated or not. Choose devtool: 'eval' for faster build time.

webpack.config.js - loaders

Loaders are plugins to webpack that can load different types of assets.
The three that I will talk about here are

  • react-hot : hot loader module
  • babel : babel transpiler module
  • ts-loader: typescript loader

webpack.config.js file continued from last section:

module: {
      preLoaders: [
        {
            test: /\.tsx?$/,
            exclude: /(node_modules)/,
            loader: 'source-map'
        }
      ],
      loaders: [
        {
            test: /\.tsx?$/,
            exclude: /(node_modules)/,
            loaders: [
                'react-hot',
                'babel?presets[]=es2015',                  
                'ts-loader'
            ]
        }
    ]
  },
    resolve: {
      extensions: ["", ".webpack.js", ".web.js", ".js", ".ts", ".tsx"]
}

Each loader in the loaders array should have a test property that contains a regex pattern to specify which files to include. /\.tsx?$/ includes files that have a .ts or .tsx extension.

We can also exclude files. We don't want to include the files in the node_modules in the typescript compilation so we exclude those.

Then the loaders array lists the loaders that should be applied for the included files. The loaders are listed in reverse order. That is; we should first run typescript to convert to es6, then babel to convert to es5 and finally we should hot load the result.
The ? after babel is called a query. It is used to configure the loader. In this case we specify that we want to use a preset that contains transforms needed by babel to transpile es2015 code. Note that in some tutorials this is shown as stage=0 which was the syntax for the same thing in older versions of babel. There is also a preset called 'react' that often is used with babel when doing react development, but we don't need it as typescript is doing the JSX transpilation.

The final part is the resolve.extensions array. This is very important to add when using typescript. This array specifies which extensions that webpack should include. The default is ["", ".webpack.js", ".web.js", ".js"] which means that webpack will ignore your .ts and .tsx files. Remember to add these to the resolve extensions array to include them.

And that's it. Don't be intimidated by this rather lengthy blogpost. If you look at the github repo there isn't really a lot of stuff, the length of this post is much more a result of that I wanted to be detailed as that is what I wished for myself when I first struggled to get this working. A post that included all the details to get started with Webpack, React, Hot-loading and TypeScript