The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

建立 Node.js 使用 ESM 搭配 TypeScript, ESLint 與 VSCode 的專案範本

我在兩年前曾經寫過一篇如何快速建置一個 Node.js 專案並使用 TypeScript 與 VSCode 進行開發文章,由於現在的 Node.js 大多以 ESM 為主流,TypeScript 也更新到 5.x 版,而且當時的 TSLint 也都被換成 ESLint 為主了,所以這篇文章就來更新一下內容,幫助大家更方便、更快速的建立一個支援 TypeScript, ESLint, VSCodeESM 並搭配 esbuild 的專案範本。

image

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

依照慣例,沒空看文章的人可以直接使用我寫好的 Duotify.Templates.DotNetNew 專案範本,快速建立專案:

  1. 使用 .NET CLI 命令列工具,安裝我寫好的專案範本

    dotnet new install Duotify.Templates.DotNetNew
    
  2. 使用 dotnet new tsnode-esm 建立專案範本並執行 npm install 安裝 npm 套件

    mkdir myproj && cd myproj
    dotnet new tsnode-esm -c "Will 保哥"
    npm install
    
  3. 開啟 Visual Studio Code 並對 src/app.ts 檔案進行開發

    code .
    
  4. 按下 Ctrl+Shift+B 即可自動執行 npm start 命令

    程式會自動監視檔案變更並自動重啟程式!

如何設定 TypeScript Node.js 與原生的 ESM 模組支援

這篇文章我就不會重頭建立起專案了,完整的建立步驟依然可以參見 如何快速建置一個 Node.js 專案並使用 TypeScript 與 VSCode 進行開發 文章的說明。

要設定 TypeScript 搭配 Node.js 並使用 ESM 模組系統,最有可能先遇到的問題,就是以下錯誤:

Error [ERR_REQUIRE_ESM]: require() of ES Module not supported

而這個錯誤必須靠許多設定上的調整來解決,老實說,知識含量有點大,以下是大致的更新步驟!

  1. 首先,我們必須在 package.json 檔案中加入以下設定:

    {
      "name": "my-project",
      "type": "module",  // <-- 加入這一段
    }
    

    這段設定可以讓 Node 在執行程式時,主要採用 ESM 為預設模組系統。

  2. 所有的 import 語法載入專案內的 *.ts 檔案都一定要這樣寫:

    import { qux } from './bar/baz.js'; // <-- 注意: 副檔名一定要加上 .js 結尾
    
  3. 更新你的 tsconfig.json 設定檔

    這裡我們會參考到 https://github.com/tsconfig/bases 這個專案,這裡有寫好一堆常用的 tsconfig.json 設定檔,其中也包含 Node LTS 的環境,我們可以直接從自己專案的 tsconfig.json 參考並繼承這裡的設定檔,就可以大幅簡化我們的專案內的 tsconfig.json 設定。

    先安裝支援 Node LTS 的 npm 套件:

    npm install --save-dev @tsconfig/node-lts
    

    然後修改你的 tsconfig.json 繼承 @tsconfig/node-lts/tsconfig.json 設定檔:

    "extends": "@tsconfig/node-lts/tsconfig.json"
    

    附帶補充一下我從 node_modules\@tsconfig\node-lts\tsconfig.json 看到的設定內容如下:

    // This file was autogenerated by a script
    // Equivalent to a config of: node18
    {
      "$schema": "https://json.schemastore.org/tsconfig",
      "display": "Node LTS",
      "_version": "18.1.0",
      "compilerOptions": {
        "lib": ["es2023"],
        "module": "node16",
        "target": "es2022",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "moduleResolution": "node16"
      }
    }
    

    所以我們的 tsconfig.json 就只需要這樣設定即可:

    {
        "$schema": "https://json.schemastore.org/tsconfig",
        "extends": "@tsconfig/node-lts/tsconfig.json",
        "compilerOptions": {
            "resolveJsonModule": true,
            "outDir": "./dist"
        },
        "include": [
            "src/**/*"
        ]
    }
    
  4. 設定 ts-node 可以支援 ESM

    加入以下設定到 tsconfig.json 之中:

    {
        ...,
    
        "ts-node": {
            "esm": true
        }
    }
    

    目前我們的 tsconfig.json 設定如下:

    {
        "$schema": "https://json.schemastore.org/tsconfig",
        "extends": "@tsconfig/node-lts/tsconfig.json",
        "compilerOptions": {
            "resolveJsonModule": true,
            "outDir": "./dist"
        },
        "include": [
            "src/**/*"
        ],
        "ts-node": {
            "esm": true
        }
    }
    

    注意: 目前 ts-node 尚未支援 TypeScript 5.0 的 moduleResolution: bundler 設定,你可以到 moduleResolution bundler support #2034 追蹤這個議題。這個 moduleResolution: bundler 設定可以讓你的程式在 import 時,在 from 的地方不用加上 .js 副檔名,例如:import { version } from "./utils";,所以現在 import 的時候只好乖乖加上 .js 結尾。詳見 TypeScript 5.0: new mode bundler & ESM 文章。

  5. 加入 ESLint 設定

    基本上我是參考 Getting Started | typescript-eslint 這篇文章進行設定。

    不過我有稍微做一點修正,加上了 env: { node: true } 設定:

    /* eslint-env node */
    module.exports = {
        extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
        parser: '@typescript-eslint/parser',
        plugins: ['@typescript-eslint'],
        root: true,
        env: {
            node: true,
        },
    };
    
  6. 設定 esbuild Bundler

    我們在準備發佈應用程式時,就有可能會透過 esbuild 來做 Bundling 的動作,徹底減少部署的檔案數,避免發佈 node_modules 資料夾!

    以下是我對 package.jsonscripts 區段所做出的設定:

    {
      "name": "myproj",
      "type": "module",
      ...
      "main": "dist/app.js",
      "scripts": {
        "esbuild-base": "esbuild ./src/app.ts --bundle --outfile=dist/app.js --format=esm --platform=node",
        "esbuild:prod": "npm run esbuild-base -- --minify",
        "esbuild": "npm run esbuild-base -- --sourcemap",
        "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
    
        "start": "nodemon src/app.ts",
        "build": "tsc",
        "node": "tsc && node dist/app.js"
      },
      ...
    }
    

相關連結

留言評論