捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:webpack 5 與 TypeScript
在第 4 天個人曾經推薦在 tsconfig.json 中透過 paths
設定短路徑、省去在檔案間互相 import 時計算路徑的麻煩。而今天就會來詳細解釋如何為 webpack 加上 alias
設定,以及要安裝哪些 loader 來讓 webpack 能夠將 TypeScript 轉為瀏覽器能夠理解的 JavaScript 內容。
流程說明
先來順一下今天預計要做的事情:
- 從
tsconfig.json
讀取paths
資料,並把這些內容轉為 webpack alias 能夠使用的格式 - 將 alias 灌進 webpack 開發環境設定中。注意:沒有提供 alias 的話,
webpack-dev-server
拉起來之後,是無法正確解析我們在 tsconfig.json 中自訂的@Asset/*
這類路徑的 - 為 webpack 加上 esbuild-loader 來將
.ts/.tsx/.js
處理成瀏覽器環境能夠讀懂的格式。選擇 esbuild-loader 是因為此套件是用 Go 寫的,編譯速度較快 - 到
./public
資料夾中新增一個給 React app 掛載的 index.html 檔案,然後設定html-webpack-plugin
讓 webpack 知道去哪裡撈 html 內容
完成以上步驟後,你的本機伺服器就能正常顯示專案中的 TypeScript 內容了 (・∀・)
處理 alias
首先在專案根目錄的 ./tool
資料夾中新增 resolvePath.ts
:
/* Packages */
import fs from 'fs';
import path from 'path';
/* Data */
const APP_ROOT = fs.realpathSync(process.cwd());
/* Functions */
export function resolvePath(file: string) {
return path.resolve(APP_ROOT, file);
}
這個工具會負責回傳「傳入的參數相對於專案根目錄的絕對路徑」。比如當我呼叫 resolvePath('src/index')
時,我會得到(以 mac 環境為例)/Users/<user name>/ithome-2023/src/index
這樣的路徑資訊。
接著新增 ./config/data/alias.ts
並撰寫以下內容:
/* Tools */
import { resolvePath } from '@/tool/resolvePath';
/* Data */
import tsConfig from '@/tsconfig.json';
type PathPair = [string, string];
/* Main */
const paths: PathPair[] = Object.entries(tsConfig.compilerOptions.paths).map(
(pathPair) => {
const [pathKey, pathValue] = pathPair;
return [pathKey.replace('/*', ''), pathValue.join().replace('/*', '')];
}
);
const alias = paths.reduce((reducedValue, currentValue) => {
const [key, pathToResolve] = currentValue;
return {
...reducedValue,
[key]: resolvePath(pathToResolve),
};
}, {});
export default alias;
首先透過 Object.entries(tsConfig.compilerOptions.paths)
將 tsconfig.json 中的 paths 物件轉為 [key, value]
陣列,再將 key 部分的 @Asset/*
轉為 @Asset
、將 value 部分的 ["src/asset/*"]
轉為 src/asset
。印出來看會長這樣:
[
[ '@', '.' ],
[ '@Api', './src/api' ],
[ '@Asset', './src/asset' ],
[ '@Component', './src/component' ],
[ '@Hook', './src/hook' ],
[ '@Model', './src/model' ],
[ '@Reducer', './src/reducer' ],
[ '@Style', './src/style' ],
[ '@Tool', './src/tool' ]
]
然後使用 Array..prototype.reduce()
並搭配一開始寫好的 resolvePath
工具,將 paths
加工為以下格式:
{
'@': '/Users/<user name>/ithome-2023/',
'@Api': '/Users/<user name>/ithome-2023/src/api',
'@Asset': '/Users/<user name>/ithome-2023/src/asset',
'@Component': '/Users/<user name>/ithome-2023/src/component',
'@Hook': '/Users/<user name>/ithome-2023/src/hook',
'@Model': '/Users/<user name>/ithome-2023/src/model',
'@Reducer': '/Users/<user name>/ithome-2023/src/reducer',
'@Style': '/Users/<user name>/ithome-2023/src/style',
'@Tool': '/Users/<user name>/ithome-2023/src/tool'
}
接下來把這個 alias
變數餵進 resolve.alias 中,webpack 就能認得我們在 tsconfig.json 中自訂的路徑了:
const webpackDevelopmentConfig: WebpackConfiguration = {
resolve: {
alias,
},
};
處理 TypeScript
以下是個人慣用的最小啟動+處理 TypeScript 設定:
const webpackDevelopmentConfig: WebpackConfiguration = {
module: {
rules: [
{
test: /\.(js|ts|tsx)$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015',
},
},
],
},
resolve: {
extensions: ['.js', '.ts', '.tsx'],
alias,
},
};
較詳細的說明如下:
module.rules
目前的任務是:當偵測到.js
/.ts
/.tsx
類型的檔案時,使用esbuild-loader
來處理之。這邊的寫法是從 loader 的官方說明借來的,有更多客製化的需求可以參考該套件的 README- 設定
resolve.extensions
為['.js', '.ts', '.tsx']
代表可以在 import 這類檔案時省略副檔名,參考 webpack 文件
現在你可以放心撰寫 TypeScript 檔案了 (*゚ー゚)
目前為止的 webpack 設定
只差一點點就能啟動了!現在加上 html-webpack-plugin
來讓 webpack 知道要去哪裡抓 index.html 模板(記得模板內要留一個 html 元件綁 id="root"
讓 React app 有地方住)。另外個人也習慣設定 devtool: source-map
來確保除錯時能夠取得最完整的檔案名、程式行數資訊:
/* Packages */
import HtmlWebpackPlugin from 'html-webpack-plugin';
import Webpack from 'webpack';
/* Tools */
import { resolvePath } from '@/tool/resolvePath';
/* Data */
import alias from './data/alias';
import env from './data/env';
import type {
WebPackDevServerConfiguration,
WebpackConfiguration,
EnvForStartApp,
} from './data/types';
const envForStartApp = env['process.env'] as EnvForStartApp;
const port = +JSON.parse(envForStartApp.APP_PORT);
/* Main */
const webpackDevelopmentConfig: WebpackConfiguration = {
mode: 'development',
entry: resolvePath('src/index'),
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|ts|tsx)$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015',
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: resolvePath('public/index.html'),
publicPath: '/',
}),
new Webpack.DefinePlugin({ ...env }),
],
resolve: {
extensions: ['.js', '.ts', '.tsx'],
alias,
},
};
export const webpackDevServerConfig: WebPackDevServerConfiguration = {
port,
open: true,
historyApiFallback: true,
};
export default webpackDevelopmentConfig;
./public/index.html
先簡單寫就好,後續開始部署或是有 SEO 優化需求時再更新即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ithome 2023 demo</title>
</head>
<body>
<main id="root"></main>
</body>
</html>
現在 webpack 已經知道如何編譯 TypeScript、也知道去哪裡尋找 index.html 內容了。在終端輸入 make start
(參考第 6 天)來看看到目前為止的成果吧 (≖ᴗ≖๑)