I've recently been getting into TypeScript following a lot of positive blogs about it from
Tom Dale
and others. Today I'll show you how I've set up a TypeScript project from scratch that uses React, and Webpack for managing the build process. I'll also discuss my initial impressions of TypeScript and in particular working with TypeScript and ReactJS.
I won't be going into detail on the specifics of TypeScript's syntax, but you can read either the
TypeScript handbook
or the free book
TypeScript Deep Dive
which will also give you a great introduction to the language.
The first thing to do was install TypeScript locally into my
node_modules
directory, which I did using Yarn, first using
yarn init
to create a new project:
yarn init
yarn add typescript
When you install TypeScript you get the
tsc
command line tool which can compile TypeScript but also create a starting
tsconfig.json
for you to edit. You can get this by running
tsc --init
- if you've installed TypeScript locally you'll need to run
./node_modules/.bin/tsc --init
.
Note:
I have the
./node_modules/.bin
directory on my
$PATH
,
which you can find in my dotfiles
. This is
slightly
risky, as I could accidentally run any executable that's in that directory, but I'm willing to take that risk because I know what's installed locally and it saves a lot of typing!
tsc --init
generates a
tsconfig.json
which is where all the config for TypeScript's compiler lives. There's a few changes I've made to the default config, and the one I'm using is below:
{ "compilerOptions":{ "module":"es6",// use ES2015 modules "target":"es6",// compile to ES2015 (Babel will do the rest) "allowSyntheticDefaultImports":true,// see below "baseUrl":"src",// enables you to import relative to this folder "sourceMap":true,// make TypeScript generate sourcemaps "outDir":"ts-build",// output directory to build to (irrelevant because we use Webpack most of the time) "jsx":"preserve",// enable JSX mode, but "preserve" tells TypeScript to not transform it (we'll use Babel) "strict":true, }, "exclude":[ "node_modules"// don't run on any code in the node_modules directory ] }
allowSyntheticDefaultImports
This rule allows you to use ES2015 style default imports even when the code you're importing doesn't have an ES2015 default export.
This happens when you import, for example, React, whose code is not written in ES2015 (the source code is, but React ships a built version). This means that it technically doesn't have an ES2015 default export, so TypeScript will tell you so when you import it. However, build tools like Webpack are able to import the right thing, so I turn this option on because I prefer
import React from 'react'
over
import * as React from 'react'
.
strict
: true
TypeScript version 2.3
introduced a new config option,
strict
. When turned on this configures TypeScript's compiler to be as strict as possible - this might not be what you want if you're porting some JS to TS, but for new projects it makes sense to be as strict as possible out of the box. This turns on a few different settings, the most notable of which are
noImplicitAny
and
strictNullChecks
:
noImplicitAny
Often when you want to add TypeScript to an existing project TypeScript makes it easy by not throwing an error when you don't declare the types of variables. However, when I'm creating a new TypeScript project from scratch I'd like the compiler to be as strict as possible.
One of the things TypeScript does by default is implicitly add the
any
type to variables.
any
is effectively an escape hatch in TypeScript to say "don't type-check this, it can be any value". That's useful when you're porting JavaScript, but it's better to be strict when you can. With this setting set to
true
, you can't miss any declarations. For example, this code will error when
noImplicitAny
is set to
true
:
This is another option that makes TypeScript's compiler stricter. The TypeScript Deep Dive book has a
great section on this option
. With this option on, TypeScript will spot more occasions where you're referencing a value that might be undefined, it will error at you. For example:
person.age.increment();
With
strictNullChecks
, if TypeScript thinks that
person
or
person.age
might be
undefined
, it will error and make sure you deal with it. This prevents runtime errors so it seems like a pretty good option to enable from the get go.
Setting up Webpack, Babel and TypeScript
I'm a big Webpack fan; I enjoy the ecosystem of plugins available, I like the developer workflow and it's good at managing complex applications and their build pipeline. Therefore, even though we could just use TypeScript's compiler, I'd still like to add Webpack in. We'll also need Babel because the TypeScript compiler is going to output ES2015 + React for us, so we'll get Babel to do the work. Let's install Webpack, Babel and the relevant presets, along with
ts-loader
, the Webpack plugin for TypeScript.
There is also
awesome-typescript-loader
, but I found
ts-loader
first and so far it's been great. I would love to hear from anyone who uses the
awesome-typescript-loader
, and how it compares.
module.exports ={ // put sourcemaps inline devtool:'eval',
// entry point of our application, within the `src` directory (which we add to resolve.modules below): entry:['index.tsx'],
// configure the output directory and publicPath for the devServer output:{ filename:'app.js', publicPath:'dist', path: path.resolve('dist'), },
// configure the dev server to run devServer:{ port:3000, historyApiFallback:true, inline:true, },
// tell Webpack to load TypeScript files resolve:{ // Look for modules in .ts(x) files first, then .js extensions:['.ts','.tsx','.js'],
// add 'src' to the modules, so that when you import files you can do so with 'src' as the relative route modules:['src','node_modules'], },
module:{ loaders:[ // .ts(x) files should first pass through the Typescript loader, and then through babel { test:/\.tsx?$/, loaders:['babel-loader','ts-loader'], include: path.resolve('src'), }, ], }, };
We configure the loaders so that any
.ts(x)
file is first passed through
ts-loader
. This compiles it with TypeScript using the settings in our
tsconfig.json
- and emits
ES2015
. We then use Babel to convert that down to ES5. To do this I create a
.babelrc
that contains the presets that we need:
{ "presets":["es2015","react"] }
And with that, we're now ready to write our TypeScript application.
Writing a TypeScript React component
Now we are ready to create
src/index.tsx
, which will be our application's entry point. For now we can create a dummy component and render it to check it's all working.
If you run Webpack now against this code you'll see some errors:
ERROR in ./src/index.tsx
(1,19): error TS2307: Cannot find module 'react'.
ERROR in ./src/index.tsx
(2,22): error TS2307: Cannot find module 'react-dom'.
This happens because TypeScript is trying to figure out the type of React, and what it exports, and is trying to do the same for React DOM. React isn't authored in TypeScript so it doesn't contain that information, but thankfully for this situation the community has created
DefinitelyTyped
, a large repository of types for modules.
The installation mechanism changed recently; all the types are published under the npm
@types
scope, so to get the types for React and ReactDOM we run:
yarn add @types/react
yarn add @types/react-dom
And with that the errors go away. Whenever you install a dependency you can always try installing the
@types
package too, or if you want to see if it has types available, you can use the
TypeSearch
website to do so.
Running the app locally
To run the app locally we just run the
webpack-dev-server
command. I set up a script,
start
, that will do just that:
"scripts":{ "start":"webpack-dev-server" }
The dev server will find the
webpack.config.json
file and use it to build our application.
If you run
yarn start
you will see the output from the server, including the
ts-loader
output that confirms it's all working.
$ webpack-dev-server
Project is running at http://localhost:3000/
webpack output is served from /dist
404s will fallback to /index.html
ts-loader: Using [email protected] and /Users/jackfranklin/git/interactive-react-introduction/tsconfig.json