原文:Creating Secondary Entry Points for your Angular Library
自从 Angular 库性能(从 Angular 7 开始)公布以来,当初开发 Angular 库比以往任何时候都容易。Angular 库自身装备了一个名为 ng-packagr 的社区驱动包,它简直是外围。在本文中,咱们将看看如何利用 ng-packagr 辅助入口点进一步拆分咱们的 Angular 库!
Why do we need secondary entry points?
咱们心愿领有辅助入口点的起因之一是使咱们可能拆分咱们的依赖项。让咱们看一个例子,其中一个模块有 peerDependencies,而另一个没有。
假如咱们有如下的 library 文件夹构造:
library 名称:my-awesome-lib
两个 module,awesome-plain 和 awesome-time
查看 awesome-plain Component 的实现:
import {Component} from '@angular/core';
@Component({
selector: 'awesome-plain',
template: `
<div>Hey I'm just a plain text with no dependencies!</div>
`
})
export class AwesomePlainComponent {}
以及 awesome-time Component 的实现:
import {Component} from '@angular/core';
import * as moment_ from 'moment';
const moment = moment_;
@Component({
selector: 'awesome-time',
template: `
<div>Hey, Awesome Time:</div>
<div>{{time}}</div>
`
})
export class AwesomeTimeComponent {
time: string;
constructor() {this.time = moment().format();}
}
其中 plain Component 没有任何依赖,而 time Component 依赖于 moment.
moment 依赖的定义在 library 的 package.json 里:
{
"name": "my-awesome-lib",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14",
"moment": "^2.26.0"
}
}
请留神,这些 peerDependencies 放在库 my-awesome-lib 的范畴内,而不是放在单个模块(库内的文件)上。
最初,以下是在 my-awesome-lib/src/public-api.ts 下如何导出 awesome-plain 和 awesome-time:
export * from './awesome-plain/awesome-plain.component';
export * from './awesome-plain/awesome-plain.module';
export * from './awesome-time/awesome-time.component';
export * from './awesome-time/awesome-time.module';
The problem: Client needs to install ALL peer dependencies from the library
下面这样设计的问题是什么?
假如咱们有一个 Angular 应用程序想要应用 my-awesome-lib。客户端应用程序(Angular 应用程序)须要做的第一件事是装置库:
npm i my-awesome-lib
装置后,客户端应用程序而后持续导入和应用,例如只有 awesome-plain 组件。这是客户端应用程序中的代码可能看起来很像:
// app.module.ts
import {AwesomePlainModule} from 'my-awesome-lib';
@NgModule({
...,
imports: [
...,
AwesomePlainModule,
],
bootstrap: [AppComponent]
})
export class AppModule {}
// app.component.html
<awesome-plain></awesome-plain>
然而,ng serve 命令行会导致如下谬误:
ERROR in ./node_modules/my-awesome-lib/fesm2015/my-awesome-lib.js Module not found: Error: Can’t resolve ‘moment’ in ‘/app-showcase-v8/node_modules/my-awesome-lib/fesm2015’
它说它找不到装置在客户端应用程序中的时刻。嗯,这就是产生的事件。只管客户端应用程序仅导入并应用 awesome-plain,但 Angular 编译器仍会要求装置 my-awesome-lib 中定义的所有 peerDependencies,这在本例中很重要。
如果客户端应用程序同时应用 awesome-plain 和 awesome-time,当前情况可能会很好。然而,设想一下,如果库变大并且有不止 2 个模块,假如有 10 个模块。让咱们再夸张一点;如果 10 个模块中有 5 个具备不同的 peerDependencies 会怎么?如果有一个客户端应用程序应用这个库并且只应用 1 个没有任何 peerDependencies 的模块,那么客户端应用程序依然须要装置所有 5 个 peerDependencies!当然,应该有比这更好的办法,对吧?
Enter the secondary entry points!
侥幸的是,很有可能优化以后的办法。到目前为止,库中应用的办法仅应用称为次要入口点的货色。这由 package.json 文件示意,该文件仅存在于 my-awesome-lib/package.json 下,其中定义了整个库的所有 peerDependencies。
通过二级入口点,咱们能够进一步将 peerDependencies 拆分到库级别之外;它使得在库内的文件夹或模块中定义 peerDependencies 变得可行。
例如,通过将 awesome-time 设置为二级入口点,咱们能够在子目录中创立另一个 package.json 文件,该文件蕴含仅实用于 awesome-time 模块的 peerDependencies。
因而,咱们不再在库级别定义 peerDependencies;咱们改为在
子目录中定义它们。
此外,辅助入口点使咱们可能像上面这样导入库:
// Primary entry points
import {AwesomePlainModule} from 'my-awesome-lib';
// Secondary entry points
import {AwesomeTimeModule} from 'my-awesome-lib/awesome-time';
这样,如果 Client App 只应用 AwesomePlainModule,编译器就不会再要求装置 moment 了!
Implement secondary entry points
心愿下面的解释能让大家对咱们为什么要应用辅助入口点有一个大抵的理解。
好消息是,实现二级入口点相当简单明了,因为 ng-packagr 将在幕后实现大部分工作!
咱们将应用 my-awesome-lib 作为以下施行指南的上下文。在这种状况下,咱们将设置 awesome-time 作为主要入口点,而 awesome-plain 将放弃原样(依然是次要入口点)。
(1) Place the folders for secondary entry points directly under the library folder.
依据 ng-packagr 文档,辅助入口点的文件夹布局示例之一是如下所示:
乏味的是,这是 @angular/common 包中应用的相似文件夹布局,它以 @angular/common 作为次要入口点,而 @angular/common/testing 作为主要入口点。
文件夹构造如下:
(2) Create additional package.json and public-api.ts files in secondary entry points folder.
要创立辅助入口点,咱们须要通知 ng-packagr 要查找哪个文件夹。这能够通过在 /my-awesome-lib/awesome-time 文件夹下创立另一个 package.json 和 public-api.ts 文件来实现,除了主入口点的文件。仅通过这样做,ng-packagr 将动静发现辅助入口点。
/my-awesome-lib/awesome-time/package.json 的内容能够是:
{
"ngPackage": {
"lib": {
"entryFile": "public-api.ts",
"umdModuleIds": {"moment": "moment"}
}
},
"peerDependencies": {"moment": "^2.26.0"}
}
请留神,到目前为止,咱们将 moment 作为 peerDependencies 搁置在这里。此外,“umdModuleIds”用于在构建库时从 ng-packagr 中删除正告。
以及 /my-awesome-lib/awesome-time/public-api.ts 的内容如下:
/*
* Public API Surface of my-awesome-lib/awesome-time
*/
export * from './awesome-time.component';
export * from './awesome-time.module';
(3) Remove secondary entry points peer dependencies from the main package.json and secondary entry point exported files from the main public-api.ts.
{
"name": "my-awesome-lib",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
}
}
moment 包已被删除,因为它已在 /my-awesome-lib/awesome-time/package.json 中定义。
此外,咱们将删除在主 my-awesome-lib/src/public-api.ts 中导出的 awesome-time 文件。该文件当初应该只导出 awesome-plain 文件,如下所示:
/*
* Public API Surface of my-awesome-lib
*/
export * from './awesome-plain/awesome-plain.component';
export * from './awesome-plain/awesome-plain.module';
(4) Build the library.
当初所有都已设置结束,咱们当初能够尝试通过执行命令 ng build my-awesome-lib 来构建库。如果正确实现,您应该在终端中看到以下内容:
另外,如果关上库构建文件夹 dist/my-awesome-library,文件夹内应该还有一些名为 my-awesome-lib-awesome-time..js 的文件,例如 dist/fesm2015 和 dist/bundles . 如果将它与没有辅助入口点的那个进行比拟,构建文件夹通常只蕴含 my-awesome-lib..js,这是仅针对库自身的构建。
(5) Install and import the library in the Client App.
最初一步是最终在 Angular 应用程序中应用它。因为咱们从次要入口点挪动了 awesome-time,因而导入门路会略有变动。要在客户端应用程序中应用新的库文件夹构造,它应该如下所示:
// Primary entry points
import {AwesomePlainModule} from 'my-awesome-lib';
// Secondary entry points
import {AwesomeTimeModule} from 'my-awesome-lib/awesome-time';
当初,如果客户端应用程序只应用 AwesomePlainModule,咱们应该能够在不装置 moment 的状况下运行应用程序(仅在 AwesomeTimeModule 中应用)。
请记住,施行辅助入口点可能会导致您的 Angular 库产生重大变动。起因是因为应用您的库的客户端应用程序必须更新导入门路。否则,他们的应用程序将中断,因为当初不再从“your-lib”导入辅助入口点文件。因而,此更改不向后兼容。
Should there be any primary entry points at all? Is it okay to only have secondary entry points for the library?
您可能想晓得,咱们甚至应该应用次要入口点吗?在我看来,只有主要入口点是能够的,次要是因为 @angular/material 只应用主要入口点。另一方面,对于逻辑上类似的性能或个性,个别也倡议应用次要入口点。以下是在 Angular Package Format 文档中编写的:
Angular Package Format 的个别规定是为最小的逻辑连贯代码集生成 FESM 文件。例如,Angular 包有一个用于 @angular/core 的 FESM。当开发人员应用来自 @angular/core 的 Component 符号时,他们很可能也会间接或间接应用诸如 Injectable、Directive、NgModule 等符号。因而,所有这些局部都应该捆绑到一个繁多的 FESM 中。对于大多数库状况,应该将单个逻辑组组合到一个 NgModule 中,并且所有这些文件应该捆绑在一起作为包中的单个 FESM 文件,代表 npm 包中的单个入口点。
此外,就我而言,教训法令是将某些模块作为辅助入口点,如果它们具备不同的 peerDependencies。这是为了避免客户端应用程序被迫手动装置所有依赖项,只管它们并未应用所有依赖项。
Conclusions
总而言之,主要入口点是一个奇妙的性能,它容许咱们进一步拆分 Angular 库,尤其是在解决 peerDependencies 时。它也很容易实现,因为 ng-packagr 将通过子目录的 package.json 动静发现辅助入口点。
益处之一是它会缩小客户端应用程序被迫装置所有依赖项的机会,即便该应用程序没有导入 / 应用依赖于已装置依赖项的任何库函数。
更多 Jerry 的原创文章,尽在:” 汪子熙 ”: