Angular 創(chuàng)建庫(kù)

2022-07-20 11:30 更新

創(chuàng)建庫(kù)

對(duì)于如何創(chuàng)建和發(fā)布新庫(kù),以擴(kuò)展 Angular 的功能,本頁(yè)面提供了一個(gè)概念性的總覽

如果你發(fā)現(xiàn)自己要在多個(gè)應(yīng)用中解決同樣的問(wèn)題(或者要把你的解決方案分享給其它開(kāi)發(fā)者),你就有了一個(gè)潛在的庫(kù)。簡(jiǎn)單的例子就是一個(gè)用來(lái)把用戶帶到你公司網(wǎng)站上的按鈕,該按鈕會(huì)包含在你公司構(gòu)建的所有應(yīng)用中。

快速上手

使用 Angular CLI,用以下命令在新的工作區(qū)中生成一個(gè)新庫(kù)的骨架:

ng new my-workspace --no-create-application
cd my-workspace
ng generate library my-lib
命名你的庫(kù)
如果你想稍后在公共包注冊(cè)表(比如 npm)中發(fā)布它,則在選擇庫(kù)名稱時(shí)應(yīng)該非常小心。
避免使用以 ?ng-? 為前綴的名稱,比如 ?ng-library?。?ng-? 前綴是 Angular 框架及其庫(kù)中使用的保留關(guān)鍵字。首選 ?ngx-? 前綴作為用于表示該庫(kù)適合與 Angular 一起使用的約定。這也是注冊(cè)表的使用者區(qū)分不同 JavaScript 框架庫(kù)的優(yōu)秀指示器。

?ng generate? 命令會(huì)在你的工作區(qū)中創(chuàng)建 ?projects/my-lib? 文件夾,其中包含帶有一個(gè)組件和一個(gè)服務(wù)的 NgModule。

可以使用單一倉(cāng)庫(kù)(monorepo)模式將同一個(gè)工作區(qū)用于多個(gè)項(xiàng)目。

當(dāng)你生成一個(gè)新庫(kù)時(shí),該工作區(qū)的配置文件 ?angular.json? 中也增加了一個(gè) 'library' 類型的項(xiàng)目。

