Webpack for ngUpgrade, Part 2: Setting Up TypeScript

Upgrading AngularJS
12 min readFeb 19, 2018

--

Mastering Webpack during a real world ngUpgrade requires four things:

  1. An understanding of the basics (the last guide)
  2. Setting up TypeScript (this guide)
  3. A development environment (the next guide)
  4. Environment-specific builds, especially production (the final guide)

This holds true whether you’re coming from script tags or from a task runner like Gulp or Grunt.

In our last guide, we walked through exactly how to add Webpack to a project and set up our very first config. Webpack is successfully generating our bundle, which is basically just taking any imports and putting them into one file and then enabling the modules to run in the browser using the Webpack bootstrapping code. That’s fine for a very simple example, but we need something more robust for a real application that need to prepare for ngUpgrade.

In this guide, we’ll set up TypeScript in our project and set up Webpack to bundle it. That will lay the groundwork for migrating our AngularJS code to TypeScript.

(If the length of this guide freaks you out, just hop over to our course for step-by-step videos on this topic and even advanced Webpack tactics.)

Our Starting Point

Take a minute and clone our course sample project on GitHub (don’t forget to run npm install). I’ll reference different commits throughout this guide so you can see examples of everything we cover here. Checkout this commit to see our starting point:

git checkout 5d4d5ab654c0e5e60f96b2cb90566fe35c348bf1

You’ll see where we left off last time:

  • We have a very basic Webpack setup
  • Our home component has been converted to an ES6 module
  • Our index.html now references our bundle

Now, let’s get started with TypeScript!

TypeScript Time

The first thing we’re going to do is install and set up TypeScript. Now, you might be thinking, “Wait a second, why are skipping ES6/ES2015 and going straight to TypeScript?” There are three reasons for this.

First, Angular (2+) is both written in and best used with TypeScript. It’s a very enjoyable development experience and doesn’t require a bunch of esoteric knowledge about the language to be productive. The earlier we can make the switch to set up TypeScript and start learning it, the easier it will be to then add in the ngUpgrade library and start writing Angular. We won’t have to tackle the setup, TypeScript learning curve, and Angular learning curve all at once — we can isolate our efforts to getting familiar with Angular’s new features and concepts.

Second, TypeScript is actually just a superset of JavaScript:

Diagram by Sudheer Jonna

We can still write valid ES6 or ES5 and the TypeScript compiler will compile it down to ES5 JavaScript. So, we don’t have to technically write TypeScripty features (and I don’t recommend you do this with AngularJS, it gets complicated fast), but we still get to take advantage of all the strict typing and the safety that a compiler provides for us. For example, TypeScript won’t even let our app compile if there are syntax errors in our code. This not only improves the quality of our code, but speeds up our development time considerably. We’ll also get super helpful stuff like Intellisense in Visual Studio Code based on library types.

Finally (and this is the big one), it’s now actually possible to do this. Lemme ‘splain. Somewhere prior to about mid-2017, skipping ES6 and hopping into TypeScript with a real app with real dependencies was a nightmare. TypeScript required typing definitions for everything and was very strict on including types in code. On top of that, there were several different type definition repositories and the set up of these was really confusing and terrible. This made it nearly impossible to actually integrate TypeScript into a large legacy code base. That random date picker widget you used in 2015 sure as heck doesn’t have a type definition file, and your legacy code certainly doesn’t specify types throughout it. It was a great idea in theory, but just not practical. So, many of us (myself included) set up Babel to transpile ES6 and left TypeScript alone for a while.

Thankfully, the TypeScript team and the community came to the rescue. TypeScript implemented some new options to allow things to “loosen up” so that you can write valid ES6 or ES5 without getting yelled at by the compiler, including being more lax on requiring type definitions. And, the community came together and settled on storing type definitions under an npm namespace to make installing types just as easy as adding packages. These changes are monumental, and we should be sure to thank everyone involved for making TypeScript a viable option for legacy code, not just greenfield projects.

So now, let’s get to work.

Installing TypeScript

Languages like ES6 and TypeScript need to be transpiled down into the previous version of JavaScript (ES5) for older browsers to understand. We need to set up the TypeScript compiler and get it wired up in our Webpack build.

The first thing we need to do is install TypeScript as a development dependency of our application. We just do that with our friend npm:

