釋出 TypeScript 包
本文專門介紹有關 TypeScript 釋出的事項。“釋出”是指透過 npm(或其他包管理器)以包的形式分發;這與編譯要在生產環境中執行的應用/伺服器(例如 PWA 和/或端點伺服器)無關。
一些需要注意的重要事項
-
釋出包中的所有內容都適用於此。
-
像
main這樣的欄位作用於*已釋出*的內容,因此當 TypeScript 原始碼轉譯為 JavaScript 時,JavaScript 是已釋出的內容,而main會指向一個帶有 JavaScript 副檔名的 JavaScript 檔案(例如main.ts→"main": "main.js")。 -
像
scripts.test這樣的欄位作用於原始碼,因此它們會使用原始碼的副檔名(例如"test": "node --test './src/**/*.test.ts')。
-
-
Node 透過一個名為“型別剝離”的過程執行 TypeScript 程式碼,其中 Node(透過 Amaro)移除 TypeScript 特定的語法,留下原生 JavaScript(Node 已經理解)。此行為在 Node 22.18.0 及更高版本中預設啟用。
- Node **不會**剝離
node_modules中的型別,因為這可能對官方 TypeScript 編譯器(tsc)和 VS Code 的部分功能造成嚴重的效能問題,所以 TypeScript 維護者希望阻止人們釋出原始的 TypeScript,至少目前是這樣。
- Node **不會**剝離
-
在 Node 中使用 TypeScript 特定的功能(如
enum)仍然需要一個標誌(--experimental-transform-types)。無論如何,對於這些功能通常有更好的替代方案。- 為確保不包含 TypeScript 特定的功能(這樣你的程式碼就可以直接在 Node 中執行),請在 TypeScript 5.8 及更高版本中設定
erasableSyntaxOnly配置選項。
- 為確保不包含 TypeScript 特定的功能(這樣你的程式碼就可以直接在 Node 中執行),請在 TypeScript 5.8 及更高版本中設定
-
使用 Dependabot 來保持你的依賴項最新,包括 GitHub Actions 中的依賴。這是一個非常容易“設定後即忘”的配置。
-
.nvmrc來自nvm,一個用於 Node 的多版本管理器。它允許你指定專案通常應使用的 Node 版本。
一個程式碼倉庫的目錄概覽可能如下所示:
example-ts-pkg/
├ .github/
│ ├ workflows/
│ │ ├ ci.yml
│ │ └ publish.yml
│ └ dependabot.yml
├ src/
│ ├ foo.fixture.js
│ ├ main.ts
│ ├ main.test.ts
│ ├ some-util.ts
│ └ some-util.test.ts
├ LICENSE
├ package.json
├ README.md
└ tsconfig.json
其已釋出包的目錄概覽可能如下所示:
example-ts-pkg/
├ LICENSE
├ main.d.ts
├ main.d.ts.map
├ main.js
├ package.json
├ README.md
├ some-util.d.ts
├ some-util.d.ts.map
└ some-util.js
關於目錄組織的說明:放置測試有幾種常見的做法。最小知識原則建議將它們共置(放在實現檔案的旁邊)。有時,這在同一個目錄中,或在一個像 __test__ 這樣的抽屜目錄中(也與實現檔案相鄰,“檔案共置但隔離”)。或者,一些人選擇建立一個與 src/ 平級的 test/ 目錄(“‘src’和‘test’完全隔離”),其結構可以是映象的,也可以是一個“雜物抽屜”。
如何處理你的型別
像對待測試一樣對待型別
型別的目的是警告某個實現將無法正常工作
const = 'a';
const bar: number = 1 + ;TypeScript 已經警告說上面的程式碼不會按預期執行,就像單元測試警告程式碼不會按預期執行一樣。它們是互補的,驗證不同的東西——你應該兩者都有。
你的編輯器(例如 VS Code)可能內建了對 TypeScript 的支援,會在你工作時顯示錯誤。如果沒有,或者你錯過了這些錯誤,CI 會為你提供保障。
下面的 GitHub Action 設定了一個 CI 任務,自動檢查(並要求)合併到 main 分支的 PR 的型別檢查透過。
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Tests
on:
pull_request:
branches: ['*']
jobs:
check-types:
# Separate these from tests because
# they are platform and node-version independent
# and need be run only once.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: npm clean install
run: npm ci
# You may want to run a lint check here too
- run: node --run types:check
get-matrix:
# Automatically pick active LTS versions
runs-on: ubuntu-latest
outputs:
latest: ${{ steps.set-matrix.outputs.requireds }}
steps:
- uses: ljharb/actions/node/matrix@main
id: set-matrix
with:
versionsAsRoot: true
type: majors
preset: '>= 22' # glob is not backported below 22.x
test:
needs: [get-matrix]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
node-version: ${{ fromJson(needs.get-matrix.outputs.latest) }}
os:
- macos-latest
- ubuntu-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Use node ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: npm clean install
run: npm ci
- run: node --run test
請注意,測試檔案很可能應用了不同的 tsconfig.json(因此在上面的示例中它們被排除了)。
生成型別宣告
型別宣告(.d.ts 及相關檔案)以伴隨檔案的形式提供型別資訊,允許執行程式碼是原生 JavaScript 的同時仍然擁有型別。
由於這些是根據原始碼生成的,它們可以作為釋出過程的一部分構建,無需檢入到你的程式碼倉庫中。
以下面的例子為例,型別宣告在釋出到 npm 登錄檔之前生成。
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# This is mostly boilerplate.
name: Publish to npm
on:
push:
tags:
- '**@*'
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
# - name: Publish to npm
# run: … npm publish …
你需要釋出一個編譯後支援所有 Node.js LTS 版本的包,因為你不知道消費者將執行哪個版本;本文中的 tsconfig 支援 Node 18.x 及更高版本。
npm publish 會在此之前自動執行 prepack。npm 在 npm pack --dry-run 之前也會自動執行 prepack(這樣你就可以輕鬆檢視你將釋出的包會是什麼樣子,而無需實際釋出)。**注意**,node --run 不會這樣做。你不能在此步驟中使用 node --run,所以這個警告在這裡不適用,但可能適用於其他步驟。
實際釋出到 npm 的步驟將包含在另一篇文章中(這涉及到本文範圍之外的幾個利弊)。
分解說明
生成型別宣告是確定性的:對於相同的輸入,你每次都會得到相同的輸出。所以沒有必要將這些提交到 Git。
npm publish 會抓取命令執行時所有適用且可用的內容;因此在釋出前立即生成型別宣告意味著這些檔案是可用的,並且會被包含進去。
預設情況下,npm publish 會抓取(幾乎)所有東西(參見 包中包含的檔案)。為了讓你的已釋出包保持最小(參見關於 node_modules 的“宇宙中最重的物體”的梗),你需要從打包中排除某些檔案(如測試和測試韌體)。將這些新增到 .npmignore 中指定的排除列表中;確保列出了 !*.d.ts 這個例外,否則生成的型別宣告將不會被髮布!或者,你可以使用 package.json 的 "files" 欄位來建立一個包含列表(如果意外遺漏了一個檔案,你的包可能會對下游使用者造成破壞,所以這是一個不太安全的選擇)。