"projects": {
  …
  "my-lib": {
    "root": "projects/my-lib",
    "sourceRoot": "projects/my-lib/src",
    "projectType": "library",
    "prefix": "lib",
    "architect": {
      "build": {
        "builder": "@angular-devkit/build-angular:ng-packagr",
        …

可以使用 CLI 命令來(lái)構(gòu)建、測(cè)試和 lint 這個(gè)項(xiàng)目:

ng build my-lib --configuration development
ng test my-lib
ng lint my-lib

注意,該項(xiàng)目配置的構(gòu)建器與應(yīng)用類項(xiàng)目的默認(rèn)構(gòu)建器不同。此構(gòu)建器可以確保庫(kù)永遠(yuǎn)使用 AoT 編譯器構(gòu)建。

要讓庫(kù)代碼可以復(fù)用,你必須為它定義一個(gè)公共的 API。這個(gè)“用戶層”定義了庫(kù)中消費(fèi)者的可用內(nèi)容。該庫(kù)的用戶應(yīng)該可以通過(guò)單個(gè)的導(dǎo)入路徑來(lái)訪問(wèn)公共功能(如 NgModules、服務(wù)提供者和工具函數(shù))。

庫(kù)的公共 API 是在庫(kù)文件夾下的 ?public-api.ts? 文件中維護(hù)的。當(dāng)你的庫(kù)被導(dǎo)入應(yīng)用時(shí),從該文件導(dǎo)出的所有內(nèi)容都會(huì)公開(kāi)。請(qǐng)使用 NgModule 來(lái)暴露這些服務(wù)和組件。

你的庫(kù)里應(yīng)該提供一些文檔(通常是 README 文件)來(lái)指導(dǎo)別人安裝和維護(hù)。

把應(yīng)用中的部分內(nèi)容重構(gòu)成一個(gè)庫(kù)

為了讓你的解決方案可供復(fù)用,你需要對(duì)它進(jìn)行調(diào)整,以免它依賴應(yīng)用特有的代碼。在將應(yīng)用的功能遷移到庫(kù)中時(shí),需要注意以下幾點(diǎn)。

  • 組件和管道之類的可聲明對(duì)象應(yīng)該設(shè)計(jì)成無(wú)狀態(tài)的,這意味著它們不依賴或修改外部變量。如果確實(shí)依賴于狀態(tài),就需要對(duì)每種情況進(jìn)行評(píng)估,以決定它是應(yīng)用的狀態(tài)還是庫(kù)要管理的狀態(tài)。
  • 組件內(nèi)部訂閱的所有可觀察對(duì)象都應(yīng)該在這些組件的生命周期內(nèi)進(jìn)行清理和釋放
  • 組件對(duì)外暴露交互方式時(shí),應(yīng)該通過(guò)輸入?yún)?shù)來(lái)提供上下文,通過(guò)輸出參數(shù)來(lái)將事件傳給其它組件
  • 檢查所有內(nèi)部依賴。
    • 對(duì)于在組件或服務(wù)中使用的自定義類或接口,檢查它們是否依賴于其它類或接口,它們也需要一起遷移
    • 同樣,如果你的庫(kù)代碼依賴于某個(gè)服務(wù),則需要遷移該服務(wù)
    • 如果你的庫(kù)代碼或其模板依賴于其它庫(kù)(比如 Angular Material),你就必須把它們配置為該庫(kù)的依賴
  • 考慮如何為客戶端應(yīng)用提供服務(wù)。
    • 服務(wù)應(yīng)該自己聲明提供者(而不是在 NgModule 或組件中聲明提供者),以便它們是可搖樹(shù)優(yōu)化的。這樣,如果服務(wù)器從未被注入到導(dǎo)入該庫(kù)的應(yīng)用中,編譯器就會(huì)把該服務(wù)從該 bundle 中刪除。
    • 如果你在多個(gè) NgModules 注冊(cè)全局服務(wù)提供者或提供者共享,使用?forRoot()? 和 ?forChild()? 設(shè)計(jì)模式提供?RouterModule?
    • 如果你的庫(kù)中提供的可選服務(wù)可能并沒(méi)有被所有的客戶端應(yīng)用所使用,那么就可以通過(guò)輕量級(jí)令牌設(shè)計(jì)模式為這種情況支持正確的樹(shù)狀結(jié)構(gòu)

使用代碼生成原理圖與 CLI 集成

一個(gè)庫(kù)通常都包含可復(fù)用的代碼,用于定義組件,服務(wù),以及你剛才導(dǎo)入到項(xiàng)目中的其他 Angular 工件(管道,指令等等)。庫(kù)被打包成一個(gè) npm 包,用于發(fā)布和共享。這個(gè)包還可以包含一些原理圖,它提供直接在項(xiàng)目中生成或轉(zhuǎn)換代碼的指令,就像 CLI 用 ?ng generate component? 創(chuàng)建一個(gè)通用的新 ?component?。比如,用庫(kù)打包的原理圖可以為 Angular CLI 提供生成組件所需的信息,該組件用于配置和使用該庫(kù)中定義的特定特性或一組特性。這方面的一個(gè)例子是  Angular Material 的導(dǎo)航原理圖,它用來(lái)配置 CDK 的 BreakpointObserver 并把它與 Material 的 MatSideNav 和 MatToolbar 組件一起使用。

創(chuàng)建并包含以下幾種原理圖。

  • 包含一個(gè)安裝原理圖,以便 ?ng add? 可以把你的庫(kù)添加到項(xiàng)目中。
  • 在庫(kù)中包含了生成原理圖,以便 ?ng generate? 可以為項(xiàng)目中的已定義工件(組件,服務(wù),測(cè)試等)提供支持。
  • 包含一個(gè)更新的原理圖,以便 ?ng update? 可以更新你的庫(kù)的依賴,并提供一些遷移來(lái)破壞新版本中的更改。

你的庫(kù)中所包含的內(nèi)容取決于你的任務(wù)。比如,你可以定義一個(gè)原理圖來(lái)創(chuàng)建一個(gè)預(yù)先填充了固定數(shù)據(jù)的下拉列表,以展示如何把它添加到一個(gè)應(yīng)用中。如果你想要一個(gè)每次包含不同傳入值的下拉列表,那么你的庫(kù)可以定義一個(gè)原理圖來(lái)用指定的配置創(chuàng)建它。然后,開(kāi)發(fā)人員可以使用 ?ng generate? 為自己的應(yīng)用配置一個(gè)實(shí)例。

假設(shè)你要讀取配置文件,然后根據(jù)該配置生成表單。如果該表單需要庫(kù)的用戶進(jìn)行額外的自定義,它可能最適合用作 schematic。但是,如果這些表單總是一樣的,開(kāi)發(fā)人員不需要做太多自定義工作,那么你就可以創(chuàng)建一個(gè)動(dòng)態(tài)的組件來(lái)獲取配置并生成表單。通常,自定義越復(fù)雜,schematic 方式就越有用。

發(fā)布你的庫(kù)

使用 Angular CLI 和 npm 包管理器來(lái)構(gòu)建你的庫(kù)并發(fā)布為 npm 包。

Angular CLI 使用一個(gè)名為 ?ng-packagr? 的工具從已編譯的代碼中創(chuàng)建可以發(fā)布到 npm 的軟件包。

你應(yīng)該總是使用 ?production ?配置來(lái)構(gòu)建用于分發(fā)的庫(kù)。這樣可以確保所生成的輸出對(duì) npm 使用了適當(dāng)?shù)膬?yōu)化和正確的軟件包格式。

