﻿# 如何快速建置一個 Node.js 專案並使用 TypeScript 與 VSCode 進行開發

每次要開始寫一個 [Node.js](https://nodejs.org/) 搭配 [TypeScript](http://typescriptlang.org/) 的專案，都沒有一個能讓自己滿意的專案範本，不然就是網路上經常找到不完整的參考文件。這篇文章我打算建立一個 Node.js 搭配 TypeScript 進行開發的專案範本，完整交代整個建置過程，並用一個簡單的例子說明開發與執行的注意事項。

### TL;DR (Too Long; Don't Read)

如果你是個大忙人，可以直接使用我寫好的專案範本，跳過以下所有內容：

1.  使用 [.NET CLI](https://dotnet.microsoft.com/download) 命令列工具，安裝我寫好的專案範本
    
    ```bash
    dotnet new --install Duotify.Templates.DotNetNew
    ```
    
2.  使用 `dotnet new tsnode` 建立專案範本並執行 `npm install` 安裝 npm 套件
    
    ```bash
    mkdir punycode-converter && cd punycode-converter
    dotnet new tsnode -c "Will 保哥"
    npm install
    ```
    
3.  開啟 Visual Studio Code 並對 `src/app.ts` 檔案進行開發
    
    ```bash
    code .
    ```
    
4.  按下 `Ctrl+Shift+B` 即可自動執行 `npm start` 命令
    
    程式會自動監視檔案變更並自動重啟程式！
    

### 準備 Windows 命令列環境

要準備好一個 Node.js 與 TypeScript 的開發環境，你大致需要以下工具：

1.  [Node.js](https://nodejs.org/)
    
2.  [PowerShell Core](https://github.com/PowerShell/PowerShell/releases) (在 Windows 唯一推薦的命令列工作環境)
    
    > 事實上 PowerShell Core 是跨平台的 Shell 環境，只是 Linux 使用者很少人用
    
3.  [UnxUtils](http://unxutils.sourceforge.net/) (常用的 GNU utilities 工具)
    
4.  [curl](https://curl.se/) (模擬 HTTP 命令與下載檔案的好工具)
    
5.  [jq](https://stedolan.github.io/jq/) (一個專門用來操作 JSON 資料的強大工具)
    

以下可以用單一命令將環境全部自動裝好：

```bash
choco install nodejs-lts powershell-core unxutils curl jq -y
```

### 建立 Node.js with TypeScript 專案範本

以下命令同時可以相容於 Linux Shell 與 PowerShell Core 執行環境：

> 請不要用 **Command Prompt** 或 Windows 內建的 **PowerShell** 來執行以下程式碼，使用上地雷太多了，不但使用 Pipe 的時候可能會有檔案編碼問題，命令的輸入方式還無法跨平台相容。以下命令我測試過無數次，可完美相容於於 Linux, macOS 常見的 Shell 環境，請記得額外安裝 [curl](https://curl.se/) 與 [jq](https://stedolan.github.io/jq/) 工具即可！👍

```bash
mkdir punycode-converter
cd punycode-converter

npm init -y
npx typescript --init
npx tslint --init

echo 'loglevel=silent' > .npmrc

npm install typescript @types/node tslint nodemon ts-node --save-dev

npx strip-json-comments-cli tsconfig.json | jq '.' | npx sponge tsconfig.json
jq --arg     'outDir' './dist' '.compilerOptions.outDir = $outDir' tsconfig.json | npx sponge tsconfig.json
jq --argjson 'strict' 'false'  '.compilerOptions.strict = $strict' tsconfig.json | npx sponge tsconfig.json

jq --arg 'severity' 'warning' '.defaultSeverity = $severity' tslint.json | npx sponge tslint.json

curl -s https://www.toptal.com/developers/gitignore/api/node > .gitignore

npx node-editorconfig

jq 'del(.scripts.test)' package.json | npx sponge package.json

jq --arg cmd 'nodemon src/app.ts'      '.scripts.start = $cmd' package.json | npx sponge package.json
jq --arg cmd 'tsc'                     '.scripts.build = $cmd' package.json | npx sponge package.json
jq --arg cmd 'tsc && node dist/app.js' '.scripts.node  = $cmd' package.json | npx sponge package.json

mkdir src
touch src/app.ts

npm start
```

### 詳細解釋建立 Node.js with TypeScript 專案範本的過程

1.  建立專案
    
    請注意目錄名稱的命名規則必須符合 npm 套件名稱的[命名規範](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#name)：
    
    -   套件名稱總長度**不得超過 214 字元**
    -   套件名稱**不能**有任何**英文大寫**字元
    -   套件名稱**不能**違反 URL 可用字元的規則
    
    簡單來說，建議都用小寫字元(`[a-z]`)與減號(`-`)就好！
    
    ```bash
    mkdir punycode-converter
    cd punycode-converter
    ```
    
    > 如果專案不會發布到 [npm registry](https://www.npmjs.com/) 的話，那麼 `name` 是什麼其實不重要！
    
2.  初始化 `package.json` 檔案
    
    ```bash
    npm init -y
    echo 'loglevel=silent' > .npmrc
    ```
    
    > 我設定 `loglevel=silent` 到 `.npmrc` 的目的是不希望 `npm start` 的時候看到一堆沒用的 log 訊息！
    
3.  安裝 TypeScript 必備的 npm 套件
    
    ```bash
    npm install typescript @types/node tslint nodemon ts-node --save-dev
    ```
    
    > 套件 `typescript` 為 TypeScript 編譯器本身。套件 `@types/node` 是 Nodejs 的 TypeScript 型別定義檔(declaration file)。套件 `tslint` 則是 TypeScript 的 Linting 工具，檢查各種 TypeScript 寫作風格。`nodemon` 用來監控程式碼變更，自動重新執行 Node.js 程式。`ts-node` 用來直接執行 `*.ts` 程式，省去手動執行 `tsc` 編譯的過程。
    
4.  初始化 `tsconfig.json` 設定檔
    
    這個 `tsconfig.json` 設定檔主要用來幫助 TypeScript 編譯器判斷該如何編譯我們的專案。
    
    ```bash
    npx typescript --init
    ```
    
    在 `tsconfig.json` 檔案中包含的大量註解，這些註解極具參考價值，但你可以視情況清除這些註解。
    
    ```bash
    npx strip-json-comments-cli tsconfig.json | jq '.' | npx sponge tsconfig.json
    ```
    
    初始化 `tsconfig.json` 之後，我會修改 `tsconfig.json` 的 `compilerOptions.outDir` 屬性，讓 TypeScript 編譯好的結果輸出到 `./dist` 目錄。
    
    ```bash
    jq --arg 'outDir' './dist' '.compilerOptions.outDir = $outDir' tsconfig.json | npx sponge tsconfig.json
    ```
    
    由於 `tsconfig.json` 預設採用「嚴格型別檢查」功能，有時候我會把 `tsconfig.json` 的 `compilerOptions.strict` 設定為 `false`，以免型別檢查太過嚴格影響開發體驗！ XD
    
    ```bash
    jq --argjson 'strict' 'false' '.compilerOptions.strict = $strict' tsconfig.json | npx sponge tsconfig.json
    ```
    
5.  初始化 `tslint.json` 設定檔
    
    ```bash
    npx tslint --init
    ```
    
    你可以考慮設定 TSLint 檢查出來的問題標示為「警告」而非「錯誤」！
    
    ```bash
    jq --arg 'severity' 'warning' '.defaultSeverity = $severity' tslint.json | npx sponge tslint.json
    ```
    
6.  初始化 `.gitignore` 設定檔 ([gitignore.io](https://www.toptal.com/developers/gitignore))
    
    ```bash
    curl -s https://www.toptal.com/developers/gitignore/api/node > .gitignore
    ```
    
7.  初始化 `.editorconfig` 編輯器設定檔 ([node-editorconfig](https://www.npmjs.com/package/node-editorconfig))
    
    ```bash
    npx node-editorconfig
    ```
    
8.  修改 `package.json` 的 `scripts` 區段，增加一個 `npm start` 的執行命令
    
    有個 `scripts.test` 屬性只是個範例，必要時可刪除：
    
    ```bash
    jq 'del(.scripts.test)' package.json | npx sponge package.json
    ```
    
    設定執行 `tsc` 會自動編譯專案目錄下所有 `*.ts` 檔案並輸出到 `dist/` 目錄，接著直接透過 `node` 啟動該程式！
    
    ```bash
    jq --arg var 'tsc && node dist/app.js' '.scripts.start = $var' package.json | npx sponge package.json
    ```
    
    修改之後 `packages.json` 的 `scripts` 區段會變成以下內容：
    
    ```json
    "scripts": {
      "start": "tsc && node dist/app.js"
    },
    ```
    
9.  改用 [nodemon](https://github.com/remy/nodemon) 啟動程式
    
    每次啟動 Node.js 程式都要執行一次 `npm start` 其實有點不太方便，因為在開發時期一直在更新程式碼，我們的程式需要不斷頻繁的重新啟動。
    
    此時最好的方法，就是有個工具可以不斷監視資料夾中是否有原始碼更新，只要有檔案異動，就自動重新啟動 Node.js 程式，此時就是 [nodemon](https://github.com/remy/nodemon) 與 [ts-node](https://github.com/TypeStrong/ts-node) 上場的時候！當你用 `nodemon` 執行一個 `*.ts` 程式，他在內部就會自動使用 `ts-node` 自動編譯這些程式，並自動透過 `node` 執行起來，當有程式異動存檔，也會自動重啟程式，非常方便！
    
    ```bash
    jq --arg cmd 'nodemon src/app.ts'      '.scripts.start = $cmd' package.json | npx sponge package.json
    jq --arg cmd 'tsc'                     '.scripts.build = $cmd' package.json | npx sponge package.json
    jq --arg cmd 'tsc && node dist/app.js' '.scripts.node  = $cmd' package.json | npx sponge package.json
    ```
    
    所以我們的 `packages.json` 的 `scripts` 區段，就會變成以下內容：
    
    ```json
    "scripts": {
      "start": "nodemon src/app.ts",
      "build": "tsc",
      "node": "tsc && node dist/app.js"
    },
    ```
    
10.  建立主程式的原始碼資料夾
     
     ```bash
     mkdir src
     touch src/app.ts
     ```
     
11.  加入 Visual Studio Code 的 `.vscode/tasks.json` 工作定義檔
     
     以下定義檔可以讓你按下 `Ctrl+Shift+B` 就會自動執行 `npm start` 命令，非常便利！
     
     ```json
     {
         "version": "2.0.0",
         "tasks": [
             {
                 "label": "npm start",
                 "type": "shell",
                 "command": "npm start",
     
                 "group": {
                     "kind": "build",
                     "isDefault": true
                 },
                 "presentation": {
                     "showReuseMessage": false,
                     "reveal": "always",
                     "clear": true,
                     "focus": true
                 }
             }
         ]
     }
     ```
     

### 開發一段簡短的 Node.js 程式

我打算建立一個 [Punycode](https://en.wikipedia.org/wiki/Punycode) (國際化域名編碼) 的轉換工具，讓你可將任意 Punycode 轉成 Unicode 字元，或是將 Unicode 字元轉成 Punycode 編碼。

1.  安裝會用到的套件與 TypeScript 型別定義檔
    
    這裡我們會使用 [Punycode.js](https://github.com/bestiejs/punycode.js) 模組來完成這個應用程式。
    
    ```bash
    npm install punycode --save
    npm i @types/punycode --save-dev
    ```
    
    ```bash
    npm install express@4.17 --save
    npm install @types/express@4.17 --save-dev
    ```
    
    > 你可以到 [TypeScript: Search for typed packages](https://www.typescriptlang.org/dt/search) 搜尋上萬個現成的 TypeScript 型別定義檔
    
2.  正確的在 `src/app.ts` 載入強型別的模組
    
    一般 Node.js 程式都是用以下方式匯入 CommonJS 模組
    
    ```bash
    const punycode = require('punycode/');
    ```
    
    但是在 TypeScript 要換用 `import` 的方式匯入模組才能參考到正確的型別
    
    ```bash
    import punycode = require('punycode/');
    ```
    
    另外用 ESM (ES Modules) 語法也是可以的
    
    ```bash
    import * as punycode from 'punycode/';
    ```
    
    ![punycode](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/2e9db421cdf341fa274d-125117505-ec9f5480-e120-11eb-8367-ac647ef84eb7.jpg)
    
    > 備註：上述範例你看到我匯入的模組名稱是 `punycode/`，各位可能會覺得有點奇怪，其實是因為 punycode 模組一直都是 Node.js 內建的模組，但是從 `v7.0.0` 以來，就一直處於 Deprecated (退役) 狀態，官方是說遲早有一天會從 Node.js 中移除，但是至今都尚未移除。短時間內只能透過在名稱後面加上 `/` 當作暫時的緩解措施(Workaround)，否則你會看到如下圖的訊息提示： ![2021-07-18\_21-05-07](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/e02cc8a4e83d2a6de712-126068112-9da8b792-5954-4116-b883-f9b41c90aa87.jpg)
    
3.  只要能正確匯入模組，撰寫程式的時候就可以享受 IntelliSense 提示與型別檢查
    
    ```typescript
    console.log(punycode.version);
    console.log(punycode.toUnicode('xn--cssyd'));
    console.log(punycode.toASCII('多奇'));
    ```
    
    ![function punycode.toUnicode(input: string): string](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/e281811b60235da51ec5-126068013-ac39367f-8282-459e-91a0-02c230c21cb7.jpg)
    
4.  執行程式
    
    ```bash
    npm start
    ```
    
    > 在 Visual Studio Code 中可以按下 `Ctrl+Shift+B` 快速鍵，就可以直接啟動程式
    
5.  開發一段簡短的 Express 程式
    
    請在 `src/app.ts` 填入以下程式碼，過程中可以發現每個變數都有完整的型別標示
    
    ```typescript
    import punycode = require('punycode/');
    import express from 'express';
    
    const app = express();
    const port = 3000;
    
    // http://localhost:3000/toUnicode?str=xn--cssyd
    app.get('/toUnicode', (req, res) => {
        res.send(punycode.toUnicode(req.query['str'].toString()));
    });
    
    // http://localhost:3000/toASCII?str=多奇
    app.get('/toASCII', (req, res) => {
        res.send(punycode.toASCII(req.query['str'].toString()));
    });
    
    app.listen(port, () => {
        console.log(`server is listening on ${port}`);
    });
    ```
    
    ![res.send(punycode.toASCII(req.query\['str'\].toString()));](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/75275aba2bb12d2d7ec8-126068898-93084281-359a-4d2c-ae97-202d0d532e3c.jpg)
    
    > 在 Visual Studio Code 中可以按下 `Ctrl+Shift+B` 快速鍵，就可以直接啟動程式。啟動後 `nodemon` 會自動監視資料夾下的檔案變更，有檔案儲存就會自動重啟程式！
    

### 完整的範例程式

> https://github.com/doggy8088/punycode-converter

### 相關連結

-   [remy/nodemon: Monitor for any changes in your node.js application and automatically restart the server - perfect for development](https://github.com/remy/nodemon)
-   [TypeStrong/ts-node: TypeScript execution and REPL for node.js](https://github.com/TypeStrong/ts-node)
-   [How To Set Up a Node Project With Typescript | DigitalOcean](https://www.digitalocean.com/community/tutorials/setting-up-a-node-project-with-typescript)
-   [bestiejs/punycode.js: A robust Punycode converter that fully complies to RFC 3492 and RFC 5891.](https://github.com/bestiejs/punycode.js)
-   [Express - Node.js web application framework](https://expressjs.com/)
-   [DefinitelyTyped/types/punycode at master · DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/punycode)
-   [TypeScript: Search for typed packages](https://www.typescriptlang.org/dt/search)
-   [jq play](https://play.jqlang.org/)
