[Frontend Note] Path Aliases in Typescript + Webpack5 + Jest

How to set up Typescript, Webpack and Jest path aliases once and for all in a project?

Posted by Jamie on Wednesday, October 12, 2022

Path Aliases in Typescript + Webpack5 + Jest

Preface

In my project I originally only used path aliases in webpack, but I found that vscode still couldn’t resolve those paths. It turned out to be a Typescript compilation issue. After fixing that, I ran into another path issue when running unit tests with Jest, which took me some more time to solve. This post is to record those solutions.

Goal

Suppose the project structure is like this:

  • src
    • themes
      • index.ts
    • plugins
      • index.ts
    • App.tsx
    • helper.ts

Our goal is to make any file under src/* reachable as @base/*, files under themes/* as @themes/*, and files under plugins/* as @plugins/*, instead of having to write something like ../../../plugins/xxx.ts because of relative paths.

Below I will first cover how to set this up in webpack, typescript, and jest individually, and then provide a unified setup for the current project.

Webpack5 Aliases

When webpack does module resolution, you need to set alias in webpack.config.js, as follows:

  • webpack.config.js

    const  path  = require('path')
    
    module.exports = {
    	...
    	resolve:{
    		alias: {
    			'@base': path.resolve(__dirname, 'src'),
    			'@plugins': path.resolve(__dirname, 'src/plugins'),
    			'@themes': path.resolve(__dirname, 'src/themes'),
    			...
    		}
    	},
    	...
    }
    

Defining Path Aliases in Typescript

If you only set alias in webpack and don’t also set it in tsconfig.json, vscode won’t be able to resolve the paths.

  • tsconfig.json

    {
    "compilerOptions": {
    		"paths": {
    			"@base/*": ["src/*"],
    			"@plugins/*": ["src/plugins/*"],
    			"@themes/*": ["src/themes/*"],
    			......
    		}
    	}
    }
    

Defining Path Names in Jest

Because Jest does not run via the webpack bundle you configured, but compiles separately, if you don’t configure module resolution for Jest, Jest also won’t be able to resolve the paths.

  • jest.config.js

    module.exports = {
    	...,
    	 moduleNameMapper: {
    	 	'^@base/(.*)': '<rootDir>/src/$1',
    	 	'^@plugins/(.*)': '<rootDir>/src/plugins/$1',
    	 	'^@themes/(.*)': '<rootDir>/src/themes/$1',
    	 }
    }
    

Unified Setup

The methods above all solve the path alias problem, but if you want to rename one of the aliased folders, or add a new folder and create a new alias, you have to update three files for one change—that’s way too tedious. So below is a one-and-done approach: change one file, and the rest update automatically.

  • First, define a tsconfig.alias.json in the root directory:

    {
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@base/*": ["src/*"],
             "@themes/*": ["src/themes/*"],
             "@plugins/*": ["src/plugins/*"],
        }
    }
    }
    
  • tsconfig.json Use tsconfig.json’s extensibility to extend tsconfig.alias.json:

    {
    "compilerOptions": {
    		...,
    		"extends": "./tsconfig.alias.json",
    	}
    }
    
  • webpack.config.json

In webpack, define a function to convert the strings in tsconfig.alias.json into the format expected by webpack alias:

function getWebpackAliasesFromPaths(configPaths) {
    const alias = Object.entries(configPaths).reduce(
        (webpackAliases, [configAlias, configPathList]) => {
            const [aliasKey] = configAlias.split('/')
            const [relativePathToDir] = configPathList[0].split('/*')
            return {
                ...webpackAliases,
                [aliasKey]: path.resolve(__dirname, `../${relativePathToDir}`),
            }
        },
        {},
    )
    return alias
}

Then you can import tsconfig.alias.json and let the function handle building the alias config:

const aliases = require('../tsconfig.alias.json')

module.exports = {
	...,
	resolve:{
		alias:  getWebpackAliasesFromPaths(aliases.compilerOptions.paths)
	},
}
  • jest.config.js For jest, there is already an npm package ts-jest that can help convert paths, like this:

    const { pathsToModuleNameMapper } = require('ts-jest')
    const aliases = require('./tsconfig.alias.json')
    
    module.exports = {
    	...,
    	 moduleNameMapper: {
    	 	...pathsToModuleNameMapper(aliases.compilerOptions.paths, {
            prefix: '<rootDir>/',
        }),	 
    }
    }
    

This way, whenever you want to add another folder with an alias, or change something else, you only need to modify the configuration in tsconfig.alias.json!

ChangeLog

  • 20221013 - init revision
  • 20260501–translate by claude code