[Next Note] - 把 TypeScript、ESLint、Prettier、Alias 摻再一起做沙尿牛丸

Introduction & 前言

Next.js + TS + ESLint + Prettier

你開發使用 Tab 還是 Space 縮排呢?兩格還是四格?單引號還是雙引號?

哎呀,git pull 下來怎麼排版又跑掉了…

這個 function 傳進來的參數到底是 string 還是 number

來吧!這篇文章將你的痛處一次解決。

如果你剛好想配置專案的 Alias,但對於 ESlintTypeScript 怎麼設定毫無頭緒,也適合這篇文章的閱讀,最後文章會附上 Source Code

統一團隊的 Coding Style 吧,筆者認為 TS + ESLint + Prettier 可以稱為前端規範三劍客


Summary & 摘要

這篇文章預設讀者已經有基本的 npm install 能力,或是已經涉略過一點 CRA 框架。

本篇文章預設學習前的基本條件需求:

  • 會基本的英文
  • 會使用 npm install (或 yarn)
  • node 版本 >=16.0.0
  • 有一顆熱忱且耐心的心

本篇文章將會學到:

  1. 如何將團隊的 Coding Style 規範統一
  2. 解決一些可能會碰到的痛點
  3. 避免踩到關於規範套件的坑(也許會慢慢更新上來)

為何要寫這篇

最近在建置公司的新專案,有鑒於之前工作時,到後期多個前端一起開發後,常常不同的人有不同的 VSCode 配置,有的人希望使用雙引號,有的人希望使用單引號。

或許單引號雙引號是小事,或許你曾經碰過有人幫你 Code Review 後,修改了一點你的邏輯,但是你發現怎麼改了幾百幾千行?只因為他的縮排跟你不同

為了解決這個情況,在開始接觸 ESLint 後,筆者就決定之後的專案要使用了;而講到專案規範也不得不提 TypeScript,這次將會使用一個新的專案示範如何將 ESLint、TypeScript 還有 Prettier 摻在同一個專案變成完美多汁的沙尿牛丸。

順帶一提,這次不使用 CRA (Create React App) 建立普通專案是因為剛好在新的專案中筆者需要使用 Next,如果你要使用 CRA 或者其他 SSR、CSP 也是可以的,安裝起來其實大同小異。

另外處使這篇文章的產生也是因為筆者在配置 Alias 的路徑時發生一些錯誤,所以想將過程記錄下來。

備註:這邊需要提及一下,這篇文章將會適合初始化專案沒有使用預設推薦安裝的人,現在安裝 Next.js 的時候會詢問是否要一併安裝,下面將會說到兩種安裝方式。


Next 是什麼?

如果你已經會使用 Next 且知道這是什麼了,這段可以跳過

相信會看到這篇的讀者一定大部分都知道 Next 了,但可能有些讀者只有使用過 CRA,不用太擔心,如果你使用 CRA 也可以照著這篇的方式去安裝。

Next 是基於 React 的框架,同時支援 SSR (Server Side Rendering)SSG (Static Side Generation) 兩種方法,如果你還不清楚什麼是 SSR、SSG、CSP…等等的話,可以參考這篇 淺談 SPA、CSR、SSR、MPA、SSG 專有名詞 文章。

這篇文章就不著墨於這邊太多,簡單說一下通常會使用 Next.js 的絕大多情況,當你學習並且使用 CRA 在工作上都穩穩妥妥的,在某天風和日麗的情況下,老闆或 PM 突然跟你說 『欸欸,我們來優化一下網站的 SEO 吧!!』,相信大部分會開始研究 Next 的夥伴們都是從這邊開始的。

接下來就留給讀者去研究了,如果你有興趣也可以參考這篇鐵人賽文章 - 從零開始學習 Next.js,除了為什麼需要使用及 CSR、SSR 等等,也會介紹一系列的使用方式


安裝方式一

這是相對簡單的安裝方式,如果你是使用 CRA 或者你的專案已經存在一段時間,這時候想要中途加入這些套件,請參照安裝方式二。

首先初始化專案,這個步驟猶如 Hello World,都是各位將建立出偉大專案的第一步,參照 Next.js 官網的 Create Next App 安裝方式。

1
2
3
4
5
$ npx create-next-app@latest
# or
$ yarn create next-app
# or
$ pnpm create next-app

安裝方式一

輸入結束後,Next.js 將會詢問三個問題,分別是 專案名稱、是否使用 TypeScript 以及 是否使用 ESLint,如果你都接受使用,這就是安裝最簡單的方式。

