Angular 包格式

2022-07-20 13:40 更新

Angular 包格式

本文檔描述了 Angular 包格式 (APF)。APF 是針對 npm 包結(jié)構(gòu)和格式的 Angular 專用規(guī)范,所有第一方 Angular 包(?@angular/core? 、 ?@angular/material? 等)和大多數(shù)第三方 Angular 庫都使用了該規(guī)范。

APF 能讓包在使用 Angular 的大多數(shù)常見場景下無縫工作。使用 APF 的包與 Angular 團隊提供的工具以及更廣泛的 JavaScript 生態(tài)系統(tǒng)兼容。建議第三方庫開發(fā)者也都遵循這種格式。

APF 與 Angular 的其余部分一起進行版本控制,每個主要版本都改進了包格式。你可以在此 google doc 中找到 v13 之前版本的規(guī)范。

為什么要指定包格式?

在當今的 JavaScript 環(huán)境中,開發(fā)人員將使用多種不同的工具鏈(Webpack、rollup、esbuild 等)以多種不同的方式使用包。這些工具可能理解并需要不同的輸入 —— 一些工具能處理最新的 ES 語言版本,而其他工具也許要直接使用較舊的 ES 版本。

這種 Angular 分發(fā)格式支持所有常用的開發(fā)工具和工作流,并著重于優(yōu)化,從而縮小應(yīng)用程序有效負載大小或縮短開發(fā)迭代周期(構(gòu)建時間)。

開發(fā)人員可以依靠 Angular CLI 和 ng-packagr(Angular CLI 使用的構(gòu)建工具)來生成 APF 格式的包。

文件布局

以下示例顯示了 ?@angular/core? 包文件布局的簡化版本,并附有對包中每個文件的解釋。

此表描述了 ?node_modules/@angular/core? 下的文件布局,注釋為描述文件和目錄的用途:

文件

用途

README.md

包 README,由 npmjs web UI 使用。

package.json

主要的 package.json,描述包本身以及所有可用的入口點和代碼格式。此文件包含供運行時使用的 "exports" 映射和一些用于執(zhí)行模塊解析的工具。

index.d.ts

主入口點 @angular/core 捆綁的 .d.ts。

esm2020/
  ─ core.mjs
  ─ index.mjs
  ─ public_api.mjs

esm2020/
─ core.mjs
─ index.mjs
─ public_api.mjs

未展平的 ES2020 格式的 @angular/core 源代碼樹。

esm2020/testing/

未扁平化的 ES2020 格式的 @angular/core/testing 入口點的樹。

fesm2015/
  ─ core.mjs
  ─ core.mjs.map
  ─ testing.mjs
  ─ testing.mjs.map

扁平化 (FESM) ES2015 格式的所有入口點的代碼,以及源碼映射。

fesm2020/
  ─ core.mjs
  ─ core.mjs.map
  ─ testing.mjs
  ─ testing.mjs.map

扁平化 (FESM) ES2020 格式的所有入口點的代碼,以及源碼映射。

testing/

代表 testing 入口點的目錄。

testing/index.d.ts

為 @angular/core/testing 入口點打包的 .d.ts。

package.json

主 ?package.json? 包含重要的包元數(shù)據(jù),包括以下內(nèi)容:

  • 它把此包聲明為 EcmaScript 模塊 (ESM) 格式
  • 它包含一個 ?"exports"? 字段,用于定義所有入口點的可用源碼格式
  • 它包含定義主入口點 ?@angular/core? 的可用源碼格式的一些鍵,供不理解 ?"exports"? 的工具使用。這些鍵已棄用,隨著對 ?"exports"? 的支持在整個生態(tài)系統(tǒng)中逐步退出,這些鍵將被刪除。
  • 它聲明此包是否包含副作用

ESM 聲明

頂級 ?package.json? 包含此鍵:

{
  "type": "module"
}

這會通知解析器,此包中的代碼正在使用 EcmaScript 模塊而不是 CommonJS 模塊。

"exports"

?"exports"? 字段具有以下結(jié)構(gòu):

"exports": {
  "./schematics/*": {
    "default": "./schematics/*.js"
  },
  "./package.json": {
    "default": "./package.json"
  },
  ".": {
    "types": "./core.d.ts",
    "esm2020": "./esm2020/core.mjs",
    "es2020": "./fesm2020/core.mjs",
    "es2015": "./fesm2015/core.mjs",
    "node": "./fesm2015/core.mjs",
    "default": "./fesm2020/core.mjs"
  },
  "./testing": {
    "types": "./testing/testing.d.ts",
    "esm2020": "./esm2020/testing/testing.mjs",
    "es2020": "./fesm2020/testing.mjs",
    "es2015": "./fesm2015/testing.mjs",
    "node": "./fesm2015/testing.mjs",
    "default": "./fesm2020/testing.mjs"
  }
}

