開發者工具
程式庫

建立程式庫

本頁提供如何建立並發行新程式庫以擴充 Angular 功能的概念性總覽。

如果你發現自己需要在多個應用程式中解決同一個問題(或想與其他開發者分享你的解決方案),那你就有了一個程式庫的候選題目。 一個簡單的例子可能是一個按鈕,它會將使用者帶到你公司的網站,並且會包含在你公司建置的所有應用程式中。

開始使用

使用 Angular CLI,透過下列指令在新工作區中產生新的程式庫骨架。

ng new my-workspace --no-create-application
cd my-workspace
ng generate library my-lib

為你的程式庫命名

如果你想日後將程式庫發行到 npm 等公開套件註冊表,在選擇程式庫名稱時應該非常謹慎。 請參閱發行你的程式庫。

避免使用以 ng- 為字首的名稱,例如 ng-library。 ng- 字首是 Angular 框架及其程式庫使用的保留關鍵字。 ngx- 字首是慣例上偏好的字首,用來表示該程式庫適合與 Angular 一起使用。 它也能很好地向註冊表的使用者表明,這個程式庫與其他 JavaScript 框架的程式庫有所區別。

ng generate 指令會在你的工作區中建立 projects/my-lib 資料夾,裡面包含一個元件。

HELPFUL: 程式庫專案結構的更多詳情,請參閱專案檔案結構指南的程式庫專案檔案一節。

使用 monorepo 模型,讓同一個工作區容納多個專案。 請參閱設定多專案工作區。

當你產生新的程式庫時,工作區設定檔 angular.json 會被更新,加入一個類型為 library 的專案。

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

使用 CLI 指令建置、測試並檢查(lint)專案:

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

請注意,為這個專案設定的建置器與應用程式專案的預設建置器不同。 這個建置器除其他事項外,還能確保程式庫一律以 AOT 編譯器建置。

若要讓程式庫程式碼能重複使用,你必須為它定義公開 API。 這個「使用者層」定義了你的程式庫的使用者可以取用什麼。 你的程式庫的使用者應該能透過單一匯入路徑,存取公開功能(例如服務提供者與一般工具函式)。

你的程式庫的公開 API 維護在程式庫資料夾中的 public-api.ts 檔案裡。 從這個檔案匯出的任何東西,在你的程式庫被匯入應用程式時都會公開。

你的程式庫應提供文件(通常是 README 檔案),說明安裝與維護方式。

進入點

進入點是一個模組指定符(module specifier),使用者從它匯入,連同該指定符所公開的公開 API。 每個程式庫都有一個主要進入點,並且可以加入任意數量的次要進入點。

你可以在程式庫的 package.json 檔案中,使用 "exports" 欄位來設定進入點。 "." 鍵定義了主要進入點,指向匯出其公開 API 的 TypeScript 檔案:

projects/my-lib/package.json

{
  "name": "my-lib",
  "version": "0.0.1",
  "exports": {
    ".": "./src/public-api.ts"
  },
  …
}

使用者以套件名稱匯入主要進入點:

import {ThemeService} from 'my-lib';

次要進入點是 "exports" 中的一個額外子路徑鍵,指向它自己的公開 API 檔案。 例如,"./button" 匯出會讓使用者能使用 my-lib/button:

import {ButtonComponent} from 'my-lib/button';

Angular 套件也使用相同的結構。 例如,@angular/core 是主要進入點,而 @angular/core/testing 是同一個套件的次要進入點。

HELPFUL: 進入點如何在 Angular 套件格式中實現程式碼分割並定義區塊(chunk)邊界的架構性總覽,請參閱進入點與程式碼分割。

加入次要進入點

使用 entry-point 原理圖來在你的程式庫中產生新的次要進入點:

ng generate entry-point button --project my-lib

你也可以產生巢狀子路徑,例如 testing/harness:

ng generate entry-point testing/harness --project my-lib

執行 ng generate entry-point button --project my-lib 會執行下列步驟:

  1. 在程式庫內建立一個 button/src/ 目錄,裡面有它自己的 public-api.ts 檔案與初始元件檔案:

    projects/my-lib/
    ├── package.json
    ├── src/
    │   ├── public-api.ts         (primary entry point: my-lib)
    │   └── lib/
    │       ├── my-lib.spec.ts
    │       └── my-lib.ts
    └── button/
        └── src/
            ├── button.spec.ts
            ├── button.ts
            └── public-api.ts     (secondary entry point: my-lib/button)
  2. 在程式庫的 package.json 中註冊 "./button" 子路徑:

    projects/my-lib/package.json

    {
      "name": "my-lib",
      "version": "0.0.1",
      "exports": {
        ".": "./src/public-api.ts",
        "./button": "./button/src/public-api.ts"
      },
      …
    }
  3. 在工作區的 tsconfig.json 中加入 "my-lib/*" 萬用字元路徑對應(若尚未加入),讓同一個工作區中的應用程式能從已建置的程式庫輸出解析次要進入點:

    tsconfig.json

    {
      "compilerOptions": {
        "paths": {
          "my-lib": ["./dist/my-lib"],
          "my-lib/*": ["./dist/my-lib/*"]
        }
      }
    }

    因為 "my-lib/*" 是萬用字元模式,它會在你第一次產生次要進入點時加入,並自動涵蓋目前與未來所有的次要進入點,不需要再更新 tsconfig.json。

在建置期間,@angular/build:library 會從 projects/my-lib/package.json 的 "exports" 欄位發現所有進入點,編譯並打包每個進入點,再將對應的 types 與 default 匯出條件寫入 dist/my-lib/package.json。

將應用程式的部分功能重構為程式庫

若要讓你的解決方案能重複使用,你需要調整它,讓它不依賴應用程式專屬的程式碼。 將應用程式功能遷移到程式庫時,以下是一些需要考慮的事項。

  • 元件、管道等宣告應設計為無狀態,也就是說,它們不依賴也不更改外部變數。 如果你確實依賴狀態,你需要逐一評估每個情況,決定那是應用程式狀態,還是程式庫應管理的狀態。

  • 元件在內部訂閱的任何 observable,都應在這些元件的生命週期中清理並處置

  • 元件應透過輸入(input)公開它們的互動以提供情境,並透過輸出(output)將事件傳達給其他元件

  • 檢查所有內部依賴。

    • 對於元件或服務中使用的自訂類別或介面,檢查它們是否依賴其他也需要遷移的類別或介面
    • 同樣地,如果你的程式庫程式碼依賴某個服務,該服務也需要遷移
    • 如果你的程式庫程式碼或其範本依賴其他程式庫(例如 Angular Material),你必須為你的程式庫設定那些依賴
  • 考慮你如何向用戶端應用程式提供服務。

    • 服務應宣告它們自己的提供者,而不是在 NgModule 或元件中宣告提供者。 宣告提供者能讓該服務具備可搖樹優化的特性。 這種做法能讓編譯器在程式庫被匯入的應用程式從未注入該服務時,將它排除在包之外。 更多相關資訊請參閱可搖樹的提供者。

    • 如果你註冊全域服務提供者,請公開一個 provideXYZ() 提供者函式。

    • 如果你的程式庫提供可能不會被所有用戶端應用程式使用的選用服務,請使用輕量權杖設計模式,為該情況支援適當的搖樹優化

透過程式碼產生原理圖與 CLI 整合

