填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

建立函式庫

Creating libraries

對於如何建立和發佈新函式庫,以擴充套件 Angular 的功能,本頁面提供了一個概念性的總覽

This page provides a conceptual overview of how you can create and publish new libraries to extend Angular functionality.

如果你發現自己要在多個應用中解決同樣的問題(或者要把你的解決方案分享給其它開發者),你就有了一個潛在的函式庫。 簡單的例子就是一個用來把使用者帶到你公司網站上的按鈕,該按鈕會包含在你公司建構的所有應用中。

If you find that you need to solve the same problem in more than one app (or want to share your solution with other developers), you have a candidate for a library. A simple example might be a button that sends users to your company website, that would be included in all apps that your company builds.

快速上手

Getting started

使用 Angular CLI,用以下命令在新的工作空間中產生一個新函式庫的骨架:

Use the Angular CLI to generate a new library skeleton in a new workspace with the following commands.

ng new my-workspace --create-application=false cd my-workspace ng generate library my-lib
      
      ng new my-workspace --create-application=false
cd my-workspace
ng generate library my-lib
    

ng generate 命令會在你的工作空間中建立 projects/my-lib 資料夾,其中包含帶有一個元件和一個服務的 NgModule。

The ng generate command creates the projects/my-lib folder in your workspace, which contains a component and a service inside an NgModule.

要了解關於函式庫專案的目錄結構的詳細資訊,參閱專案檔案結構中的函式庫專案檔案部分。

For more details on how a library project is structured, refer to the Library project files section of the Project File Structure guide.

你可以使用單一儲存庫(monorepo)模式將同一個工作空間用於多個專案。參閱建立多專案型工作區

You can use the monorepo model to use the same workspace for multiple projects. See Setting up for a multi-project workspace.

當你產生一個新函式庫時,該工作區的配置檔案 angular.json 中也增加了一個 'library' 型別的專案。

When you generate a new library, the workspace configuration file, angular.json, is updated with a project of type library.

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

你可以使用 CLI 命令來建構、測試和 lint 這個專案:

You can build, test, and lint the project with CLI commands:

ng build my-lib ng test my-lib ng lint my-lib
      
      ng build my-lib
ng test my-lib
ng lint my-lib
    

注意,該專案配置的建構器與應用類別專案的預設建構器不同。此建構器可以確保函式庫永遠使用 AoT 編譯器建構,而不必再指定 --prod 標誌。

Notice that the configured builder for the project is different from the default builder for app projects. This builder, among other things, ensures that the library is always built with the AOT compiler, without the need to specify the --prod flag.

要讓函式庫程式碼可以複用,你必須為它定義一個公共的 API。這個“使用者層”定義了函式庫中消費者的可用內容。該函式庫的使用者應該可以透過單個的匯入路徑來訪問公共功能(如 NgModules、服務提供者和工具函式)。

To make library code reusable you must define a public API for it. This "user layer" defines what is available to consumers of your library. A user of your library should be able to access public functionality (such as NgModules, service providers and general utility functions) through a single import path.

函式庫的公共 API 是在函式庫資料夾下的 public-api.ts 檔案中維護的。當你的函式庫被匯入應用時,從該檔案匯出的所有內容都會公開。請使用 NgModule 來暴露這些服務和元件。

The public API for your library is maintained in the public-api.ts file in your library folder. Anything exported from this file is made public when your library is imported into an application. Use an NgModule to expose services and components.

你的函式庫裡應該提供一些文件(通常是 README 檔案)來指導別人安裝和維護。

Your library should supply documentation (typically a README file) for installation and maintenance.

把應用中的部分內容重構成一個函式庫

Refactoring parts of an app into a library

為了讓你的解決方案可供複用,你需要對它進行調整,以免它依賴應用特有的程式碼。在將應用的功能遷移到函式庫中時,需要注意以下幾點。

