Back to Webpack Js Org

TypeScript

src/content/guides/typescript.mdx

4.41.210.4 KB
Original Source

T> This guide stems from the Getting Started guide.

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. In this guide we will learn how to integrate TypeScript with webpack.

Basic Setup

First install the TypeScript compiler and loader by running:

bash
npm install --save-dev typescript ts-loader

Now we'll modify the directory structure & the configuration files:

project

diff
 webpack-demo
  ├── package.json
  ├── package-lock.json
+ ├── tsconfig.json
  ├── webpack.config.js
  ├── /dist
  │   ├── bundle.js
  │   └── index.html
  ├── /src
  │   ├── index.js
+ │   └── index.ts
  └── /node_modules

tsconfig.json

Let's set up a configuration to support JSX and compile TypeScript down to ES5...

json
{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node"
  }
}

See TypeScript's documentation to learn more about tsconfig.json configuration options.

To learn more about webpack configuration, see the configuration concepts.

Now let's configure webpack to handle TypeScript:

First, install the required dependencies:

bash
npm install --save-dev ts-node @types/node

webpack.config.ts

ts
import path from "node:path";
import { fileURLToPath } from "url";
import webpack from "webpack";

// in case you run into any TypeScript error when configuring `devServer`
import "webpack-dev-server";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const config: webpack.Configuration = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};

export default config;

For further refrence on how to write configuration in Typescript file

This will direct webpack to enter through ./index.ts, load all .ts and .tsx files through the ts-loader, and output a bundle.js file in our current directory.

Now lets change the import of lodash in our ./index.ts due to the fact that there is no default export present in lodash definitions.

./index.ts

diff
- import _ from 'lodash';
+ import * as _ from 'lodash';

  function component() {
    const element = document.createElement('div');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    return element;
  }

  document.body.appendChild(component());

T> To make imports do this by default and keep import _ from 'lodash'; syntax in TypeScript, set "allowSyntheticDefaultImports" : true and "esModuleInterop" : true in your tsconfig.json file. This is related to TypeScript configuration and mentioned in our guide only for your information.

Ways to Use TypeScript in webpack.config.ts

There are 3 ways to use TypeScript in webpack.config.ts:

  1. Using webpack with built-in Node.js type stripping feature (recommended):

    bash
    webpack -c ./webpack.config.ts
    

    Will attempt to load the configuration using Node.js's built-in type-stripping, and then attempt to load the configuration file using interpret and rechoir (in this case you need to install tsx or ts-node or other tools).

  2. Using custom --import/--require for Node.js:

    bash
    NODE_OPTIONS='--import=tsx --no-experimental-strip-types'  webpack -c ./webpack.config.ts
    
    bash
    NODE_OPTIONS='--require=ts-node/register --no-experimental-strip-types'  webpack -c ./webpack.config.ts
    

    The --no-experimental-strip-types flag is required starting with Node.js version 22.7.0.

  3. Using built-in Node.js transform types feature for Node.js ≥ v22.7.0:

    To enable the transformation of non erasable TypeScript syntax, which requires JavaScript code generation, such as enum declarations, parameter properties.

    bash
    NODE_OPTIONS='--experimental-transform-types' webpack --disable-interpret -c ./webpack.config.ts
    

TypeScript Path Aliases

<Badge text="5.105.0+" />

If you use compilerOptions.paths or compilerOptions.baseUrl in your tsconfig.json to create import aliases, starting with webpack 5.105, webpack can read these aliases directly via resolve.tsconfig. This replaces tsconfig-paths-webpack-plugin, which should no longer be used.

resolve.tsconfig accepts boolean | string | object:

webpack.config.js

js
export default {
  resolve: {
    tsconfig: true, // automatically find tsconfig.json
  },
};

Pass a string to point at a specific file (useful in monorepos):

js
export default {
  resolve: {
    tsconfig: "./tsconfig.app.json",
  },
};

Pass an object to also resolve TypeScript project references:

js
export default {
  resolve: {
    tsconfig: {
      configFile: "./tsconfig.json",
      references: "auto", // inherit references from tsconfig, or pass an array of paths
    },
  },
};

tsconfig.json

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

With the above, @/components/Button resolves to src/components/Button without any additional plugins or duplicating aliases in resolve.alias.

W> resolve.tsconfig only handles module resolution — it does not transpile TypeScript. You still need ts-loader, babel-loader with @babel/preset-typescript, or another transpilation step.

Loader

ts-loader

We use ts-loader in this guide as it makes enabling additional webpack features, such as importing other web assets, a bit easier.

W> ts-loader uses tsc, the TypeScript compiler, and relies on your tsconfig.json configuration. Make sure to avoid setting module to "CommonJS", or webpack won't be able to tree-shake your code.

Note that if you're already using babel-loader to transpile your code, you can use @babel/preset-typescript and let Babel handle both your JavaScript and TypeScript files instead of using an additional loader. Keep in mind that, contrary to ts-loader, the underlying @babel/plugin-transform-typescript plugin does not perform any type checking.

Source Maps

To learn more about source maps, see the development guide.

To enable source maps, we must configure TypeScript to output inline source maps to our compiled JavaScript files. The following line must be added to our TypeScript configuration:

tsconfig.json

diff
  {
    "compilerOptions": {
      "outDir": "./dist/",
+     "sourceMap": true,
      "noImplicitAny": true,
      "module": "commonjs",
      "target": "es5",
      "jsx": "react",
      "allowJs": true,
      "moduleResolution": "node",
    }
  }

Now we need to tell webpack to extract these source maps and include in our final bundle:

webpack.config.js

diff
 import path from "node:path";
 import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

  export default {
    entry: './src/index.ts',
+   devtool: 'inline-source-map',
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
      ],
    },
    resolve: {
      extensions: [ '.tsx', '.ts', '.js' ],
    },
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
  };

See the devtool documentation for more information.

Client types

It's possible to use webpack specific features in your TypeScript code, such as import.meta.webpack. And webpack provides types for them as well, add a TypeScript reference directive to declare it:

ts
/// <reference types="webpack/module" />
console.log(import.meta.webpack); // without reference declared above, TypeScript will throw an error

To enable the types for the whole project, add webpack/module to compilerOptions.types in tsconfig.json:

diff
  {
    "compilerOptions": {
      "types": [
+       "webpack/module"
      ]
    }
  }

Using Third Party Libraries

When installing third party libraries from npm, it is important to remember to install the typing definition for that library.

For example, if we want to install lodash we can run the following command to get the typings for it:

bash
npm install --save-dev @types/lodash

If the npm package already includes its declaration typings in the package bundle, downloading the corresponding @types package is not needed. For more information see the TypeScript changelog blog.

Importing Other Assets

To use non-code assets with TypeScript, we need to defer the type for these imports. This requires a custom.d.ts file which signifies custom definitions for TypeScript in our project. Let's set up a declaration for .svg files:

custom.d.ts

ts
declare module "*.svg" {
  const content: any;
  export default content;
}

Here we declare a new module for SVGs by specifying any import that ends in .svg and defining the module's content as any. We could be more explicit about it being a url by defining the type as string. The same concept applies to other assets including CSS, SCSS, JSON and more.

Build Performance

W> This may degrade build performance.

See the Build Performance guide on build tooling.