TypeScript已經成爲一種很是流行的JavaScript語言,這是有緣由的。它的類型系統和編譯器可以在您的軟件運行以前的編譯時捕獲各類bug,而且附加的代碼編輯器功能使它成爲一個很是適合開發人員的高效環境。javascript
可是,當你想用TypeScript編寫一個庫或包,同時又想用JavaScript來發布,這樣你的最終用戶就沒必要手動編譯你的代碼,會發生什麼?咱們如何使用現代的JavaScript功能(如ES模塊)來編寫,同時又能得到TypeScript的全部好處?java
本文旨在解決全部這些問題,併爲你提供一個設置,使你能夠放心地編寫和共享TypeScript庫,併爲包裝的使用者提供輕鬆的體驗。node
獲取本文完整代碼:公衆號中回覆關鍵字: typescript-es-modules
目錄webpack
[toc]web
咱們要作的第一件事是創建一個新項目。在本教程中,咱們將建立一個基本的數學程序包——不是一個服務於任何實際目的的程序包——由於它將讓咱們演示全部咱們須要的TypeScript,而不會偏離程序包的實際功能。typescript
首先,建立一個空目錄並運行 npm init -y
建立一個新項目。這將建立你的 package.json
併爲你提供一個空項目以供處理:shell
$ mkdir maths-package $ cd maths-package $ npm init -y
如今,咱們能夠添加第一個也是最重要的依賴項:TypeScript!npm
$ npm install --save-dev typescript
安裝TypeScript後,能夠經過運行 tsc --init
初始化TypeScript項目。 tsc
是「 TypeScript編譯器」的縮寫,是TypeScript的命令行工具。json
爲確保你運行咱們剛剛在本地安裝的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的編譯器在輸出代碼時將使用的模塊系統。
發佈模塊時我喜歡作的事情是發佈兩個版本:
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編譯咱們的代碼。咱們須要先對 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在寫代碼的同時發出一個聲明文件來解決類型信息問題。這個文件的結尾是 .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編譯器將可以選擇全部這些類型。
難題的最後一部分是還將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輸出你可能須要的多種格式,而不須要太多麻煩。