程式庫通常包含可重複使用的程式碼,用來定義元件、服務與其他 Angular 產物(管道、指令),供你匯入專案中。 程式庫會被包裝成 npm 套件,以便發行與分享。 這個套件也可以包含原理圖(schematic),為直接在你的專案中產生或轉換程式碼提供指示,就像 CLI 以 ng generate component 建立一個通用的新元件一樣。 與程式庫一起包裝的原理圖,例如可以向 Angular CLI 提供所需的資訊,產生一個能設定並使用該程式庫所定義的特定功能(或一組功能)的元件。 其中一個例子是 Angular Material 的導航原理圖,它設定 CDK 的 BreakpointObserver,並將它與 Material 的 MatSideNav 及 MatToolbar 元件一起使用。

建立並包含下列種類的原理圖:

  • 包含一個安裝原理圖,讓 ng add 能將你的程式庫加入專案
  • 在你的程式庫中包含產生原理圖,讓 ng generate 能在專案中為你定義的產物(元件、服務、測試)搭建鷹架
  • 包含一個更新原理圖,讓 ng update 能更新你的程式庫的依賴,並為新版本中的破壞性變更提供遷移

你在程式庫中包含什麼,取決於你的任務。 例如,你可以定義一個原理圖,建立一個預先填入罐頭資料的下拉選單,展示如何將它加入應用程式。 如果你想要一個每次包含不同傳入值的下拉選單,你的程式庫可以定義一個原理圖,以給定的設定來建立它。 開發者接著就能用 ng generate 為自己的應用程式設定一個實例。

假設你想讀取一個設定檔,然後根據該設定產生一個表單。 如果該表單需要由使用你的程式庫的開發者做額外客製化,做成原理圖可能最合適。 然而,如果表單永遠都是同一個樣子,不太需要開發者客製化,那麼你可以建立一個動態元件,接收設定並產生表單。 一般來說,客製化越複雜,原理圖的做法就越有用。

更多資訊請參閱原理圖總覽與程式庫的原理圖。

發行你的程式庫

使用 Angular CLI 與 npm 套件管理工具,將你的程式庫建置並發行為 npm 套件。

Angular CLI 使用 @angular/build:library 建置器,將你的程式碼編譯成符合 Angular Package Format 的套件,讓你可以發行到 npm。 關於支援的發行格式,以及如何為你的程式庫選擇正確格式的指引,請參閱發行程式庫。

你一律應使用 production 設定(ng build 的預設設定)來建置待發行的程式庫。 這能確保產生的輸出使用適當的最佳化,以及 npm 所需的正確套件格式。

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

在程式庫中管理素材

在你的 Angular 程式庫中,可發行的產物可以包含額外的素材,例如主題檔案、Sass mixin 或文件(如更新記錄)。 依預設,程式庫建置器會將 README.md 與 LICENSE* 檔案從你的程式庫根目錄複製到輸出目錄,並將元件樣式表中以相對 url() 路徑參照的素材以 data URL 的形式內嵌。 若要在建置時複製額外的檔案,請在 angular.json 中你的程式庫 build 目標上設定 assets 選項:

angular.json

{
  "projects": {
    "my-lib": {
      "architect": {
        "build": {
          "builder": "@angular/build:library",
          "options": {
            "tsConfig": "projects/my-lib/tsconfig.lib.json",
            "assets": [
              "projects/my-lib/CHANGELOG.md",
              "projects/my-lib/_index.scss",
              "projects/my-lib/prebuilt-themes",
              {
                "glob": "**/*.scss",
                "input": "projects/my-lib/src/theming",
                "output": "theming"
              }
            ]
          }
        }
      }
    }
  }
}

IMPORTANT: 包含 Sass mixin 或預先編譯的 CSS 等額外素材時,請在你的程式庫 package.json 的 "exports" 欄位中公開它們。

程式庫建置器會將自訂的 "exports" 子路徑與條件,和產生的進入點匯出合併,讓你能在 TypeScript 進入點之外,公開樣式表或自訂條件:

projects/my-lib/package.json

{
  "exports": {
    ".": {
      "sass": "./_index.scss",
      "default": "./src/public-api.ts"
    },
    "./theming": {
      "sass": "./theming/_theming.scss"
    },
    "./prebuilt-themes/indigo-pink.css": {
      "style": "./prebuilt-themes/indigo-pink.css"
    }
  }
}