npm install --save-dev typescript 

(Feel free to use yarn add typescript --dev instead.)

We're doing this locally so that everybody who works on the project will have the same version of TypeScript installed. It also lets npm run the TypeScript compiler locally. You’re probably also going to want TS installed globally:

npm install -g typescript

Alright, we've now got TypeScript installed. We can now go see where the binary lives in the .bin folder within our node_modules folder . You can see that among all our different binaries we now have a tsc binary waiting for us:

The tsc binary inside of our Node modules.

The tsc binary is the TypeScript compiler. It has it's own command line interface with commands and arguments that you can pass it, just like with Webpack. And just like with Webpack, we could just pass all of our configuration into the command line, but, that would get really tedious since there are a lot of options that we need to set.

Instead of using the command line, let’s add a config file to our project to specify settings for when our TypeScript compiles.

Adding a TypeScript Config File

Let’s create a file called tsconfig.json under our public folder where our Webpack config is.

The TypeScript config file has a lot of different options. Luckily, the Angular team has given us a good starting place in the Angular documentation.

{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}

We’re going to use this exact same setup as we would with Angular, but with one exception for our AngularJS code. Change that “noImplicitAny” option to false:

"noImplicitAny": false

TypeScript, being a strongly typed language, has the option to either infer the any typing, or be strict about requiring it with variable declarations that don’t have a type. The problem is, if you’re trying to write valid ES6 and not necessarily TypeScript, or take advantage of TypeScript features, then this noImplicitAny flag can cause a lot of compiler issues in legacy code. If you left this set to true, you’d have to specify the any type everywhere in your code that you weren’t using explicit types. That can be a huge hassle in big AngularJS apps, so let’s turn it off for now.

As for the rest of this config file, you don’t need to spend too much time worrying about all the details of what’s in here. Most of it is just a “set it and forget it” kind of thing that won’t really affect your day-to-day development. You can read further on a lot of these things, and you can also head over to the TypeScript documentation and take a look in the handbook at the tsconfig.json documentation. This will tell you in painstaking detail everything you ever wanna know about the TS config.

For now, don’t spend a lot of your time right now doing this. You can see that the target is ES5, and we’ll be using CommonJS modules with the Node style of module resolution. If your app is already using a different module style, you may need to adjust accordingly.

That’s all we need to do to get TypeScript set up in our application (you can see the finished config file here). Now, let’s get it wired up with Webpack.

Loading TypeScript with Webpack

We’ve got TypeScript installed and we’ve got our TS config file set up. Now we need to get Webpack loading, compiling, and bundling our TypeScript. So, there are a couple things we need to do to our Webpack config. The first thing we’ll do is add our module section and our very first loader. We’ll do this just after line five in our webpack.config.js file like this:

Our first loader!

Let’s break this down.

Loaders go in the module option of the Webpack config object. Within module we specify a rules array. Each rule contains:

  • A test property with a regular expression to match files against
  • A use property, which specifies which loader(s) we’ll use when that test succeeds.
  • Several other optional parameters, like an exclude property that lets us exclude a regular expression from being used with that loader.

For our TypeScript rule, our test regular expression will be for the .ts extension. Next, we’ll use a loader called ts-loader, and we’ll exclude our node_modules folder.

If you’re wondering were ts-loader comes from, well, that’s a good instinct. We need to install it with npm and save it in our development dependencies:

npm install --save-dev ts-loader

And now Webpack knows how to handle our TypeScript files! We just need to do a couple more things before we’re ready to start working.

Resolving the .ts Extension

Remember how in the last guide I said we don’t need to include our .js extensions in our imports? Part of that is some magic that Webpack does with Node for module loading, where it just automatically resolves file extensions for us. Now that we’ve added TypeScript, we need to override that default.

Back in our Webpack config, we’re going to add a resolve object under our module:

The resolve object goes after the module object.

Resolve is an object, and we’re going to override its extensions property. The extensions property is an array, and by default it contains.js and .json. We’re going to override that. We don’t really need .json and we’re going to add TypeScript’s .ts to the front of the array.