主要看 "." 和 "./testing" 這兩個鍵,它們分別定義了 @angular/core 主要入口點和 @angular/core/testing 次要入口點的可用代碼格式。對于每個入口點,可用的格式為:

格式

詳情

類型定義(.d.ts 文件)

TypeScript 在依賴于給定包時使用 .d.ts 文件。

es2020

已展平為單個源文件的 ES2020 代碼。

es2015

已展平為單個源文件的 ES2015 代碼。

esm2020

未展平的源文件中的 ES2020 代碼

認識這些鍵的工具可以優(yōu)先從 ?"exports"? 中選擇所需的代碼格式。其余 2 個鍵控制工具的默認行為:

  • ?"node"? 在 Node.js 中加載包時選擇扁平化的 ES2015 代碼。
  • 使用這種格式是由于 ?zone.js? 的要求,因為它不支持原生的 ?async ?/ ?await ?ES2017 語法。因此,指示 Node 使用 ES2015 代碼,其中 ?async ?/ ?await ?結(jié)構(gòu)已降級為 Promises。

  • ?"default"? 為所有其他消費者選擇扁平化的 ES2020 代碼。

庫可能希望公開其他靜態(tài)文件,這些文件沒有被基于 JavaScript 的入口點(比如 Sass mixins 或預(yù)編譯的 CSS)的導(dǎo)出所捕獲。

舊版解析鍵

除了 ?"exports"? 之外,頂級 ?package.json? 還為不支持 ?"exports"? 的解析器定義了舊模塊解析鍵。對于 ?@angular/core?,這些是:

{
  "fesm2020": "./fesm2020/core.mjs",
  "fesm2015": "./fesm2015/core.mjs",
  "esm2020": "./esm2020/core.mjs",
  "typings": "./core.d.ts",
  "module": "./fesm2015/core.mjs",
  "es2020": "./fesm2020/core.mjs",
}

如上,模塊解析器可以用這些鍵來加載特定的代碼格式。

注意:
與 ?"default"? 不同,?"module"? 是為 Node 以及任何未配置為使用特定鍵的工具選擇的格式。它基本和 ?"node"? 一樣,但由于 ZoneJS 的限制,選擇了 ES2015 代碼。

副作用

?package.json? 的最后一個功能是聲明此包是否有副作用

{
  "sideEffects": false
}

大多數(shù) Angular 包不應(yīng)該依賴于頂級副作用,因此應(yīng)該包含這個聲明。

入口點和代碼拆分

APF 中的包,包含一個主要入口點和零到多個次要入口點(比如 ?@angular/common/http?)。入口點有多種功能。

  1. 它們定義了用戶要從中導(dǎo)入代碼的模塊說明符(比如,?@angular/core? 和 ?@angular/core/testing?)。
  2. 用戶通常將這些入口點視為具有不同用途或功能的不同符號組。

    特定入口點可能僅用于特殊目的,比如測試。此類 API 可以與主入口點分離,以減少它們被意外或錯誤使用的機會。

  3. 它們定義了可以惰性加載代碼的粒度。
  4. 許多現(xiàn)代構(gòu)建工具只能在 ES 模塊級別進行“代碼拆分”(又名惰性加載)。由于 APF 主要為每個入口點使用一個“扁平” ES 模塊,這意味著大多數(shù)構(gòu)建工具無法將單個入口點中的代碼拆分為多個輸出塊。

APF 包的一般規(guī)則是為盡可能小的邏輯相關(guān)代碼集使用入口點。比如,Angular Material 包將每個邏輯組件或一組組件作為單獨的入口點發(fā)布 - 一個用于按鈕,一個用于選項卡等。如果需要,這允許單獨惰性加載每個 Material 組件。

并非所有庫都需要這樣的粒度。大多數(shù)具有單一邏輯目的的庫應(yīng)該作為單一入口點發(fā)布。比如 ?@angular/core? 為運行時使用單個入口點,因為 Angular 運行時通常用作單個實體。

次要入口點的解析

可以通過包的 ?package.json? 的 ?"exports"? 字段解析輔助入口點。

自述文件

markdown 格式的自述文件,用于在 npm 和 github 上顯示包的描述。

?@angular/core? 包的示例自述內(nèi)容:

Angular
=======

The sources for this package are in the main [Angular](https://github.com/angular/angular) repo.Please file issues and pull requests against that repo.

License: MIT

部分編譯

APF 格式的庫必須以“部分編譯”模式發(fā)布。這是 ?ngc ?的一種編譯模式,它生成不依賴于特定 Angular 運行時版本的已編譯 Angular 代碼,與用于應(yīng)用程序的完整編譯形成對比,其中 Angular 編譯器和運行時版本必須完全匹配。

要部分編譯 Angular 代碼,請在 ?tsconfig.json? 中的 ?"angularCompilerOptions"? 中使用 ?"compilationMode"? 標志:

{
  …
  "angularCompilerOptions": {
    "compilationMode": "partial",
  }
}

然后,在應(yīng)用程序構(gòu)建過程中,Angular CLI 將部分編譯的庫代碼轉(zhuǎn)換為完全編譯的代碼。

優(yōu)化

ES 模塊的扁平化

APF 指定代碼要以“扁平化”的 ES 模塊格式發(fā)布。這顯著減少了 Angular 應(yīng)用程序的構(gòu)建時間以及最終應(yīng)用程序包的下載和解析時間。請查看 Nolan Lawson 發(fā)表的優(yōu)秀文章“小模塊的成本”

Angular 編譯器支持生成索引 ES 模塊文件,然后可以讓這些文件借助 Rollup 等工具生成扁平化模塊,從而生成我們稱為扁平化 ES 模塊或 FESM 的文件格式。

FESM 是一種文件格式,它會將所有可從入口點訪問的 ES 模塊扁平化為單個 ES 模塊。它是通過跟蹤包中的所有導(dǎo)入并將該代碼復(fù)制到單個文件中而生成的,同時保留所有公共 ES 導(dǎo)出并刪除所有私有導(dǎo)入。

縮寫名稱“FESM”(發(fā)音為“phesom”)后面可以有一個數(shù)字,比如“FESM5”或“FESM2015”。數(shù)字是指模塊內(nèi) JavaScript 的語言級別。所以 FESM5 文件將是 ESM+ES5(導(dǎo)入/導(dǎo)出語句和 ES5 源代碼)。

要生成扁平化的 ES 模塊索引文件,請在 tsconfig.json 文件中使用以下配置選項:

{
  "compilerOptions": {
    …
    "module": "esnext",
    "target": "es2020",
    …
  },
  "angularCompilerOptions": {
    …
    "flatModuleOutFile": "my-ui-lib.js",
    "flatModuleId": "my-ui-lib"
  }
}

一旦 ngc 生成了索引文件(比如 ?my-ui-lib.js?),打包器和優(yōu)化器(如 Rollup)就可用于生成扁平化的 ESM 文件。

注意 package.json 中的默認值

從 webpack v4 開始,對于 webpack 用戶來說,ES 模塊優(yōu)化的扁平化應(yīng)該不是必需的,其實理論上我們應(yīng)該能夠在不扁平化 webpack 中的模塊的情況下獲得更好的代碼拆分。在實踐中,當使用非扁平化模塊作為 webpack v4 的輸入時,我們?nèi)匀粫吹酱笮≡黾恿恕_@就是為什么 package.json 中的 ?"module"? 和 ?"es2020"? 條目仍然指向 fesm 文件的原因。我們正在調(diào)查此問題,并希望在解決大小回歸問題后將 package.json 中的 ?"module"? 和 ?"es2020"? 入口點切換到未扁平化的文件。APF 目前包含未扁平化的 ESM2020 代碼,目的是驗證此類未來的更改。

“副作用”標志

默認情況下,EcmaScript 模塊是有副作用的:從模塊導(dǎo)入可確保該模塊頂層的任何代碼都將執(zhí)行。這通常是不可取的,因為典型模塊中的大多數(shù)副作用代碼并不是真正的副作用,而是僅影響特定符號。如果沒有導(dǎo)入和使用這些符號,通常需要在稱為 tree-shaking 的優(yōu)化過程中將它們刪除,而副作用代碼可以防止這種情況發(fā)生。

諸如 Webpack 之類的構(gòu)建工具支持一個標志,該標志允許包聲明它們并不依賴于其模塊頂層的副作用代碼,從而使工具可以更自由地對包中的代碼進行搖樹優(yōu)化。這些優(yōu)化的最終結(jié)果應(yīng)該是較小的包大小和代碼拆分后包塊中更好的代碼分布。如果此優(yōu)化包含非本地副作用,則此優(yōu)化可能會破壞你的代碼 - 然而,這在 Angular 應(yīng)用程序中并不常見,并且通常是糟糕設(shè)計的標志。我們的建議是讓所有包通過將 sideEffects 屬性設(shè)置為 false 來聲明無副作用狀態(tài),并且讓開發(fā)人員遵循 Angular 風格指南,這自然會導(dǎo)致代碼沒有非本地副作用。

更多信息:關(guān)于副作用的 webpack 文檔

ES2020 語言級別

ES2020 語言級別現(xiàn)在是 Angular CLI 和其他工具使用的默認語言級別。Angular CLI 會將捆綁包降級到所有目標瀏覽器在應(yīng)用程序構(gòu)建時都支持的語言級別。

d.ts 捆綁/類型定義的扁平化