對等依賴

Angular 程式庫應將它所依賴的任何 @angular/* 依賴列為對等依賴(peer dependency)。 這能確保當各模組要求 Angular 時,它們全都取得完全相同的模組。 如果某個程式庫將 @angular/core 列在 dependencies 而非 peerDependencies 中,它可能改取得另一個不同的 Angular 模組,進而導致你的應用程式壞掉。

在應用程式中使用你自己的程式庫

你不需要將程式庫發行到 npm 套件管理工具,就能在同一個工作區中使用它,但你必須先建置它。

若要在應用程式中使用你自己的程式庫:

  • 建置程式庫。 程式庫在建置完成前無法使用。
ng build my-lib
  • 在你的應用程式中,以名稱從程式庫匯入:
import {myExport} from 'my-lib';

建置與重新建置你的程式庫

如果你尚未將程式庫發行為 npm 套件,再從 npm 將套件安裝回你的應用程式,建置步驟就很重要。 例如,如果你複製(clone)你的 git 儲存庫並執行 npm install,若你尚未建置程式庫,你的編輯器會將 my-lib 的匯入顯示為遺失。

HELPFUL: 當你在 Angular 應用程式中從程式庫匯入某個東西時,Angular 會尋找程式庫名稱與磁碟上位置之間的對應。 當你安裝程式庫套件時,對應位於 node_modules 資料夾中。 當你建置自己的程式庫時,它必須在你的 tsconfig 路徑中找到對應。

使用 Angular CLI 產生程式庫時,會自動將它的路徑加入 tsconfig 檔案。 Angular CLI 使用 tsconfig 路徑,告訴建置系統去哪裡找程式庫。

更多資訊請參閱路徑對應總覽。

每當你變更程式庫時,你都可以重新建置它,但這個額外的步驟很花時間。 增量建置功能改善了程式庫的開發體驗。 每次有檔案變更時,系統會執行部分建置,只產出被修改的檔案。

增量建置可以作為背景程序在你的開發環境中執行。 若要利用這項功能,請在建置指令中加入 --watch 旗標:

ng build my-lib --watch

IMPORTANT: CLI 的 build 指令為程式庫使用的建置器(@angular/build:library),與為應用程式使用的建置器(@angular/build:application)不同。

程式庫會依照 Angular Package Format 進行編譯與打包,這表示同一份 TypeScript 原始碼,在建置後的程式庫中產生的輸出,會與在建置後的應用程式中產生的不同。

正因如此,依賴某個程式庫的應用程式,應只使用指向已建置程式庫的 TypeScript 路徑對應。 TypeScript 路徑對應不應指向程式庫的原始 .ts 檔案。

為本機開發連結程式庫

本節說明如何使用你的套件管理工具的本機連結功能 (例如 npm link 或 pnpm link),在本機開發期間將獨立的 Angular 程式庫與外部應用程式一起測試, 而不倚賴 monorepo 工作區結構,也不發行到 npm 註冊表。

NOTE: 如果你的程式庫與應用程式位於同一個 Angular 工作區(monorepo 設定),標準的 monorepo 工作流程會自動處理連結,通常效率更高。這個本機連結做法最適合下列情況:

  • 你正在開發一個獨立的程式庫,需要用外部的取用端應用程式測試變更。
  • 你正在 monorepo 工作區之外的取用端應用程式中測試程式庫變更。

設定取用端應用程式

若要使用連結的程式庫,你需要以如下設定來設定你的應用程式的 angular.json 檔案:

{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "preserveSymlinks": true
          },
          "configurations": {
            "development": {
              "sourceMap": {
                "scripts": true,
                "styles": true,
                "vendor": true
              }
            }
          }
        },
        "serve": {
          "builder": "@angular/build:dev-server",
          "options": {
            "prebundle": {
              "exclude": ["my-lib"]
            }
          }
        }
      }
    }
  }
}

設定選項說明:

  • preserveSymlinks: true:指示建置系統跟隨你的套件管理工具的連結指令所建立的符號連結,而不是解析到符號連結的原始位置。這對避免依賴的 node 套件出現多份副本至關重要。
  • sourceMap.vendor:啟用廠商原始碼對應表(尤其是 vendor: true),讓連結的程式庫程式碼更容易除錯。
  • prebundle.exclude:依預設,Angular CLI 可以預先打包所有 node 依賴。排除你的程式庫,能確保連結的原始碼在變更時被正確監看並重新建置。

發行程式庫

發行程式庫時有兩種發行格式可以使用:

發行格式 詳情
Partial-Ivy(建議) 包含可攜式的程式碼,可供以 Angular v12 以後任何版本建置的 Ivy 應用程式取用。
Full-Ivy 包含私有的 Angular Ivy 指令。編譯器 ABI(這些指令呼叫的內部執行階段 API)在修補版本之間是穩定的,但在次要版本之間可能變更。這種格式要求程式庫與應用程式以相同次要版本的 Angular 建置。這種格式適用於所有程式庫與應用程式程式碼都從原始碼建置,或共享於微前端架構的環境。

發行到 npm 時,請使用 Partial-Ivy 格式,因為它在 Angular 的修補版本之間是穩定的。

若要發行到 npm,請避免以 Full-Ivy 程式碼編譯程式庫,因為產生的 Ivy 指令依賴編譯器 ABI,而 ABI 可能在次要版本之間變更。

確保程式庫版本相容性

用來建置應用程式的 Angular 版本,一律應與用來建置其任何依賴程式庫的 Angular 版本相同或更高。 例如,如果你有一個使用 Angular 13 版的程式庫,依賴該程式庫的應用程式應使用 Angular 13 版或更高版本。 Angular 不支援應用程式使用較早的版本。

如果你打算將程式庫發行到 npm,請使用 production 設定來建置,它預設會將 "compilationMode" 設為 "partial",以發出 Partial-Ivy 程式碼。 這種部分格式在不同版本的 Angular 之間是穩定的,因此發行到 npm 是安全的。 採用這種格式的程式碼,會在應用程式建置期間使用相同版本的 Angular 編譯器處理,確保應用程式與其所有程式庫都使用單一版本的 Angular。

若要發行到 npm,請避免以 Full-Ivy 程式碼編譯程式庫,因為產生的 Ivy 指令依賴編譯器 ABI,而 ABI 可能在次要版本之間變更。

如果你以前從未在 npm 發行過套件,你必須先建立使用者帳號。 詳情請參閱發行 npm 套件。

在 Angular CLI 之外取用 Partial-Ivy 程式碼

應用程式會從 npm 將許多 Angular 程式庫安裝到它的 node_modules 目錄中。 然而,這些程式庫中的程式碼尚未完整編譯,無法直接與建置好的應用程式一起打包。 若要完成編譯,請使用 Angular 連結器(linker)。

對於不使用 Angular CLI 的應用程式,連結器以 Babel 外掛程式的形式提供。 這個外掛程式要從 @angular/compiler-cli/linker/babel 匯入。

Angular 連結器 Babel 外掛程式支援建置快取,也就是說,無論其他 npm 操作如何,程式庫只需要被連結器處理一次。

將外掛程式整合進自訂 webpack 建置的範例:使用 babel-loader 將連結器註冊為 Babel 外掛程式。

webpack.config.mjs

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,
          },
        },
      },
    ],
  },
  // ...
};

HELPFUL: Angular CLI 會自動整合連結器外掛程式,因此如果你的程式庫的使用者正在使用 CLI,他們就能從 npm 安裝 Ivy 原生的程式庫,不需要任何額外設定。