[TS] Setup TypeScript Template
請到:pjchender/typescript-template @ pjchender github
Usage
npm start # run src/index.ts
npx ts-node src/foobar.ts # run TS file directly
- Professional TS @ Github
- Production-Grade TypeScript @ Frontend Masters
TL;DR
- TypeScript 是使用
tsc
來編譯 TypeScript 檔案,會吃到的設定檔是tsconfig.json
。當在 VSCode 中看到 ts 的錯誤時,就是由 tsc 產生。 tsconfig.eslint.json
是給 @typescript-eslint 吃的 tsconfig 檔,它會繼承tsconfig.json
。當在 VSCode 中出現 @typescript-eslint 的錯誤就是由它產生的。- 出現
Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
的錯誤時,表示 ESLint 想要解析的檔案並沒有被列在給 @typescript-eslint 所涵括到的檔案中,解決方式可以參考 I get errors telling me "The file must be included in at least one of the projects provided" @ typescript-eslint
Project Implementation
Make TS Setup @ Github
添加 gitignore
直接使用 npx gitignore node
來添加給 NodeJS 使用的 gitignore 到專案中:
git init
npx gitignore node
安裝套件
yarn init --yes
yarn add -D typescript eslint jest
定義 package.json
// package.json
{
"types": "dist/index.ts", // 讓使用此套件的人知道 Type 的定義檔在哪
"scripts": {
"build": "tsc",
"dev": "yarn build --watch --preserveWatchOutput" // preserveWatchOutput 可以避免檔案變更時 console 中的內容被清空,
}
}
設定 tsconfig
yarn tsc --int
修改 tsconfig.json
中的內容:
{
"compilerOptions": {
"target": "ES2018", // 打包後要支援到的 ECMAScript 版本
// 如果沒設定會套用預設值(TS 自動判斷)
// 有「機會」導致 src 的資料夾也一起被 build 到 dist 資料夾中
// 例如 TS 自動把 rootDir 判斷為 { "rootDir": "." }
"rootDir": "src",
// 預設 tsc 會直接把編譯好的 js 檔放在與 ts 檔相同的路徑,但這樣檔案會很散亂,因此全部放到 dist
"outDir": "dist",
"stripInternal": true, // 針對加上 @internal 的方法不會產生對應的 Type Declaration 檔(例如針對測試寫的函式)
"types": [], // 在編譯時要一起包含的 declaration 檔
/* 下面這兩個項目之所以關閉,是因為一但開啟,其他使用本套件的開發者也需要被迫開啟 */
"esModuleInterop": false,
"skipLibCheck": false
},
"include": ["src"]
}
設定 ESLint
yarn eslint --init
- How would you like to use ESLint? --> To check syntax and find problems(根據個人選擇)
- What type of modules does your project use --> None of these(因為我們用的是 TS 而非 JS Modules)
- Which framework does your project use? --> None of these
- Does your project use TypeScript? --> Yes
- Where does your code run? --> Node
- What format do you want your config file to be in? --> JS
設定 @typescript-eslint
由於 @typescript-eslint 希望一個 TypeScript 設定檔就能涵括到所有想要 lint 的檔案,但因為測試時使用的環境會是不同的,所以我們必須額外建立一個檔案 tsconfig.eslint.json
tsc
編譯 TypeScript 時(和 VSCode 中看到的 ts 錯誤提示),實際上是根據 tsconfig.json
的設定;tsconfig.eslint.json
則是給 @typescript-eslint 用來解析型別用的(會繼承 tsconfig.json
中的設定),也就是 VSCode 中看到的 eslint 錯誤提示。
可以參考這兩篇:
- note-eslint @ pjchender.dev
- I get errors telling me "The file must be included in at least one of the projects provided" @ typescript-eslint
另外可以參考 @typescript-eslint 提供的設定檔 .eslintrc.js
和 tsconfig.eslint.json
。
設定 testing
方法一:@babel/preset-typescript
在這個範例中,TS 檔案測試的執行是使用 @babel/preset-typescript
,而 TypeScript 本身的編譯器(tsc)並不會去執行測試的檔案。另外,當有寫 .babelrc
時,Jest 預設就會去吃這支檔案。
除了使用 @babel/preset-typescript 來執行測試外,另一種方式是使用 ts-jest。
安裝對應的套件:
yarn add -D jest @types/jest @babel/preset-env @babel/preset-typescript
-
babel 是用來讓 jest 能夠解析
.ts
的檔案 -
建立
.babelrc
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "14"
}
}
],
"@babel/preset-typescript"
]
} -
建立
tests/tsconfig.json
(不建好像也可以)
透過 npx tsc --showConfig
可以檢視最終吃到的設定檔。
方法二:使用 ts-jest
Testing with jest in TypeScript @ medium
如果想要使用 ts-jest 的話,只需:
npm install ts-jest
npx ts-jest config:init
這時會新建一支 jest.config.js
的檔案,eslint 會試著去處理它,但它並沒有被包含在 tsconfig.eslint.json
中設定的路徑,因此又會出現 Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
此錯誤。
一樣只需要把該檔案放到 tsconfig.eslint.json
的 includes 中即可:
--- a/tsconfig.eslint.json
+++ b/tsconfig.eslint.json
@@ -8,6 +8,7 @@
"include": [
"src/**/*",
"tests/**/*",
- ".eslintrc.js"
+ ".eslintrc.js",
+ "jest.config.js"
]
}
ts-jest 是使用 tsc 來編譯 TypeScript 檔案而不是使用 @babel/preset-typescript,因此會吃的設定檔是 tsconfig.json
,在原本 tsconfig.json
的設定中,需要進行如下修改:
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -49,7 +49,7 @@
definitions from. */
- "types": [],
+ "types": ["jest"], // 讓 TS 認得 Jest
},
}
常見問題
設定 import alias / absolute import
tsconfig
TypeScript 可以透過以下設定來達到 import alias 的效果:
// tsconfig.json
{
// 將 @ 設為 src
"compilerOptions": {
"rootDir": "src",
"baseUrl": "." /* Base directory to resolve non-absolute module names. */,
"paths": {
"@/*": ["src/*"]
}
},
// 將 @ 設為「專案根目錄」
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
eslint
除了 tsconfig 要設定外,因為有用 eslint 的關係,因此也要讓 import/resolver
認識設定好的 import alias,才不會 ESlint 一直報錯誤,這時候可以搭配 eslint-import-resolver-babel-module
:
$ npm install --save-dev eslint-import-resolver-babel-module babel-plugin-module-resolver
並且在 .eslintrc.js
中加上以下設定:
// .eslintrc.js
const allExtensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx', '.json'];
module.exports = {
// ...
settings: {
node: {
extensions: allExtensions,
},
+ // 將 @ 指向 src
+ 'import/resolver': {
+ 'babel-module': {
+ alias: {
+ '@': './src',
+ },
+ extensions: allExtensions,
+ },
+
+ // 將 @ 指向專案根目錄
+ 'import/resolver': {
+ 'babel-module': {
+ alias: {
+ '@': './',
+ },
+ extensions: allExtensions,
},
},
},
},
}
使用 tsconfig-paths 解決 ts-node 無法 resolve 的問題
如果我們直接使用 ts-node 來執行包含 import alias 的檔案時,它會無法解析,這時候需要 tsconfig-paths 的幫忙:
$ npm install --save-dev tsconfig-paths
接著在使用 ts-node 時加上 tsconfig-paths 即可:
$ npx ts-node -r tsconfig-paths/register src/index.ts
或者放到 package.json 中:
{
"scripts": {
"start": "ts-node -r tsconfig-paths/register src/index.ts"
}
}
支援用 node 執行編譯過的檔案(無法同時支援 ts-node)
當我們透過 node dist/index.js
直接執行打包好的 js 檔時會發上錯誤:
這是因為透過 tsc 打包過的檔案並不會針對 import alias 進行處理(參考:Module path maps are not resolved in emitted code #10866):
這時候會需要安裝可以透過 module-alias 這個套件:
$ npm i --save module-alias
在 package.json
中進行設定:
diff --git a/package.json b/package.json
index cec3cf4..fb55264 100644
--- a/package.json
+++ b/package.json
@@ -46,5 +46,11 @@
"*.{js,jsx,ts,tsx}": [
"eslint --fix"
]
+ },
+ "dependencies": {
+ "module-alias": "^2.2.2"
+ },
+ "_moduleAliases": {
+ "@": "dist"
}
}
接著在專案的 entry file(例如,main.ts
或 index.ts
)載入這個套件:
diff --git a/src/index.ts b/src/index.ts
index 1a565ed..0f3ad68 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,5 @@
+import 'module-alias/register';
+
import { example1, example2, example3 } from './examples/for-async-await';
function main() {
重新打包 ts 檔案後即可使用 node 執行:
$ tsc # build ts files
$ node dist/index.js # run compiled files
使用 module-alias
雖然可以讓 node 執行 dist 中打包好的檔案,但卻會讓 ts-node
無法正確執行,因此若想使用 node-ts
直接執行 index.ts
的話,需要把 import 'module-alias/register';
拿掉。
該使用 TSC 或 Babel 編譯 TypeScript 檔
Using Babel with TypeScript @ TypeScript
一個簡單的原則是:
- 如果你打包後的檔案和 source files 是很接近的,使用
tsc
- 如果需要透過 build pipeline 產生編譯後不同的檔案(例如,
.esm
,cjs
),那麼使用babel
來編譯 TypeScript 檔,並透過tsc
來做型別檢查。
使用 Babel 提供的 preset-typescript
來進行編譯的好處在於它可以和專案原本的 pipeline 結合,並且打包所需的時間較短,因為在編譯時 Babel 不會進行型別檢查。
但相對地,也因為 Babel 在編譯時不會進行型別檢查,型別檢查的過程完全是透過 Editor,因此如果你忽略了 Editor 上的 TS 錯誤,還是可以正常編譯,這可能會導致你忽略了型別上的錯誤。另一方面,Babel 也不會產生 .d.ts
的型別定義檔,若你開發的是套件,那麼使用你套件的開發者將無法取得對應的型別資訊。
為了要解決這樣的問題,最常見的方式就是「透過 Babel 來編譯 TypeScript 檔,並搭配 tsc 來進行型別檢查和產 生 .d.ts
檔」。為了達到這樣的效果,確保在 tsconfig 中的下述設定:
// tsconfig.json
"compilerOptions": {
// Ensure that .d.ts files are created by tsc, but not .js files
"declaration": true,
"emitDeclarationOnly": true,
// Ensure that Babel can safely transpile files in the TypeScript project
"isolatedModules": true
}
Reference
- Professional TS @ Github
- Production-Grade TypeScript @ Frontend Masters