Integrating TypeScript with Webpack
By: Uday Hiwarale
Tags: Programming, Web Development, Typescript, JavaScript, Javascript Development

● Setting up Webpack and TypeScript

Before we do anything else, we need to set up a project and install some dependencies. For Webpack, we need webpack and webpack-cli package and for TypeScript features, we need typescript and ts-loader packages.

$ npm init -y

$ npm install -D webpack webpack-cli typescript ts-loader

Now that all the dependencies are ready to be used, we need to create the webpack.config.js file which is used to tell Webpack which files to be bundled, which loaders need to be used, how to bundle output files and other important configurations about this bundling process.

According to the above webpack.config.js , the entry point of the bundling (hence the compilation) process is index.ts file inside src directory (relative to this file). The output bundle file will be main.js and it will be dumped inside dist directory (relative to this file).

The resolve.extensions list contains the extension Webpack will use to search a file when an import statement is found in a program. For example, import x from './y' will resolve in the lookup of ./y.ts and ./y.js according to the above configuration. Therefore, you don’t need to provide all the program files as entry files in the Webpack configuration.

The most important part of this configuration is module.rules . This is a list of loader configuration rules that will be matched for all entry files (including imported files). If the path of the file matches the test pattern but not the exclude pattern, then it will be filtered through loaders specified by use .

💡 You can learn more about this file from the official documentation.

The second important part of the project compilation is the tsconfig.json file that controls how a TypeScript file is compiled. As you can see, it’s not nearly as complicated as we have learned in other lessons.

At the moment, a .ts file will be transpiled to the ES5 version of JavaScript ( target ) and all import statements will be kept in the ES2015 format ( module ) in the output. You can also set the module to CommonJS as Webpack can also handle the CommonJS module system.

The noImplicitAny compiler-option is used to disallow a value implicitly having the any type. If any error TypeScript related error occurs during the compilation (such as implicit any ), Webpack will stop with an error by printing the TSC errors to the console.

So the final package.json file looks like above. The main field points to the final bundle produced by the Webpack in case this project will be used as an NPM module. The build script contains the command to start the Webpack bundling process which at the moment only contains webpack command.

/webpack-setup

├── dist

| └── main.js

├── node_modules/...

├── package.json

├── src

| ├── index.ts

| └── lib

| └── classes.ts

├── tsconfig.json

└── webpack.config.js

Currently, our project structure looks like above. The dist folder and files in it will be generated by the Webpack so ignore it for now. The src directory contains the actual program files. The index.ts imports lib/classes.ts , hence it will also be a part of the compilation.

Now to actually compile the project with Webpack, we need to use npm run build command which internally will call webpack command. You can also use npx webpack command without having to modify package.json .

You should visit this repository to see the actual source code of the src directory. The classes.ts file exports the Person class and index.ts file imports and instantiates it. It has a console.log statement to print the result of person.getFullName() method call which returns the name of the person .

$ node dist/main.js

John Doe

You can run the final bundle file main.js using node which prints the full name of the person . You can also run the main.js bundle in a browser and it will also print the same result. This is possible because the main.js doesn’t contain import/export statements. Rather, its code looks like below.

As you can see, this file doesn’t contain import or export statements. Rather, this file contains some helper functions injected by Webpack to process modules. Also, all the code is transformed in the ES5 syntax.

● Generate Source Maps

So far, we have managed to produce the main.js bundle file by compiling TypeScript programs. However, when we use this code in actual production, we might need to debug this file such as using browser’s DevTool while looking for errors. If your code looks like above, it will take years for you to find the bug. That’s where source maps come in.

A source map .map file contains the original source codes of the files from which the final bundle was created. This file is used by DevTools to display the original source code while debugging. However, we need to configure both Webpack and TypeScript to produce these files.

First of all, we need to set sourceMap compiler-option to true in the tsconfig.json . Using this, TSC will return a .map file (along with the .js file) which contains the actual source code of the .ts file. The ts-loader will supply this source map file to Webpack to take further action.

// tsconfig.json {

"compilerOptions": {

...

"sourceMap": true

}

}

The next thing we need to do is to give devtool property to an appropriate value inside webpack.config.js so that Webpack can generate an appropriate source map. You can find possible values for it here. I have used 'source-map' value to generate exclusive .map file for the bundle.

// webpack.config.js module.exports = {

devtool: 'source-map', // generate source map

...

};

After these settings, when you compile the project using the same old npm run build command, you get main.js.map file in the dist directory.

webpack-setup/dist/

├── main.js

└── main.js.map

The main.js.map file contains all the original TypeScript file sources (such as index.ts and classes.ts ) which will be available in the DevTool. To visualize this, import main.js inside index.html file and open it inside a browser using live-sever (or any other means).

(source: Chrome DevTools)

If you want to continuously run Webpack in the background and compile the project again whenever a .ts file changes, set watch option to true in the webpack.config.js file. Now, whenever a file included in the compilation process changes, Webpack will rebuild the project.

● Making builds faster

When you compile this project, TypeScript has to run a full type analysis on all the .ts files included in the Webpack compilation and produce JavaScript output. This can take a lot of time if you have hundreds of TypeScript files in the project. This problem is quite evident if you have watch mode on.

To mitigate this issue, we can split the job of analyzing types and transpiling JavaScript between two different threads. First of all, we need to set the transpileOnly option of the ts-loader to true . This will configure ts-loader to avoid full type analysis by the TSC.

Then we need to install fork-ts-checker-webpack-plugin NPM module which is a Webpack plugin that runs the type analysis of the .ts files included in the compilation on a separate thread. With this setup, we get the best of both worlds but running parallelly on separate threads. The new webpack.config.js file looks like below.

This approach has a few problems as described here. These problems are bound to occur since the TypeScript compiler at the moment does not provide a mechanism to transpile TypeScript to JavaScript without having to perform type analysis first. Therefore, the ts-loader has to use some tricks to get around it. The issue is raised here to provide a flag --transpileOnly that should technically make this happen but it’s an open issue.

💡 However, TSC does provide noEmit compiler-option (and --noEmit ) to only perform type analysis on the code without having to produce any output. This option is mostly used by third-party tools to analyze TypeScript programs.