You might see the .tsx extension in some documentation and tutorials surrounding Webpack and TypeScript. TSX isn’t the Toronto Stock Exchange in this case — it’s the TypeScript version of JSX that’s used a lot with React. We really don’t need to mess with JSX or TSX for AngularJS or Angular, so we’re not going to include the .tsx extension here. Feel free to read more about TSX here.

Now that we’ve got TypeScript installed and working with Webpack, let’s make some changes in our code so we can start putting it to work.

Implementing TypeScript in Our Sample App

To start using TypeScript in our sample project, let’s rename our root level app file to app.ts and update our entry point in Webpack:

entry: './app.ts',

Your finished Webpack config should look like this.

And just like that, we’ve switched over to TypeScript. Now we just need to convert the rest of our files to TypeScript, import them, and remove them from index.html. Before we start on that though, we’d better go check our app.ts file and make sure it’s still working.

Import Angular and Install Types

I’ve opened our app.ts file, which was just our app root before. You can see that we’ve got some red squiggles:

Our freshly renamed app.ts file.

Rather than be alarmed by that, I actually want you to rejoice in the red squiggles, because this means that TypeScript is working and it’s giving us information. If we hover over the squiggle under angular.module, we see the error cannot find name ‘angular’. That makes sense, because TypeScript has no idea what’s going on with Angular. It doesn’t know about Angular in the global sense, because TypeScript is only looking within this file and not within our global vendor files or anything like that. These errors are really useful because this gives us a direction that we need to go.

In fact, if I try to run Webpack and compile TypeScript, we’ll get some errors:

Familiar error messages.

You see, we get the same TypeScript errors through Webpack. The compiler errors in Visual Studio Code are actually showing up as Intellisense hints. This is super useful! How many times have you tried to figure out what’s wrong in your JavaScript and had no idea? This is why TypeScript is so awesome, even if we’re not really using all the bells and whistles that it provides us.

Luckily, these are easy fixes, but there are few things that we need to do. First of all, we need to import AngularJS and its router in our application root. We can do this at the top of our file:

import * as angular from 'angular';
import 'angular-route';

Webpack will magically resolve to our node modules folder. You can see that our red squiggly went away underneath our references to Angular, so that’s a step in the right direction.

Install AngularJS Types

There’s another thing we need to do though. We need to install some type definitions for TypeScript that will help us out as we develop our application. In npm, types can be installed by using the @types namespace followed by a slash and then the library. We’re going to install the AngularJS types using this command:

npm install @types/angular --save-dev 

You don’t have to install type definitions for every library. This is actually a new change to TypeScript that makes a huge difference. Not long ago, in order to use TypeScript with your application, you would need type definitions for every library that you depended on. This made it really arduous to try to develop if you weren’t using pure TypeScript with all kinds of interface definitions and definitions of statistic types or strict types.

Luckily, the TypeScript maintainers listen to the larger data script community and enabled this option to be able to just use npm type definitions and optionally look for definitions inside of node modules.

Convert Home Component to TypeScript

Let’s rename our home component file to .ts and see what happens. We can also remove the script tags for AngularJS and the AngularJS router from index.html. Now, we can run Webpack by opening a terminal and running:

cd public
webpack

You should see this output:

Our home.ts file is part of the bundle!

Our home component is now included in the bundle!

You can now run static-server inside of public and go look at the application. It should run correctly, with no visible difference between our TypeScript and our JavaScript pieces. If you inspect at the bundle file, in addition to the bootstrap for Webpack, you’ll see AngularJS, the router, our app module and our home component. Awesome!

You can see the current state of our project here, which will be the starting point of the next guide.

What’s Next

Before we keep going through and converting pieces to TypeScript, let’s make a couple changes to our development process here. Otherwise, it’s gonna get super tedious trying to rerun Webpack and refreshing static-server every single time we make a change. We’ll set up our Webpack development server in the next guide.

If you love this guide, I’ve got 200+ detailed videos, quiz questions, and more for you in my comprehensive course UpgradingAngularJS.com. I created it for everyday, normal developers and it’s the best ngUpgrade resource on the planet. Head on over and sign up for our email list to get yourself a free Upgrade Roadmap Checklist so you don’t lose track of your upgrade prep. And, while you’re there, check out our full demo.

--

--

Upgrading AngularJS

The best ngUpgrade resource on the planet. Comprehensive, step by step course to help you move from AngularJS to Angular.