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
- …
- themes
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.jsonin the root directory:{ "compilerOptions": { "baseUrl": ".", "paths": { "@base/*": ["src/*"], "@themes/*": ["src/themes/*"], "@plugins/*": ["src/plugins/*"], } } }tsconfig.json Use
tsconfig.json’s extensibility to extendtsconfig.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-jestthat 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