To make your solution reusable, you need to adjust it so that it does not depend on app-specific code. Here are some things to consider in migrating application functionality to a library.

  • 元件和管道之類別的可宣告物件應該設計成無狀態的,這意味著它們不依賴或修改外部變數。如果確實依賴於狀態,就需要對每種情況進行評估,以決定它是應用的狀態還是函式庫要管理的狀態。

    Declarations such as components and pipes should be designed as stateless, meaning they don’t rely on or alter external variables. If you do rely on state, you need to evaluate every case and decide whether it is application state or state that the library would manage.

  • 元件內部訂閱的所有可觀察物件都應該在這些元件的生命週期內進行清理和釋放。

    Any observables that the components subscribe to internally should be cleaned up and disposed of during the lifecycle of those components.

  • 元件對外暴露互動方式時,應該透過輸入引數來提供上下文,透過輸出引數來將事件傳給其它元件。

    Components should expose their interactions through inputs for providing context, and outputs for communicating events to other components.

  • 檢查所有內部依賴。

    Check all internal dependencies.

    • 對於在元件或服務中使用的自訂類別或介面,檢查它們是否依賴於其它類別或介面,它們也需要一起遷移。

      For custom classes or interfaces used in components or service, check whether they depend on additional classes or interfaces that also need to be migrated.

    • 同樣,如果你的函式庫程式碼依賴於某個服務,則需要遷移該服務。

      Similarly, if your library code depends on a service, that service needs to be migrated.

    • 如果你的函式庫程式碼或其範本依賴於其它函式庫(比如 Angular Material ),你就必須把它們配置為該函式庫的依賴。

      If your library code or its templates depend on other libraries (such as Angular Material, for instance), you must configure your library with those dependencies.

  • 考慮如何為客戶端應用提供服務。

    Consider how you provide services to client applications.

    • 服務應該自己宣告提供者(而不是在 NgModule 或元件中宣告提供者),以便它們是可搖樹優化的 。這樣,如果伺服器從未被注入到匯入該函式庫的應用中,編譯器就會把該服務從該 bundle 中刪除。關於這方面的更多資訊,請參閱Tree-shakable 提供者

      Services should declare their own providers, rather than declaring providers in the NgModule or a component. Declaring a provider makes that service tree-shakable. This practice allows the compiler to leave the service out of the bundle if it never gets injected into the application that imports the library. For more about this, see Tree-shakable providers.

    • 如果你在多個 NgModules 註冊全域性服務提供者或提供者共享,使用forRoot()forChild() 設計模式由提供RouterModule

      If you register global service providers or share providers across multiple NgModules, use the forRoot() and forChild() design patterns provided by the RouterModule.

    • 如果你的函式庫中提供的可選服務可能並沒有被所有的客戶端應用所使用,那麼就可以透過輕量級令牌設計模式為這種情況支援正確的樹狀結構了

      If your library provides optional services that might not be used by all client applications, support proper tree-shaking for that case by using the lightweight token design pattern.

使用程式碼產生原理圖與 CLI 整合

Integrating with the CLI using code-generation schematics

一個函式庫通常都包含可複用的程式碼 ,用於定義元件,服務,以及你剛才匯入到專案中的其他 Angular 工件(管道,指令等等)。函式庫被打包成一個 npm 套件,用於發佈和共享。這個套件還可以包含一些原理圖 ,它提供直接在專案中產生或轉換程式碼的指令,就像 CLI 用 ng generate component 建立一個通用的新 ng generate component 。例如,用函式庫打套件的原理圖可以為 Angular CLI 提供產生元件所需的資訊,該元件用於配置和使用該函式庫中定義的特定特性或一組特性。這方面的一個例子是 Angular Material 的導航原理圖,它用來配置 CDK 的 BreakpointObserver 並把它與 Material 的 MatSideNavMatToolbar 元件一起使用。

A library typically includes reusable code that defines components, services, and other Angular artifacts (pipes, directives, and so on) that you simply import into a project. A library is packaged into an npm package for publishing and sharing. This package can also include schematics that provide instructions for generating or transforming code directly in your project, in the same way that the CLI creates a generic new component with ng generate component. A schematic that is packaged with a library can, for example, provide the Angular CLI with the information it needs to generate a component that configures and uses a particular feature, or set of features, defined in that library. One example of this is Angular Material's navigation schematic which configures the CDK's BreakpointObserver and uses it with Material's MatSideNav and MatToolbar components.

