TypeScript已經成為一種非常流行的JavaScript語言,這是有原因的。它的類型系統和編譯器能夠在您的軟件運行之前的編譯時捕獲各種bug,並且附加的代碼編輯器功能使它成為一個非常適合開發人員的高效環境。
但是,當你想用TypeScript編寫一個庫或包,同時又想用JavaScript來發布,這樣你的最終用戶就不必手動編譯你的代碼,會發生什么?我們如何使用現代的JavaScript功能(如ES模塊)來編寫,同時又能獲得TypeScript的所有好處?
本文旨在解決所有這些問題,並為你提供一個設置,使你可以放心地編寫和共享TypeScript庫,並為包裝的使用者提供輕松的體驗。
入門
我們要做的第一件事是建立一個新項目。在本教程中,我們將創建一個基本的數學程序包——不是一個服務於任何實際目的的程序包——因為它將讓我們演示所有我們需要的TypeScript,而不會偏離程序包的實際功能。
首先,創建一個空目錄並運行 npm init -y 創建一個新項目。這將創建你的 package.json 並為你提供一個空項目以供處理:
$ mkdir maths-package
$ cd maths-package
$ npm init -y
現在,我們可以添加第一個也是最重要的依賴項:TypeScript!
$ npm install --save-dev typescript
安裝TypeScript后,可以通過運行 tsc --init 初始化TypeScript項目。 tsc 是“ TypeScript編譯器”的縮寫,是TypeScript的命令行工具。
為確保你運行我們剛剛在本地安裝的TypeScript編譯器,應在命令前加上 npx。npx是個很棒的工具,它將在node_modules 文件夾中查找你提供的命令,因此,通過在命令前面加上前綴,可以確保我們使用的是本地版本,而不是你可能已安裝的TypeScript的任何其他全局版本。
$ npx tsc --init
這將創建一個 tsconfig.json 文件,該文件負責配置我們的TypeScript項目。您會看到該文件具有數百個選項,其中大多數選項已被注釋掉(TypeScript支持 tsconfig.json 文件中的注釋)。我已將文件縮減為僅啟用的設置,如下所示:
{
"compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true } }
我們需要對此配置進行一些更改,以使我們能夠使用ES模塊發布程序包,因此,讓我們現在來看一下這些選項。
配置tsconfig.json 選項
如果您正在尋找所有可能的 tsconfig 選項的完整列表,可以在TypeScript網站上找到此方便的參考。
讓我們從 target 開始,這定義了你將在瀏覽器中提供代碼的JavaScript支持級別。如果您必須使用一組較舊的瀏覽器,這些瀏覽器可能不具有所有最新和最強大的功能,則可以將其設置為 ES2015。如果您確實需要最大的瀏覽器覆蓋范圍,TypeScript甚至將支持 ES3。
我們將在此處針對該模塊使用 ES2015,但可以隨時進行相應更改。例如,如果我為自己建立一個快速的輔助項目,並且只關心尖端的瀏覽器,那么我很高興將其設置為 ES2020。
選擇模塊系統
接下來,我們必須決定將用於該項目的模塊系統。請注意,這不是我們要編寫的模塊系統,而是TypeScript的編譯器在輸出代碼時將使用的模塊系統。
發布模塊時我喜歡做的事情是發布兩個版本:
- 帶有ES模塊的現代版本,以便捆綁工具可以巧妙地將未使用的代碼tree–shake ,因此支持ES模塊的瀏覽器只需導入文件
- 使用CommonJS模塊的版本(如果在Node中工作,你將習慣使用 require 代碼),因此較早的構建工具和Node.js環境可以輕松運行該代碼
稍后我們將介紹如何使用不同的選項捆綁兩次,但是現在,讓我們將TypeScript配置為輸出ES模塊。我們可以通過將 module 設置設置為 ES2020 來實現。
現在,你的 tsconfig.json 文件應如下所示:
{
"compilerOptions": { "target": "ES2015", "module": "ES2020", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true } }
編寫一些代碼
在討論捆綁代碼之前,我們需要寫一些代碼!讓我們創建兩個小模塊,它們既導出函數,又為導出所有代碼的模塊提供一個主 entry 文件。
我喜歡將所有TypeScript代碼放在 src 目錄中,因為這意味着我們可以直接將TypeScript編譯器指向它,因此,我將使用以下代碼創建 src/add.ts:
export const add = (x: number, y:number):number => { return x + y; }
我也將創建 src/subtract.ts:
export const subtract = (x: number, y:number):number => { return x - y; }
最后,src/index.ts 將導入我們所有的API方法並再次導出它們:
import { add } from './add.js' import { subtract } from './subtract.js' export { add, subtract }
這意味着,用戶可以通過導入只需要的東西來獲取我們的功能,也可以通過獲取所有的東西來獲取。
import { add } from 'maths-package'; import * as MathsPackage from 'maths-package';
請注意,在 src/index.ts 中,我的導入包含文件擴展名。如果只想支持Node.js和構建工具(例如webpack),則不需要這樣做,但是如果要支持支持ES模塊的瀏覽器,則需要文件擴展名。
使用TypeScript進行編譯
讓我們看看是否可以讓TypeScript編譯我們的代碼。我們需要先對 tsconfig.json 文件進行一些調整,然后才能執行以下操作:
{
"compilerOptions": { "target": "ES2015", "module": "ES2020", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "outDir": "./lib", }, "include": [ "./src" ] }
我們進行了兩項更改:
- compilerOptions.outDir ——這告訴TypeScript將我們的代碼編譯到一個目錄中。在這種情況下,我已經告訴它命名該目錄 lib,但是您可以根據需要命名它。
- include ——告訴TypeScript我們希望在編譯過程中包含哪些文件。在我們的例子中,我們所有的代碼都位於src 目錄中,因此我將其傳入。這就是為什么我喜歡將所有TS源文件保存在一個文件夾中的原因,這使配置變得非常容易
讓我們來試一試,看看會發生什么吧! 我發現在調整我的TypeScript配置時,最適合我的方法是調整、編譯、檢查輸出,然后再調整。不要害怕嘗試這些設置,看看它們如何影響最終結果。
要編譯TypeScript,我們將運行 tsc 並使用 -p 標志(“project”的縮寫)告訴它 tsconfig.json 的位置:
npx tsc -p tsconfig.json
如果你有任何類型錯誤或配置問題,將在此處顯示。如果沒有,您應該什么也看不到——但是請注意,你有一個新的 lib 目錄,其中有文件!TypeScript編譯時不會將任何文件合並在一起,而是將每個模塊轉換成對應的JavaScript。
讓我們看一下輸出的三個文件:
// lib/add.js export const add = (x, y) => { return x + y; }; // lib/subtract.js export const subtract = (x, y) => { return x - y; }; // lib/index.js import { add } from './add.js'; import { subtract } from './subtract.js'; export { add, subtract };
它們看起來和我們的輸入非常相似,但沒有我們添加的類型注釋。這是可以預期的:我們在ES模塊中編寫了我們的代碼,並告訴TypeScript也要以這種形式輸出。如果我們使用了比ES2015更新的任何JavaScript功能,TypeScript會將它們轉換為ES2015友好的語法,但是在我們的案例中,我們沒有使用它,因此TypeScript在很大程度上僅保留了所有內容。
該模塊現在可以發布到npm上供其他用戶使用,但是我們有兩個問題需要解決:
- 我們不會在代碼中發布任何類型信息。這不會對我們的用戶造成破壞,但這是一個錯過的機會:如果我們也發布我們的類型信息,那么使用支持TypeScript的編輯器的人或用TypeScript編寫應用程序的人將獲得更好的體驗。
- Node還不支持開箱即用的ES模塊。發布CommonJS版本也很好,所以Node不需要額外的工作。ES模塊支持將出現在Node 13和更高的版本中,但是要趕上生態系統還需要一段時間。
發布類型定義
我們可以通過要求TypeScript在寫代碼的同時發出一個聲明文件來解決類型信息問題。這個文件的結尾是 .d.ts,它將包含關於我們代碼的類型信息。將它看作源代碼,除了不包含類型和實現之外,它只包含類型。
讓我們在 tsconfig.json 中添加 "declaration": true(在 "compilerOptions" 部分中),然后再次運行 npx tsc -p tsconfig.json。
提示:我想在我的 package.json 文件中添加一個腳本來進行編譯,因此無需輸入以下內容:
"scripts": { "tsc": "tsc -p tsconfig.json" }
然后我可以運行 npm run tsc 來編譯我的代碼。
現在,您將看到每個JavaScript文件(例如 add.js )旁邊都有一個等效的 add.d.ts 文件,如下所示:
// lib/add.d.ts export declare const add: (x: number, y: number) => number;
因此,現在當用戶使用我們的模塊時,TypeScript編譯器將能夠選擇所有這些類型。
發布到CommonJS
難題的最后一部分是還將TypeScript配置為輸出使用CommonJS的代碼版本。為此,我們可以制作兩個 tsconfig.json 文件,一個針對ES模塊,另一個針對CommonJS。不過,我們可以讓CommonJS配置擴展我們的默認設置並覆蓋 modules 設置,而不是復制所有配置。
讓我們創建 tsconfig-cjs.json:
{
"extends": "./tsconfig.json", "compilerOptions": { "module": "CommonJS", "outDir": "./lib/cjs" }, }
重要的是第一行,這意味着此配置默認情況下會繼承 tsconfig.json 的所有設置。這很重要,因為你不需要在多個JSON文件之間同步設置。
然后覆蓋需要更改的設置。我相應地更新模塊,然后將 outDir 設置更新到 lib/cjs ,這樣我們就可以輸出到lib 中的子文件夾。
此時,我還更新了 package.json 中的 tsc 腳本:
"scripts": { "tsc": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json" }
現在,當我們運行 npm run tsc 時,我們將編譯兩次,並且我們的lib目錄將如下所示:
這個有點亂,讓我們通過更新 tsconfig 中的 outDir 選項來將ESM輸出更新到 lib/esm 中
接下來,我們將設置 module 屬性。這是應該鏈接到我們軟件包的ES模塊版本的屬性。支持此功能的工具將能夠使用此版本的軟件包。因此,應將其設置為 ./lib/esm/index.js 。
接下來,我們將 files entry 添加到 package.json 中。在這里,我們定義了發布模塊時應包括的所有文件。我喜歡使用這種方法來明確定義要在最終模塊中推送到npm的文件。
這樣我們就可以減小模塊的大小。例如,我們不會發布 src 文件,而是發布 lib 目錄。如果你在 files entry 中提供目錄,則默認情況下會包含其所有文件和子目錄,因此你不必全部列出。
提示:如果要查看模塊中將包含哪些文件,請運行 npx pkgfiles 以獲得列表。
現在,我們的 package.json 中包含以下三個附加字段:
"main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", "files": [ "lib/" ],
還有最后一步。因為我們要發布 lib 目錄,所以需要確保在運行 npm publish 時 lib 目錄是最新的。npm文檔中有一節是關於如何做到這一點的——我們可以使用 prepublishOnly 腳本。當我們運行 npm publish 時,該腳本將自動為我們運行:
"scripts": { "tsc": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json", "prepublish": "npm run tsc" },
注意,還有一個名為 prepublish 的腳本,這使選擇哪個稍微有些混亂。npm文檔提到了這一點:不推薦使用prepublish ,如果只想在發布時運行代碼,則應使用prepublishOnly。
這樣,運行 npm publish 將運行我們的TypeScript編譯器並在線發布模塊!我將該軟件包發布在 @ jackfranklin/maths-package-for-blog-post 下,雖然我不建議你使用它,但是你可以瀏覽文件並查看。我還將所有代碼都上傳到了CodeSandbox中,因此您可以根據需要下載或破解它。
結束
就是這樣!我希望這篇教程已經告訴你,使用TypeScript上手和運行TypeScript並不像最初看起來那么困難,只要稍加調整,就可以讓TypeScript輸出你可能需要的多種格式,而不需要太多麻煩。