[Frontend筆記] Typescript+Webpack5+Jest 的路徑命名問題

如何在專案中,一勞永逸的改動Typescript、Webpack、Jest中的路徑別名問題?

Posted by 李定宇 on Wednesday, October 12, 2022

Typescript + Webpack5 + Jest 的路徑命名問題

前言

因為在專案中,原本只有在 webpack 中使用路徑重命名,發現在 vscode 中還是沒辦法找到該路徑,最後發現是 Typescript 的編譯問題;解決之後,在用Jest作單元測試的時候,發現還是有路徑問題,因此又花了點時間解決。本文就是來紀錄一下解決方案。

目標

假設專案結構如下:

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

我們的目標是想要讓在 src/* 底下的檔案路徑都為 @base/*themes/* 底下的檔案路徑都為 @themes/*plugins/* 底下的檔案路徑都為 @plugins/*,而不用因為相對路徑的緣故而變成../../../plugins/xxx.ts

以下會先分別介紹在webpack、typescript、jest要怎麼樣設置,然後在提供現階段專案中的統一設置方法。

webpack5 的別名

在webpack執行module resolution時,需要在webpack.config.js中設定alias,如下:

  • 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'),
    			...
    		}
    	},
    	...
    }
    

Typescript 定義路徑別名:

如果只在webpack中設定alias、而沒有在tsconfig.json中另外設定,會導致vscode沒辦法辨別路徑

  • tsconfig.json 設定

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

Jest中定義路徑名

由於Jest執行的時候,不是投過設定好的Webpack打包後執行,而是另外編譯的,所以如果沒有在為Jest設定module resolution,Jest也是沒辦法辨別路徑

  • jest.config.js

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

統一設定

雖然說上述方法都解決了路徑別名問題,但如果某個別名的資料夾要修改名稱、或者要新增一個資料夾然後想創別名,這樣一個改動要改三個檔案,實在太麻煩了吧?所以下面為一勞永逸、只要改動一個檔案、其他都會連動更改。

  • 先在根目錄定義一個tsconfig.alias.json

    {
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@base/*": ["src/*"],
             "@themes/*": ["src/themes/*"],
             "@plugins/*": ["src/plugins/*"],
        }
    }
    }
    
  • tsconfig.json設定 利用tsconfig.json的可拓展特性,來extends tsconfig.alias.json

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

在webpack中建立一個函數來處理tsconfig.alias.json中的字符串,以符合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
}

然後就可以先把tsconfig.alias.json給import進來,然後透過函數處理來寫入alias

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

module.exports = {
	...,
	resolve:{
		alias:  getWebpackAliasesFromPaths(aliases.compilerOptions.paths)
	},
}
  • jest.config.js設定 在jest中,已經有一個npm包ts-jest可以幫忙處理這個路徑轉換,如下:

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

這樣一來,如果之後有要新增其他資料夾然後增加別名、或者有什麼要更動的,就只要修改tsconfig.alias.json裡面的設定即可!

ChangeLog

  • 20221013 - 更改初稿