你可以建立幷包含以下幾種原理圖。

You can create and include the following kinds of schematics.

  • 包含一個安裝原理圖,以便 ng add 可以把你的函式庫新增到專案中。

    Include an installation schematic so that ng add can add your library to a project.

  • 在函式庫中包含了產生原理圖,以便 ng generate 可以為專案中的已定義工件(元件,服務,測試等)提供支援。

    Include generation schematics in your library so that ng generate can scaffold your defined artifacts (components, services, tests, and so on) in a project.

  • 包含一個更新的原理圖,以便 ng update 可以更新你的函式庫的依賴,並提供一些遷移來破壞新版本中的更改。

    Include an update schematic so that ng update can update your library’s dependencies and provide migrations for breaking changes in new releases.

你的函式庫中所包含的內容取決於你的任務。例如,你可以定義一個原理圖來建立一個預先填充了固定資料的下拉列表,以展示如何把它新增到一個應用中。如果你想要一個每次包含不同傳入值的下拉列表,那麼你的函式庫可以定義一個原理圖來用指定的配置建立它。然後,開發人員可以使用 ng generate 為自己的應用配置一個實例。

What you include in your library depends on your task. For example, you could define a schematic to create a dropdown that is pre-populated with canned data to show how to add it to an app. If you want a dropdown that would contain different passed-in values each time, your library could define a schematic to create it with a given configuration. Developers could then use ng generate to configure an instance for their own app.

假設你要讀取配置檔案,然後根據該配置產生表單。如果該表單需要函式庫的使用者進行額外的自訂,它可能最適合用作 schematic。但是,如果這些表單總是一樣的,開發人員不需要做太多自訂工作,那麼你就可以建立一個動態的元件來獲取配置並產生表單。通常,自訂越複雜,schematic 方式就越有用。

Suppose you want to read a configuration file and then generate a form based on that configuration. If that form will need additional customization by the developer who is using your library, it might work best as a schematic. However, if the form will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form. In general, the more complex the customization, the more useful the schematic approach.

要了解更多資訊,參閱 原理圖概覽供函式庫使用的原理圖

To learn more, see Schematics Overview and Schematics for Libraries.

發佈你的函式庫

Publishing your library

使用 Angular CLI 和 npm 套件管理器來建構你的函式庫併發布為 npm 套件。

Use the Angular CLI and the npm package manager to build and publish your library as an npm package.

使用 Angular CLI 和 npm 套件管理器來把你的函式庫建構併發布成 npm 套件。不建議把 Ivy 格式的函式庫發佈到 NPM 儲存庫。在把某個函式庫發佈到 NPM 之前,使用 --prod 標誌建構它,此標誌會使用老的編譯器和執行時,也就是檢視引擎(View Engine),以代替 Ivy.

Before publishing a library to NPM, build it using the --prod flag which will use the older compiler and runtime known as View Engine instead of Ivy.

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

如果你之前從未在 npm 中發佈過套件,就必須建立一個使用者帳號。點此閱讀發佈 npm 套件的更多資訊。

If you've never published a package in npm before, you must create a user account. Read more in Publishing npm Packages.

目前,不建議把 Ivy 函式庫發佈到 NPM 上,因為 Ivy 產生的程式碼不會向後相容 View Engine 的應用,所以,使用 View Engine 的應用將不能消費它們。此外,Ivy 的內部用法尚未完全穩定,這有潛在的可能會導致使用其它 Angular 版本的消費程式碼被此函式庫的建構者破壞。

For now, it is not recommended to publish Ivy libraries to NPM because Ivy generated code is not backward compatible with View Engine, so apps using View Engine will not be able to consume them. Furthermore, the internal Ivy instructions are not yet stable, which can potentially break consumers using a different Angular version from the one used to build the library.

當已發佈的函式庫在 Ivy 應用中使用時,Angular CLI 會自動把它用一個叫作 Angular 相容性編譯器(ngcc)的工具轉換成 Ivy 版本的。這樣一來,把你的函式庫用 View Engine 編譯器發佈,就可以確保它們能同時被 View Engine 和 Ivy 的應用透明的消費。

