[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