ng build my-lib
cd dist/my-lib
npm publish

管理庫(kù)中的資產(chǎn)(assets)

對(duì)于 Angular 庫(kù),可分發(fā)文件中可包含一些額外的資產(chǎn),如主題文件、Sass mixins 或文檔(如變更日志)。欲知詳情,請(qǐng)參見(jiàn)在構(gòu)建時(shí)將資產(chǎn)復(fù)制到庫(kù)中將資產(chǎn)嵌入到組件樣式中。

當(dāng)包含額外的資產(chǎn)(如 Sass mixins 或預(yù)編譯的 CSS)時(shí),你需要將這些手動(dòng)添加到主入口點(diǎn)的 ?package.json? 中的條件化 ?"exports"? 部分。
?ng-packagr? 會(huì)將手寫(xiě)的 ?"exports"? 與自動(dòng)生成的 ?"exports"? 合并,以便讓庫(kù)作者配置額外的導(dǎo)出子路徑或自定義條件。
"exports": {
  ".": {
    "sass": "./_index.scss",
  },
  "./theming": {
    "sass": "./_theming.scss"
  },
  "./prebuilt-themes/indigo-pink.css": {
    "style": "./prebuilt-themes/indigo-pink.css"
  }
}

以上是 @angular/material 可分發(fā)文件的摘錄。

同級(jí)依賴

