
專案能跑不代表能維護。這篇幫你把 ESLint 和 Typings 設好,讓「三個月後的你」不會想掐死「現在的你」。本篇是 系列第二篇(Jest 測試設定) 的前置。
先講結論
- ESLint:裝好 + 跑一次
pnpm run lint,通常會噴兩個錯(unused import 和any),修掉它們就是你寫好 TypeScript 的第一步 - Typings 資料夾:把路由路徑之類的「魔術字串」抽成 enum,以後改路由不用全域搜尋替換
- 完成後的資料夾結構長這樣,記住就好
src/
routes/
bin/
app.ts
typings/
routes/
index.ts
users.ts
ESLint:你以為你不需要,直到你 diff 出 200 行空白變更
你有沒有遇過這種事?PR review 的時候,diff 顯示改了 200 行,結果 180 行都是 tab 變 space 或多了一個空行。這就是沒有統一 linter 的下場。
安裝
一行搞定:
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin \
eslint-config-airbnb-base eslint-config-prettier eslint-plugin-import \
eslint-plugin-node eslint-plugin-prettier prettier @eslint/js typescript-eslint套件很多對吧?簡單分類:@typescript-eslint/* 是讓 ESLint 看懂 TypeScript 的、prettier 系列是處理格式的、airbnb-base 是規則集。你不需要記,裝了就對了。
初始化
pnpm exec eslint --init選項照這樣選:syntax and find problems → esm → none(framework)→ typescript → node。最後會生出 eslint.config.mjs:
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
export default [
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
}
}
];argsIgnorePattern: "^_" 這條很實用 — Express 的 middleware 常常有用不到的參數(像 _req),加底線就不會被 ESLint 罵。
跑第一次 lint
在 package.json 加上:
"scripts": {
"lint": "eslint 'src/**/*.ts'"
}然後 pnpm run lint,你大概會看到兩個錯誤:
src/app.ts → 'join' is defined but never used
src/bin/www.ts → Unexpected any. Specify a different type
第一個好解決,把 app.ts 裡沒用到的 import { join } from 'path' 刪掉。第二個比較有意思 — 我們在 www.ts 的 error handler 用了 any,要改成正確的型別。
修 any — 用 NodeJS.ErrnoException
把 www.ts 的 onError 參數型別從 any 改成 NodeJS.ErrnoException:
const onError = (error: NodeJS.ErrnoException) => {
if (error.syscall !== 'listen') throw error;
// ... 其餘不變
};這不只是消 lint 警告而已。NodeJS.ErrnoException 有 .code、.syscall 這些屬性的型別定義,IDE 的自動完成會變好用很多。用 any 就像開車不繫安全帶,出事之前都覺得沒差。
Typings:把魔術字串關進 enum 裡
你的路由現在寫死了 '/' 和 '/users'。如果有一天要把 /users 改成 /api/users,你得搜尋整個 codebase。路由少還好,多了就是噩夢。
建立 typings 資料夾
mkdir -p typings/routestypings/routes/index.ts:
export enum IndexRoutePaths {
HOME = '/',
USERS = '/users',
}typings/routes/users.ts:
export enum UserRoutePaths {
INDEX = '/',
}更新路由來用 enum
// src/routes/index.ts
import { IndexRoutePaths } from '../../typings/routes/index';
router.get(IndexRoutePaths.HOME, (req: Request, res: Response) => {
res.send('Welcome to the Home Page');
});
router.use(IndexRoutePaths.USERS, usersRouter);以後要改路徑?改 enum 一個地方就好,TypeScript compiler 會幫你檢查有沒有漏改的。
更新 tsconfig.json
記得把 typings/ 加進 include,不然 TypeScript 不會去編譯它:
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*", "tests/**/*", "typings/**/*"]
}注意 rootDir 從 ./src 變成了 ./,因為現在 typings/ 跟 src/ 是平行的。不改的話 TypeScript 會跟你說 typings/ 不在 rootDir 裡面。
到這裡的成果
跑 pnpm run lint 應該零錯誤了。你的專案現在有了統一的 code style、有意義的型別定義,不再是一個「只是能跑」的原型。
下一篇 Jest 測試設定 會把測試環境也搭起來,寫幾個 route 的 integration test,讓你改 code 的時候有個安全網。
ESLint 就像交通規則 — 你可以不遵守,但遲早會出事。