專案架構

建立成功後會像上圖,該需要的設定檔都會出現了。

介紹完第二個安裝方式後,將會介紹如何去簡單配置一些設定,還有如何安裝 Prettier 及配置 Alias


安裝方式二

這個方式適用於專案初始時沒有安裝 TS、ESLint 的人,或許剛開始你的專案像下圖一樣起始時沒有安裝這些規範套件的。

安裝方式二

TypeScript

先確定路徑在專案內,接著先安裝 TypeScript,如果你還不知道 TS 是什麼可以參考 **什麼是 TypeScript**,這邊就不多做介紹。

在終端機內輸入底下指令:

1
$ yarn add -D typescript

這時候不會有什麼變化,在設定 TSESLint 的時候,都有一個共通點就是需要透過 Config 來設定,所以需要先初始化一個設定檔。

初始化設定檔

你可以選擇安裝全域的 TypeScript 之後使用指令初始化,也可以透過專案裡 node_modules 去初始化,如下指令:

  • 透過全域初始化
1
2
3
4
# 全域安裝 TS
$ yarn add -g typescript
# 在專案目錄底下初始化 TS
$ tsc --init
  • 透過 node_modules
1
2
# 在專案目錄底下初始化 TS 設定檔
$ ./node_modules/.bin/tsc --init

關於設定檔的內容配置,可以參考 TypeScript 的官方文件

專案架構

這時候看一下專案架構,會發現多了一個 tsconfig.json,往後關於一些 JS 的規範就可以設定在這裡面。

接下來我們需要手動把檔案改成 .tsx 結尾,這樣 TypeScript 才能監測並且檢查檔案有沒有符合規範。

把專案底下的檔案改完之後,會發現出現很多條紅色蚯蚓,剛開始使用 TS 或許會很頭痛,但是日後開發上會幫助很大。

錯誤一
錯誤二

仔細看一下修改完的檔案出現的錯誤,這時候就要開始去修改 tsconfig.json 的配置。

如果你使用 Next 先安裝一下幾個 types,有時候程式碼會報錯也是因為套件找不到該程式碼定義的 types

1
yarn add -D @types/node @types/react @types/react-dom

tsconfig.json 的配置改為下列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

然後重啟專案或者透過 command + p 打開 VSCode 的搜尋欄位,輸入 >TypeScript 會有一個重啟 TS 的選項可以選擇

重啟 TS

如果你是使用 Next.js 這時候會有第一個需要排解的地方,打開 pages/index.tsx 會發現引入 import styles from '../styles/Home.module.css' 底下出現紅紅的蚯蚓,要解決的方法就是在專案底下建立 next-env.d.ts,然後內容輸入:

1
2
3
4
5
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

CSS 的錯誤

這時候再重啟即可,之後的配置可以隨著專案需求慢慢新增或者修改。

ESLint

裝完 TS 接下來就是要安裝 ESLint,如果你還不知道這是什麼的話可以參考 **ESLint 是什麼?**。

打開終端機,確定目前路徑在專案底下,輸入以下指令:

1
2
3
4
$ yarn add -D eslint eslint-config-airbnb eslint-plugin-jsx-a11y

# 如果你的專案是 Next 才需要底下這行
$ yarn add -D eslint-config-next

是否要多安裝 Airbnb 可以看使用需求,這是一個 ESLint 的規範風格,裡面有定義好一些預設的規範,通常較多人使用,但是也比較嚴格,其餘還有 StandardGoogle 可以選擇,有興趣的讀者也可以餵狗(Google)搜尋一下或參考這篇文章 五分鐘快速替專案加上 ESLint 文章底下有介紹各種不同的風格差異,這邊就不多做介紹

接下來一樣要初始化 ESlint 的設定檔,你可以選擇安裝全域的 ESLint 之後使用指令初始化,也可以透過專案裡 node_modules 去初始化,如下指令:

  • 透過全域初始化
1
2
3
4
# 全域安裝 ESLint
$ yarn add -g eslint
# 在專案目錄底下初始化 ESLint
$ eslint --init
  • 透過 node_modules
1
2
# 在專案目錄底下初始化 ESLint 設定檔
$ ./node_modules/.bin/tsc --init

初始化 ESLint

初始化的時候會分別問一些 ESLint 的問題,這些依照專案需求即可,舉例有下面幾個問題,像是你想在什麼情況下使用 ESLint、專案是用什麼模式導入模組、專案使用哪個框架…等等

  • ✔ How would you like to use ESLint? · problems
  • ✔ What type of modules does your project use? · esm
  • ✔ Which framework does your project use? · react
  • ✔ Does your project use TypeScript? · No / Yes
  • ✔ Where does your code run? · browser
  • ✔ What format do you want your config file to be in? · JSON