各種 Angular 庫(kù)應(yīng)該把自己依賴的所有 ?@angular/*? 都列為同級(jí)依賴。這確保了當(dāng)各個(gè)模塊請(qǐng)求 Angular 時(shí),都會(huì)得到完全相同的模塊。如果某個(gè)庫(kù)在 ?dependencies ?列出 ?@angular/core? 而不是用 ?peerDependencies?,它可能會(huì)得到一個(gè)不同的 Angular 模塊,這會(huì)破壞你的應(yīng)用。

在應(yīng)用中使用你自己的庫(kù)

如果要在同一個(gè)工作空間中使用某個(gè)庫(kù),你不必把它發(fā)布到 npm 包管理器,但你還是得先構(gòu)建它。

要想在應(yīng)用中使用你自己的庫(kù):

  • 構(gòu)建該庫(kù)。在構(gòu)建之前,無(wú)法使用庫(kù)。
  • ng build my-lib
  • 在你的應(yīng)用中,按名字從庫(kù)中導(dǎo)入:
  • import { myExport } from 'my-lib';

構(gòu)建和重建你的庫(kù)

如果你沒(méi)有把庫(kù)發(fā)布為 npm 包,然后把它從 npm 安裝到你的應(yīng)用中,那么構(gòu)建步驟就是必要的。比如,如果你克隆了 git 倉(cāng)庫(kù)并運(yùn)行了 ?npm install?,編輯器就會(huì)把 ?my-lib? 的導(dǎo)入顯示為缺失狀態(tài)(如果你還沒(méi)有構(gòu)建過(guò)該庫(kù))。

當(dāng)你在 Angular 應(yīng)用中從某個(gè)庫(kù)導(dǎo)入一些東西時(shí),Angular 就會(huì)尋找?guī)烀痛疟P(pán)上某個(gè)位置之間的映射關(guān)系。當(dāng)你用 npm 包安裝該庫(kù)時(shí),它就映射到 ?node_modules ?目錄下。當(dāng)你自己構(gòu)建庫(kù)時(shí),它就會(huì)在 ?tsconfig ?路徑中查找這個(gè)映射。
用 Angular CLI 生成庫(kù)時(shí),會(huì)自動(dòng)把它的路徑添加到 ?tsconfig ?文件中。Angular CLI 使用 ?tsconfig ?路徑告訴構(gòu)建系統(tǒng)在哪里尋找這個(gè)庫(kù)。
欲知詳情,參見(jiàn)路徑映射概覽

如果你發(fā)現(xiàn)庫(kù)中的更改沒(méi)有反映到應(yīng)用中,那么你的應(yīng)用很可能正在使用這個(gè)庫(kù)的舊版本。

每當(dāng)你對(duì)它進(jìn)行修改時(shí),都可以重建你的庫(kù),但這個(gè)額外的步驟需要時(shí)間。增量構(gòu)建功能可以改善庫(kù)的開(kāi)發(fā)體驗(yàn)。每當(dāng)文件發(fā)生變化時(shí),都會(huì)執(zhí)行局部構(gòu)建,并修補(bǔ)一些文件。

增量構(gòu)建可以作為開(kāi)發(fā)環(huán)境中的后臺(tái)進(jìn)程運(yùn)行。要啟用這個(gè)特性,可以在構(gòu)建命令中加入 ?--watch? 標(biāo)志:

ng build my-lib --watch
CLI 的 ?build ?命令為庫(kù)使用與應(yīng)用不同的構(gòu)建器,并調(diào)用不同的構(gòu)建工具。
  • 應(yīng)用的構(gòu)建體系(?@angular-devkit/build-angular?)基于 ?webpack?,并被包含在所有新的 Angular CLI 項(xiàng)目中。
  • 庫(kù)的構(gòu)建體系基于 ?ng-packagr?。只有在使用 ?ng generate library my-lib? 添加庫(kù)時(shí),它才會(huì)添加到依賴項(xiàng)中。
這兩種構(gòu)建體系支持不同的東西,即使它們支持相同的東西,它們的執(zhí)行方式也不同。這意味著同一套 TypeScript 源碼在生成庫(kù)時(shí)生成的 JavaScript 代碼可能與生成應(yīng)用時(shí)生成的 JavaScript 代碼也不同。
因此,依賴于庫(kù)的應(yīng)用應(yīng)該只使用指向內(nèi)置庫(kù)的 TypeScript 路徑映射。TypeScript 的路徑映射不應(yīng)該指向庫(kù)的 ?.ts? 源文件。

發(fā)布庫(kù)

發(fā)布庫(kù)時(shí)可以使用兩種分發(fā)格式:

分發(fā)格式

詳情

部分 Ivy(推薦)

包含可移植代碼,從 v12 開(kāi)始,使用任何版本的 Angular 構(gòu)建的 Ivy 應(yīng)用都可以使用這些可移植代碼。

完全 Ivy

包含專用的 Angular Ivy 指令,不能保證它們可在 Angular 的不同版本中使用。這種格式要求庫(kù)和應(yīng)用使用完全相同的 Angular 版本構(gòu)建。這種格式對(duì)于直接從源代碼構(gòu)建所有庫(kù)和應(yīng)用代碼的環(huán)境很有用。

對(duì)于發(fā)布到 npm 的庫(kù),請(qǐng)使用 partial-Ivy 格式,因?yàn)樗?nbsp;Angular 的各個(gè)補(bǔ)丁版本之間是穩(wěn)定的。

如果要發(fā)布到 npm,請(qǐng)避免使用完全 Ivy 的方式編譯庫(kù),因?yàn)樯傻?nbsp;Ivy 指令不屬于 Angular 公共 API 的一部分,因此在補(bǔ)丁版本之間可能會(huì)有所不同。

確保庫(kù)版本兼容性

用于構(gòu)建應(yīng)用的 Angular 版本應(yīng)始終與用于構(gòu)建其任何依賴庫(kù)的 Angular 版本相同或更大。比如,如果你有一個(gè)使用 Angular 13 版的庫(kù),則依賴于該庫(kù)的應(yīng)用應(yīng)該使用 Angular 13 版或更高版本。Angular 不支持為該應(yīng)用使用早期版本。

如果打算將庫(kù)發(fā)布到 npm,請(qǐng)通過(guò)在 ?tsconfig.prod.json? 的 ?"compilationMode": "partial"? 來(lái)使用部分 Ivy 代碼進(jìn)行編譯。這種部分格式在不同版本的 Angular 之間是穩(wěn)定的,因此可以安全地發(fā)布到 npm。這種格式的代碼在應(yīng)用程序構(gòu)建期間會(huì)使用相同版本的 Angular 編譯器進(jìn)行處理,以確保應(yīng)用程序及其所有庫(kù)使用的是同一個(gè)版本的 Angular。

如果要發(fā)布到 npm,請(qǐng)避免使用完全 Ivy 代碼來(lái)編譯庫(kù),因?yàn)樯傻?nbsp;Ivy 指令不屬于 Angular 公共 API 的一部分,因此在補(bǔ)丁版本之間可能會(huì)有所不同。

如果你以前從未在 npm 中發(fā)布過(guò)軟件包,則必須創(chuàng)建一個(gè)用戶帳戶。在發(fā)布 npm 程序包中了解更多信息。

在 Angular CLI 之外使用部分 Ivy 代碼

應(yīng)用將 npm 中的許多 Angular 庫(kù)安裝到其 ?node_modules ?目錄中。但是,這些庫(kù)中的代碼不能與已編譯的應(yīng)用直接捆綁在一起,因?yàn)樗形赐耆幾g。要完成編譯,可以使用 Angular 鏈接器。

對(duì)于不使用 Angular CLI 的應(yīng)用程序,此鏈接器可用作 Babel 插件。該插件要從 ?@angular/compiler-cli/linker/babel? 導(dǎo)入。

Angular 鏈接器的 Babel 插件支持構(gòu)建緩存,這意味著鏈接器只需一次處理庫(kù),而與其他 npm 操作無(wú)關(guān)。

下面的例子借助 babel-loader 把此鏈接器注冊(cè)為 Babel 插件,從而將此插件集成到自定義 Webpack 構(gòu)建中。

import linkerPlugin from '@angular/compiler-cli/linker/babel';

export default {
  // ...
  module: {
    rules: [
      {
        test: /\.m?js$/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins: [linkerPlugin],
            compact: false,
            cacheDirectory: true,
          }
        }
      }
    ]
  }
  // ...
}

Angular CLI 自動(dòng)集成了鏈接器插件,因此,如果你這個(gè)庫(kù)的使用方也在使用 CLI,則他們可以從 npm 安裝 Ivy 原生庫(kù),而無(wú)需任何其他配置。


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)