When a published library is used in an Ivy app, the Angular CLI will automatically convert it to Ivy using a tool known as the Angular compatibility compiler (ngcc). Thus, publishing your libraries using the View Engine compiler ensures that they can be transparently consumed by both View Engine and Ivy apps.

管理函式庫中的資產(assets)

Managing assets in a library

ng-packagr 工具的 9.x 版本開始,你可以配置它,以便在建構過程中自動把資產複製到函式庫的發佈套件裡。 如果你的函式庫需要發佈一些可選的主題檔案、Sass mixins 或文件(比如變更記錄),可以使用這個特性。

Starting with version 9.x of the ng-packagr tool, you can configure the tool to automatically copy assets into your library package as part of the build process. You can use this feature when your library needs to publish optional theming files, Sass mixins, or documentation (like a changelog).

連結函式庫

Linked libraries

在開發要發佈的函式庫時,可以使用 npm link 來避免每次建構時都被迫重新安裝函式庫。

While working on a published library, you can use npm link to avoid reinstalling the library on every build.

必須在每次修改時都重新建構這個函式庫。在連結函式庫時,確保建構步驟在監視模式下執行,並且該函式庫的 package.json 配置指向了正確的入口點。例如,main 應該指向一個 JavaScript 檔案,而不是一個 TypeScript 檔案。

The library must be rebuilt on every change. When linking a library, make sure that the build step runs in watch mode, and that the library's package.json configuration points at the correct entry points. For example, main should point at a JavaScript file, not a TypeScript file.

對同級依賴使用 TypeScript 路徑對映

Use TypeScript path mapping for peer dependencies

