在第 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 */
/* Tools */import { resolvePath } from "@/tool/resolvePath";import HtmlWebpackPlugin from "html-webpack-plugin";import Webpack from "webpack";/* Data */import alias from "./data/alias";import env from "./data/env";import type { EnvForStartApp, WebpackConfiguration, WebPackDevServerConfiguration,} 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 天)來看看到目前為止的成果吧 (≖ᴗ≖๑)