Search This Blog

Sunday 12 November 2017

How to write a React app in TypeScript?

A tutorial and example code on how to write a React app in TypeScript, using Webpack.

If you just want to see the final code, you can get it here. Otherwise follow along, and I'll talk you through it!

Get Node

First install Node Package Manager (npm) if you do not already have it. Then create a new directory and run npm init.

NPM will ask you a series of questions interactively - where a default is provided, that's fine, and for others you can use your best judgement.

Install packages

We need to install some packages that our project will depend on.

Webpack

npm install --save webpack serve

Node.js is principally a JavaScript compiler than allows you to execute code written in JavaScript on your machine. Webpack then inverts this by using Node.js to bundle up JavaScript code you have written for later execution in a browser. It takes the many files that make an application (source code in various languages, images, fonts, CSS files) and bundles them into one or two files that can be deployed to a web server. More on this next!

Serve is a package that gives us a development server for testing our application.

Typescript

npm install --save typescript awesome-typescript-loader babel-core

Webpack looks at the files your source tree and bundles them into an output file (or files). By default, it just knows how to bundle JavaScript (.js) files. Via a plugin system, more file types can be supported. Plugins that enable Webpack to process different kinds of input files are called loaders. So here, we install both the Typescript compiler (which can be used standalone from Webpack) and a loader that will invoke the compiler appropriately from Webpack.

There are two main loaders for Typescript in Webpack 2, awesome typescript loader and ts-loader. I know the former best, but you may want to investigate the latter. I'll return to talk about Babel at the end.

Other loaders

npm install --save file-loader source-map-loader

These loaders give us the ability to just copy files from input to output (which we will need for our index.html file) and the ability to output a source map. If you're not familiar with source maps, it's an implementation detail that you don't need to know much about, but you can read more here.

React

npm install --save react react-dom
npm install --save @types/react @types/react-dom @types/node

In addition to just installing React, we also want to be able to write type-safe code using React. React, being written in JavaScript, doesn't have any static type information associated with it. The solution? Developers (or third parties) can publish Typescript type definition packages for existing JavaScript packages. These are named using the @types/XXX format. In general, when developing for Typescript under node, whenever you install package foo, you also need to install @types/foo.

Write the code!

This is the easy part. We need to create two files: An index.html file that will be served up to the user's browser, and a single React component, written in Typescript, to define what should be rendered. From this starting point you can build as complicated a React app as you like.

Create an empty src dir, and inside it an empty main dir. (The idea being that you could put tests in src/tests). Then, add these two files: index.html and index.tsx.

If you take a look at index.html, you can see it is very simple: It's just a div with the ID "react" and a script tag, plus some CSS to make things pretty. You may wonder where bundle.js is coming from - this is what webpack will compile all our Typescript code down to.

Next, index.tsx. Let's talk about the file extension. "ts" is the standard file extension for Typescript. React uses a preprocessor step to allow us to embed HTML (or rather XML) directly in our code. "tsx" is the extension for using this with Typescript, as jsx is for JavaScript.

Now let's take a look at this chunk:

ReactDOM.render(
    <App />,
    document.getElementById("react")
);

This is the entrypoint to the application. This says to find the div with ID "react" and begin rendering our react app there. What to render? The <App />; line says to render an instance of the App component, which is just above:

class App extends React.Component<undefined, undefined> {
    render() {
        return <div>Hello world from React, Webpack and TypeScript!</div>;
    }
}

Finally, we have this line:

require('file-loader?name=[name].[ext]!./index.html');

index.tsx will be our entry point for Webpack. From index.tsx it will crawl all referenced files and make sure they end up in the output. This line makes sure that index.html is registered as a dependency, and will get copied to the output.

Configure everything!

Nearly there! We need to configure Webpack and Typescript.

First, create a webpack.config.js file in the project root, like this one. Let me talk you through a few important points from that linked file. First of all entry: This defines the entrypoint for the app; as I mentioned at the end of the previous section, if there isn't a transitive relationship from this file to some other file in the project, through requires or imports, then that file won't be included in the final output.

Secondly, output. This is self-explanatory, I just want you to note that bundle.js matches up with the script tag in index.html.

Thirdly, loaders. This is what connects file types to loader plugins. We wire up *.ts and *.tsx files to awesome-typescript-loader, excluding the "node_modules" folder to make sure that we don't try and compile other peoples packages. We also enable the source-map-loader for *.js files. Note that this means that we will get source map support for Typescript, as the *.ts files are compiled to *.js.

We also need to create a file to configure the Typescript compiler further: tsconfig.json. Again, this should be fairly self-explanatory. Note that we are targeting ES6, and that we have told Typescript to compile everything in src - this will happen for every Typescript file, even if it isn't referenced by Webpack - the two systems don't seem to work that closely together.

Run it!

Edit package.json and edit scripts so that it looks like:


"scripts": {
    "start": "serve out",
    "webpack": "webpack",
    "watch": "webpack --watch",
    "test": "echo \"Error: no test specified\" && exit 1"
},

Then you can run npm run webpack to compile and bundle the code (or npm run watch to keep watching the files and recompile any time anything changes). Then run npm start to run a local development server hosting the files. Open your browser to see things all working! Hooray, hopefully! (If not, please see if your code is the same as in the repository, and give a me a bug report if you think I deserve one!)

Do check out the out folder Webpack has generated to get a better feel for how this all works.

Compatibility

One final tweak. Everything will be looking fine for you, because you are running with a modern browser, you tech-savvy developer you (and also because this app couldn't be simpler). But you may have problems when you get on to complex apps that need to run on old creaky browsers. Babel is a JavaScript transpiler that compiles from new shiny versions of JavaScript down to something that will run on your local hospital's ageing Windows machines.

We already installed the babel-core package, and enabled awesome-typescript-loader's integration with it in the Webpack config. The final step is to create a .babelrc file in the project root like so to tell Babel that we want to target ECMAScript 5. Now the pipeline is like this:

Typescript ―――――――――⟶ ES6 JavaScript ――――⟶ ES5 JavaScript ――――――⟶ bundle.js
                |                      |                      |
       Typescript Compiler           Babel             Webpack bundling

Given that we told Typescript to target ES6 earlier, couldn't we just tell it to target to ES5 and skip Babel? No, because then Typescript will restrict us to features in ES5. Whereas this approach gives us all the features of ES6, but compatibility with ES5.

All sorted!

I hope this has been helpful to you. Watch out for a future blog post (and in the mean time look at the master branch of the linked repository) on how to test a Typescript/Webpack/React app!