Angular 函式庫應該把所有 @angular/* 依賴項都列為同級依賴。這確保了當各個模組請求 Angular 時,都會得到完全相同的模組。如果某個函式庫在 dependencies 列出 @angular/core 而不是用 peerDependencies,它可能會得到一個不同的 Angular 模組,這會破壞你的應用。

Angular libraries should list all @angular/* dependencies as peer dependencies. This ensures that when modules ask for Angular, they all get the exact same module. If a library lists @angular/core in dependencies instead of peerDependencies, it might get a different Angular module instead, which would cause your application to break.

在開發函式庫的過程中,你必須透過 devDependencies 安裝所有的同級依賴,以確保函式庫能夠正確編譯。這樣,一個連結過的函式庫就會擁有自己的一組用於建構的 Angular 函式庫,它們位於 node_modules 資料夾中。但是,這會在建構或執行應用程式時引發問題。

While developing a library, you must install all peer dependencies through devDependencies to ensure that the library compiles properly. A linked library will then have its own set of Angular libraries that it uses for building, located in its node_modules folder. However, this can cause problems while building or running your application.

為了解決此問題,你可以使用 TypeScript 路徑對映來告訴 TypeScript 它應該從指定的位置載入某些模組。在 TypeScript 配置檔案 ./tsconfig.json 中列出該函式庫使用的所有同級依賴,並把它們指向應用的 node_modules 資料夾中的本地副本。

To get around this problem you can use TypeScript path mapping to tell TypeScript that it should load some modules from a specific location. List all the peer dependencies that your library uses in the workspace TypeScript configuration file ./tsconfig.json, and point them at the local copy in the app's node_modules folder.

{ "compilerOptions": { // ... // paths are relative to `baseUrl` path. "paths": { "@angular/*": [ "./node_modules/@angular/*" ] } } }
      
      {
  "compilerOptions": {
    // ...
    // paths are relative to `baseUrl` path.
    "paths": {
      "@angular/*": [
        "./node_modules/@angular/*"
      ]
    }
  }
}
    

此對映可確保你的函式庫始終載入所需模組的本地副本。

This mapping ensures that your library always loads the local copies of the modules it needs.

在應用中使用自己的函式庫

Using your own library in apps

你不必把函式庫發佈到 npm 套件管理器上就可以在自己的應用中使用它,但必須先建構它。

You don't have to publish your library to the npm package manager in order to use it in your own apps, but you do have to build it first.

要想在應用中使用你自己的函式庫:

To use your own library in an app:

  • 建構該函式庫。在建構之前,無法使用函式庫。

    Build the library. You cannot use a library before it is built.

    ng build my-lib
          
          ng build my-lib
        
  • 在你的應用中,按名字從函式庫中匯入:

    In your apps, import from the library by name:

    import { myExport } from 'my-lib';
          
          import { myExport } from 'my-lib';
        

建構和重建你的函式庫

Building and rebuilding your library

如果你沒有把函式庫發佈為 npm 套件,然後把它從 npm 安裝到你的應用中,那麼建構步驟就是必要的。例如,如果你複製了 git 儲存庫並運行了 npm install,編輯器就會把 my-lib 的匯入顯示為缺失狀態(如果你還沒有建構過該函式庫)。

The build step is important if you haven't published your library as an npm package and then installed the package back into your app from npm. For instance, if you clone your git repository and run npm install, your editor will show the my-lib imports as missing if you haven't yet built your library.

當你在 Angular 應用中從某個函式庫匯入一些東西時,Angular 就會尋找函式庫名和磁碟上某個位置之間的對映關係。當你用 npm 套件安裝該函式庫時,它就對映到 node_modules 目錄下。當你自己建構函式庫時,它就會在 tsconfig 路徑中查詢這個對映。

When you import something from a library in an Angular app, Angular looks for a mapping between the library name and a location on disk. When you install a library package, the mapping is in the node_modules folder. When you build your own library, it has to find the mapping in your tsconfig paths.

用 Angular CLI 產生函式庫時,會自動把它的路徑新增到 tsconfig 檔案中。Angular CLI 使用 tsconfig 路徑告訴建構系統在哪裡尋找這個函式庫。

Generating a library with the Angular CLI automatically adds its path to the tsconfig file. The Angular CLI uses the tsconfig paths to tell the build system where to find the library.

如果你發現函式庫中的更改沒有反映到應用中,那麼你的應用很可能正在使用這個函式庫的舊版本。

If you find that changes to your library are not reflected in your app, your app is probably using an old build of the library.

每當你對它進行修改時,都可以重建你的函式庫,但這個額外的步驟需要時間。增量建構功能可以改善函式庫的開發體驗。每當檔案發生變化時,都會執行區域性建構,並修補一些檔案。

You can rebuild your library whenever you make changes to it, but this extra step takes time. Incremental builds functionality improves the library-development experience. Every time a file is changed a partial build is performed that emits the amended files.

增量建構可以作為開發環境中的後臺程序執行。要啟用這個特性,可以在建構命令中加入 --watch 標誌:

Incremental builds can be run as a background process in your dev environment. To take advantage of this feature add the --watch flag to the build command:

ng build my-lib --watch
      
      ng build my-lib --watch
    

CLI 的 build 命令為函式庫使用與應用程式不同的建構器,並呼叫不同的建構工具。

The CLI build command uses a different builder and invokes a different build tool for libraries than it does for applications.

  • 應用程式的建構體系(@angular-devkit/build-angular)基於 webpack,並被包含在所有新的 Angular CLI 專案中。

    The build system for apps, @angular-devkit/build-angular, is based on webpack, and is included in all new Angular CLI projects.

  • 函式庫的建構體系基於 ng-packagr。只有在使用 ng generate library my-lib 新增函式庫時,它才會新增到依賴項中。

    The build system for libraries is based on ng-packagr. It is only added to your dependencies when you add a library using ng generate library my-lib.

這兩種建構體系支援不同的東西,即使它們支援相同的東西,它們的執行方式也不同。 這意味著同一套 TypeScript 原始碼在產生函式庫時產生的 JavaScript 程式碼可能與產生應用時產生的 JavaScript 程式碼也不同。

The two build systems support different things, and even where they support the same things, they do those things differently. This means that the TypeScript source can result in different JavaScript code in a built library than it would in a built application.

因此,依賴於函式庫的應用應該只使用指向內建函式庫的 TypeScript 路徑對映。 TypeScript 的路徑對映不應該指向函式庫的 .ts 原始檔。

For this reason, an app that depends on a library should only use TypeScript path mappings that point to the built library. TypeScript path mappings should not point to the library source .ts files.