從 APF v8 開始,我們現(xiàn)在更喜歡運行 API Extractor 來打包 TypeScript 定義,以便整個 API 都出現(xiàn)在單個文件中。

在之前的 APF 版本中,每個入口點都會在 .d.ts 入口點旁邊有一個 ?src ?目錄,該目錄包含與原始源代碼結(jié)構(gòu)匹配的單個 d.ts 文件。雖然這種分發(fā)格式仍然被允許和支持,但非常不鼓勵它,因為它會弄暈 IDE 之類的工具,然后提供錯誤的自動完成,并允許用戶依賴深度導(dǎo)入的路徑,這些路徑通常不被認為是庫或包的公共 API。

從 APF v10 開始,我們建議添加 tslib 作為主要入口點的直接依賴項。這是因為 tslib 版本與用來編譯庫的 TypeScript 版本相關(guān)聯(lián)。

術(shù)語定義

本文檔中特意使用了以下術(shù)語。在本節(jié)中,我們定義所有這些以便更清晰。

發(fā)布到 NPM 并一起安裝的最小文件集,比如 ?@angular/core?。該包中包含一個名為 package.json 的清單、編譯后的源代碼、TypeScript 定義文件、源碼映射、元數(shù)據(jù)等。該包是通過 ?npm install @angular/core? 安裝的。

符號

包含在模塊中的類、函數(shù)、常量或變量,可選擇通過模塊導(dǎo)出,以便對外界可見。

模塊

ECMAScript 模塊的縮寫。包含導(dǎo)入和導(dǎo)出符號的語句的文件。這與 ECMAScript 規(guī)范中模塊的定義相同。

ESM

ECMAScript 模塊的縮寫(見上文)。

FESM

Flattened ES Modules 的縮寫,由一種文件格式組成,該文件格式是通過將所有可從入口點訪問的 ES 模塊扁平化為單個 ES 模塊而創(chuàng)建的。

模塊標識

導(dǎo)入語句中使用的模塊的標識符(比如 ?@angular/core?)。此 ID 通常直接映射到文件系統(tǒng)上的路徑,但由于有各種模塊解析策略,情況也并非總是如此。

模塊說明符

模塊標識符(見上文)。

模塊解析策略

用于將模塊 ID 轉(zhuǎn)換為文件系統(tǒng)路徑的算法。Node.js 就有一個良好定義且廣泛使用的,TypeScript 支持多種模塊解析策略,Closure Compiler 還有另一種策略。

模塊格式

模塊語法規(guī)范,至少涵蓋用于從文件導(dǎo)入和導(dǎo)出的語法。常見的模塊格式是 CommonJS(CJS,通常用于 Node.js 應(yīng)用程序)或 ECMAScript 模塊(ESM)。模塊格式僅表示單個模塊的封裝,而不表示用于構(gòu)成模塊內(nèi)容的 JavaScript 語言特性。正因為如此,Angular 團隊經(jīng)常使用語言級別說明符作為模塊格式的后綴,比如 ESM+ES2015 指定模塊為 ESM 格式并包含降級到 ES2015 的代碼。

捆綁包

單個 JS 文件形式的工件,由構(gòu)建工具(比如 WebpackRollup)生成,其中包含源自一個或多個模塊的符號。捆綁包是一種瀏覽器專用的解決方案,可減少瀏覽器開始下載數(shù)百甚至數(shù)萬個文件時可能造成的網(wǎng)絡(luò)壓力。Node.js 通常不使用捆綁包。常見的捆綁包格式是 UMD 和 System.register。

語言級別

代碼的語言(ES2015 或 ES2020)。獨立于模塊格式。

入口點

旨在由用戶導(dǎo)入的模塊。它由唯一的模塊 ID 引用,并導(dǎo)出該模塊 ID 引用的公共 API。一個例子是 ?@angular/core? 或 ?@angular/core/testing?。?@angular/core? 包中存在兩個入口點,但它們導(dǎo)出不同的符號。一個包可以有許多入口點。

深度導(dǎo)入

從不是入口點的模塊中檢索符號的過程。這些模塊 ID 通常被認為是私有 API,它們可以在項目的生命周期內(nèi)或在創(chuàng)建給定包的捆綁包時更改。

頂級導(dǎo)入

來自入口點的導(dǎo)入??捎玫捻敿墝?dǎo)入定義了公共 API,并在“@angular/name”模塊中公開,比如 ?@angular/core? 或 ?@angular/common?。

搖樹優(yōu)化

識別和刪除應(yīng)用程序中未使用的代碼的過程 - 也稱為死代碼消除。這是使用 Rollup 、 Closure Compiler 或 Terser 等工具在應(yīng)用程序級別執(zhí)行的全局優(yōu)化。

AOT 編譯器

Angular 的預(yù)先編譯器。

扁平類型定義

從 API Extractor 生成的捆綁 TypeScript 定義。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號