The config that you’ve selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest

  • ✔ Would you like to install them now? · No / Yes
  • ✔ Which package manager do you want to use? · yarn

這時候再回去看專案,裡面會多出一支 .eslintrc.json,副檔名會依照 What format do you want your config file to be in? 這個問題而不同。

我們稍微修改一下裡面的設定,改為下面的設定,如果你想客製化可以參考 ESLint官網設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
// 有安裝 eslint-config-airbnb 才需要多配置 "airbnb"
"airbnb", "next/core-web-vitals"
],
"overrides": [
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
// 0 = off, 1 = warn, 2 = error
// suppress errors for missing "import React" in files
"react/react-in-jsx-scope": "off",
// file extension rule
"react/jsx-filename-extension": [
1,
{
"extensions": [".ts", ".tsx", ".css"]
}
],
"quotes": [2, "double"],
"jsx-quotes": ["error", "prefer-double"],
"prefer-arrow-callback": [2, { "allowNamedFunctions": true }],
"indent": [2, 2],
"semi": [2, "always"],
"operator-linebreak": [
2, "after", { "overrides": { "?": "before", ":": "before" } }
],
"max-len": [
2,
{
"code": 180,
"tabWidth": 2,
"ignoreComments": true,
"ignoreTemplateLiterals": true
}
],
"array-element-newline": [2, { "multiline": true }],
"array-bracket-newline": "off",
// eslint-disable-next-line no-dupe-keys
"arrow-body-style": "off",
"react/function-component-definition": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": "off",
"react/require-default-props": "off",
"react/jsx-no-useless-fragment": "off",
"no-shadow": "off",
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
}
}

這時候再回去開你的 /pages/index.tsx,會發現熟悉的紅色毛毛蟲又回來了。

ESLint 錯誤

如果使用 VSCode 可以把滑鼠指向紅色蚯蚓,點選 **快速修復…(⇧⌘ㄡ)**,就可以全部一次依照 ESLint 的設定檔規範修正

VSCode 一鍵修復

此外我們也可以修改一下 package.json 的配置,將 “scripts” 那塊加上兩行程式碼:

1
2
"lint": "next lint",
"lint:fix": "eslint --fix --ext .js,.jsx,.tsx ."

加入 ESlint 設定程式碼

接著在終端機輸入 yarn lintnpm run lint,就可以檢測程式碼有無符合規範,沒有就會出現下圖。

ESLint 檢測出錯誤

而透過 yarn lint:fixnpm run lint:fix 則是可以把整個專案包含 .js,.jsx,.tsx 的檔案一次修復。

修復成功如果都無誤就會出現 ✔ No ESLint warnings or errors


Prettier

雖然我們已經有了程式碼的書寫規範,但身為工程師還是會想到更懶的問題,為什麼不能在儲存的時候就幫我們排版排好呢?還要一個一個解決問題或點 快速修復…(⇧⌘ㄡ) 太麻煩了吧。

這時候就要發揮工程師精神,讓我們繼續往下安裝:

1
$ yarn add -D prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react-hooks

裝完後接著在新建兩支檔案,分別為 .prettierrc.prettierignore,前者同樣為設定檔,後者為 prettier 要忽略不涵蓋的檔案。

.prettierrc 的內容改為:

1
2
3
4
5
6
7
8
9
10
11
{
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 180,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"bracketSpacing": true,
"endOfLine": "auto",
"arrowParens": "avoid"
}

.prettierignore 的內容改為:

1
2
3
4
5
6
7
8
package.json
package-lock.json
yarn.lock
dist
public
node_nodules/
# 如果專案是 Next.js 才需要配置
.next

然後把 package.jsonscripts 做一下修改:

1
2
3
4
5
6
7
8
9
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
// 加入下面三行
"lint": "next lint",
"lint:fix": "eslint --fix --ext .js,.jsx,.tsx .",
"format": "prettier --write --ext .js,.jsx,.tsx --config ./.prettierrc"
},

之後我們就可以先用 yarn lint 檢查程式碼是否符合規範,然後再用 yarn lint:fix 去全域依照 ESLintPrettier 去修正程式碼。

說好的自動修正呢?

你一定會有疑問,不是說好要在儲存後就自動修正嗎?沒錯,如果你是使用 VSCode 的話,請在專案底下新增 .vscode/settings.json,然後在 settings.json 裡面寫上下面的內容:

1
2
3
4
5
6
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": ["javascript"]
}

之後儲存就會自動修正了!


Alias

其實 Alias 才是促使本篇產出的重點,先說說這是什麼,以往我們在專案中會引入一些靜態文件或者 Components,大部分使用的時候我們都是會寫 import Header from '../../components/Header',但是當我們檔案一多,資料夾一多,就可能會產生出 import Header from '../../../../../components/Header',在另一個檔案可能又變成 import Header from '../../../components/Header',可能這個例子比較誇張一點,但這樣確實不美觀且寫起來不舒服啊。

所以就有了 Alias 可以使用,使用了之後上面的情況可以統一變成 import Header from '@components/Header',這就是模板的概念。

首先我們修改一下 tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"compilerOptions": {
"baseUrl": ".",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@components/*": [
"components/*"
],
"@pages/*": [
"pages/*"
],
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

多了 baseUrlTS 知道我們的基本路徑從哪裡開始,下面的 paths 則是設定 Alias 的部分,我們先這樣設定,然後在專案底下多建立一個 components 的資料夾,裡面建立一支 Header.tsx 的檔案,內容打上:

1
2
3
4
5
const Header = () => {
return <>Header</>;
};

export default Header;

目前專案架構會如下圖

專案架構

然後回到 /pages/index.tsx 或者隨便一個 .tsx 檔案裡面,在最上面輸入 import Header from "@components/Header";,會發現紅色蚯蚓又出來了。

踩坑之旅

這次碰到最大的問題就是關於引入報錯,依照 ESLint 检查 TypeScript 时报 “Unable to resolve path to module ‘xxx’” 错误 一文指出 『eslint-plugin-import 原本只支持 ES6 模块,从 README 文档也可以看出默认只支持 .js, .jsx 后缀的这类 JS 社区常见的文件。TypeScript 使用的 .ts 和 .tsx 自然是不支持的,更不用说 import 插件还需要相应的 parser 来解决 TypeScript 解析问题。』 我們可以了解到如果使用了 TSESLint 再去使用 Alias 會被紅色蚯蚓噴得體無完膚。

引入錯誤

目前 ESLint 不認得這個路徑,所以我們必須讓它認得,我們只需要安裝幾個套件,讓程式碼規範套件讀得懂即可:

1
$ yarn add -D eslint-import-resolver-alias eslint-import-resolver-typescript eslint-plugin-import

上面的安裝是為了解決 ESLint 不認得我們在 TS 設定 Alias 的錯誤,安裝完畢後修改 ..eslintrc.json 的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"airbnb",
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"import",
"react",
"@typescript-eslint",
"react-hooks",
"prettier"
],
"settings": {
// 解決 eslint-plugin-import 只支持 ES6 模块的問題,原本只支援 .js, .jsx
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"]
},
//
"import/resolver": {
"typescript": {
"alwaysTryTypes": true
}
}
},
"rules": {
// ...略
}
}

透過上面的修改後,就可以正常的讓 TypeScriptESLint 讀懂我們設定的 Alias 模板,也能知道我們要引入的檔案是不是真的存在了。


總結

TS + ESLint + Prettier 對於前端可以設置一個很好的團隊規範,也能有效地防止每個人有每個人的寫法造成 Code Review 需要去花更多時間解決不必要的衝突。

有了 ESLint 我們也可以搭配之前文章 [Git Note] - 統一團隊的 Git Commit 格式吧!不要再讓 Commit 亂糟糟 裡提過的 Husky,在我們做 pre-commit 的時候就先去幫我們跑一次 Lint,如果不符合規範就拒絕開發者 git push,實在時居家旅行….

必備良藥


Conclusion & 結論

隔了好一段時間沒有寫文章了,這段時間剛好經歷了工作的變動,一些生活上的調整等等,好一段時間都還在思索接下來的規劃。

剛好到了新的工作崗位上碰到了更多新的挑戰,最近卡在這個 Alias 上一段時間,當下心裡第一時間想的是一定要把它記錄回來部落格,希望有人因為這個卡關時,可以剛好看到這篇文章。

其中還是要特別謝謝我的大學室友,沒有他可能我這個問題還在卡關呢。

這次因為新專案加上朋友委託筆者幫忙建置 Electron 的緣故,碰到了一直很想摸索的 Next.js,我想接下來又有更多的東西可以去玩了,下次有機會就來介紹一下 Electron 這個酷東西。

Source Code: 請點我


參考網站