原文:为您的Angular库创建二级入口点自从Angular库功能发布(从Angular7开始)以来,开发Angular库现在比以往任何时候都容易。Angular库本身带有一个名为ng-packagr的社区驱动包,它几乎是核心。在本文中,我们将看看如何利用ng-packagr帮助程序入口点进一步拆分我们的Angular库!为什么我们需要二级入口点?我们希望拥有辅助入口点的原因之一是使我们能够拆分依赖项。让我们看一个示例,其中一个模块具有peerDependencies而另一个没有。假设我们有如下库文件夹结构:库名:my-awesome-lib两个模块,awesome-plain和awesome-time查看awesome-plain组件的实现:import{Component}from'@angular/core';@Component({selector:'awesome-plain',template:`
HeyI'mjustaplaintextwithnodependencies!
`})exportclassAwesomePlainComponent{}和awesome-time组件的实现:import{Component}from'@angular/core';import*asmoment_from'moment';constmoment=moment_;@Component({selector:'awesome-time',template:`
Hey,AwesomeTime:
{{time}}
`})exportclassAwesomeTimeComponent{time:string;constructor(){this.time=moment().format();}}其中plainComponent没有依赖,而timeComponent依赖于library的package.json中moment.moment依赖的定义:{"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';从'./awesome-plain/awesome-plain.module'导出*;export*from'./awesome-time/awesome-time.component';export*from'./awesome-time/awesome-time.module';问题:客户端需要从库中安装所有对等依赖项什么是上面的设计有问题吗?假设我们有一个想要使用my-awesome-lib的Angular应用程序。客户端应用程序(Angular应用程序)需要做的第一件事是安装库:npmimy-awesome-lib安装后,客户端应用程序然后继续导入和使用,例如仅awesome-plain组件。客户端应用程序中的代码可能如下所示://app.module.tsimport{AwesomePlainModule}from'my-awesome-lib';@NgModule({...,imports:[...,AwesomePlainModule,],bootstrap:[AppComponent]})exportclassAppModule{}//app.component.html
但是,ngserve命令行将导致以下错误:ERRORin./node_modules/my-awesome-lib/fesm2015/my-awesome-lib.jsModulenotfound:Error:Can'tresolve'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!当然应该有比这更好的方法,对吧?输入二级入口点!幸运的是,很有可能优化当前的方法。到目前为止,库中使用的方法仅使用称为主入口点的东西。这由package.json文件表示,该文件仅存在于my-awesome-lib/package.json下,其中定义了整个库的所有peerDependencies。使用辅助入口点,我们可以在库级别之外进一步拆分peerDependencies;它使得在库内的文件夹或模块中定义peerDependencies变得可行。例如,通过将awesome-time设置为辅助入口点,我们可以在包含仅适用于awesome-time模块的peerDependencies的子目录中创建另一个package.json文件。因此,我们不再在库级别定义peerDependencies;我们在子目录中定义它们。此外,辅助入口点允许我们像这样导入库://Primaryentrypointsimport{AwesomePlainModule}from'my-awesome-lib';//辅助入口点import{AwesomeTimeModule}from'my-awesome-lib/awesome-time';这样,如果ClientApp只使用了AwesomePlainModule,编译器就不会再要求安装moment了!实现二级入口点我希望上面的解释能让你对我们为什么使用二级入口点有一个大概的了解。好消息是,实现辅助入口点非常简单,因为ng-packagr将在幕后完成大部分工作!我们将使用my-awesome-lib作为下面实施指南的上下文。在这种情况下,我们将设置awesome-time作为次要入口点,而awesome-plain将保持原样(仍然是主要入口点)。(1)将二级入口点的文件夹直接放在库文件夹下。根据ng-packagr文档,二级入口点的文件夹布局示例之一如下:angular/common/testing作为次要入口点。文件夹结构如下:(2)在辅助入口点文件夹中创建额外的package.json和public-api.ts文件。要创建辅助入口点,我们需要告诉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的内容如下:/**PublicAPISurfaceofmy-awesome-lib/awesome-time*/export*from'./awesome-time.component';export*from'./awesome-time.module';(3)从主package.json和从主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文件,如下所示:./awesome-plain/awesome-plain.module';(4)建库。现在一切都已设置好,我们现在可以尝试通过执行命令ngbuildmy-awesome-lib来构建库。如果操作正确,您应该在终端中看到以下内容:此外,如果您打开库构建文件夹dist/my-awesome-library,文件夹中还应该有一些名为my-awesome-lib-awesome-time..js,例如dist/fesm2015和dist/bundles。如果将其与没有helper入口点的相比,build文件夹通常只包含my-awesome-lib..js,它仅用于库本身的构建。(5)在ClientApp中安装并导入库。最后一步是最终在Angular应用程序中使用它。由于我们从主入口点移动了awesome-time,因此导入路径会略有变化。要在客户端应用程序中使用新的库文件夹结构,它应该如下所示://Primaryentrypointsimport{AwesomePlainModule}from'my-awesome-lib';//二级入口点import{AwesomeTimeModule}from'my-awesome-lib'awesome-lib/awesome-time';现在,如果客户端应用程序仅使用AwesomePlainModule,我们应该能够在不安装moment(仅在AwesomeTimeModule中使用)的情况下运行该应用程序。请记住,实现辅助入口点可能会导致对Angular库的重大更改。原因是因为使用您的库的客户端应用程序必须更新导入路径。否则,他们的应用程序将中断,因为现在不再从“your-lib”导入辅助入口点文件。因此,此更改不向后兼容。是否应该有任何主要入口点?图书馆只有二级入口点可以吗?您可能想知道,我们是否应该使用主入口点?在我看来,只有二级入口是可以的,主要是因为@angular/material只使用二级入口。另一方面,对于逻辑上相似的功能或特性,通常也建议使用主入口点。AngularPackageFormat文档中写了以下内容:AngularPackageFormat的一般规则是为最小的逻辑连接代码集生成FESM文件。例如,Angular包有一个用于@angular/core的FESM。开发者在使用来自@angular/core的Component符号时,很可能也会直接或间接地使用Injectable、Directive、NgModule等符号。因此,所有这些部分都应该捆绑到一个FESM中。对于大多数库情况,应该将单个逻辑组组合成一个NgModule,并且所有这些文件都应该作为包中的单个FESM文件捆绑在一起,代表npm包中的单个入口点。此外,就我而言,经验法则是将某些模块作为辅助入口点,如果它们具有不同的peerDependencies。这是为了防止客户端应用程序被迫手动安装所有依赖项,即使它们并未使用所有依赖项。结论总而言之,辅助入口点是一个很好的特性,它允许我们进一步拆分Angular库,尤其是在处理peerDependencies时。它也很容易实现,因为ng-packagr将通过子目录的package.json动态发现辅助入口点。好处之一是它减少了客户端应用程序被迫安装所有依赖项的机会,即使应用程序不导入/使用依赖于已安装依赖项的任何库函数。更多Jerry原创文章在这里:《王子熙》: