blog icon indicating copy to clipboard operation
blog copied to clipboard

构建企业级的Angular项目结构

Open jiayisheji opened this issue 5 years ago • 0 comments

建立现代前端项目的一个重要任务是为每个不同的编程体验定义一个可伸缩的、长期的、不受未来影响的文件夹结构和命名准则。

尽管有人认为这是一个简单而次要的方面,但它往往隐藏着比看起来复杂的多问题。即使大多数时候并没有完美的解决方案,我们也可以探索一些行业最佳实践,以及我认为最有意义的一些东西。

自从Angular 4发布以来,我一直使用Angular在企业中开发,大大小小项目开发10余个,一点一滴摸索和实践,不断优化调整项目结构。最终参考 风格指南,绘制一张比较满意的文件夹结构图,一直在项目中实践运用。

image

在本文中,我将介绍:

  • 在文件夹中分配我们的AngularTypescript实体
  • monorepos vs libraries
  • 状态管理作为服务模块的集合

Angular 实体

在建立新代码库时,我经常做的第一件事是思考和定义构成我的项目的编程实体。作为Angular的开发者,我们已经非常了解其中的一些了:

  • modules 模块
  • components 组件
  • directives 指令
  • services 服务
  • pipes 管道
  • guards 守卫

正如框架的文档所建议的,每次我们创建这些实体时,我们都会在文件名后面加上实体的名称。命名规范

因此,如果我们创建一个类名为filterPipe的管道,我们将把它的文件命名为filter.pipe。如果我们有一个叫ButtonComponent的组件,我们想要它的文件button.component.tsbutton.component.htmlbutton.component.scss

如果不首先讨论Angular模块,我们就不能讨论Angular项目的结构。在Angular里主要靠模块来管理维护依赖关系。

在Angular环境中,模块是一种对相关组件、管道和服务进行分组的方式。这个模块集被分组来组成应用程序,是的,就像它是乐高积木一样。模块可以隐藏或导出组件(管道、服务等)。导出的组件可以被其他模块使用,被模块隐藏的组件只能被自己使用。

在Angular中,这种模块化称为NgModule。 每个应用程序均由至少一个NgModule类组成,该类是应用程序的根模块。 根模块默认情况下称为AppModule

由于Angular的应用程序是由导入其他的模块组成的,它们自然会成为构成Angular项目的根文件夹。每个模块将包含所有其他Angular实体,这些实体都包含在它们自己的文件夹中。

假设我们正在构建一个电子商务应用程序,并创建了一个购物车功能模块,它的结构如下所示:

1_ehG_arBxpW0L2_MfzLC11w

正如您可能注意到的,我倾向于区分容器(智能)(containers\smart)和组件(愚蠢)(components \dumb),所以我将它们放在不同的文件夹中,但这并不是我所提倡的

但是,如果某个东西需要在其他地方重用怎么办?

在本例中,我们创建了一个共享模块SharedModule,它将托管所有共享实体,这些实体将被提供给项目的每个模块。

1_4yJiLhCEV4RNKN_7dw5PBA

SharedModule通常由一些实体组成,这些实体在一个项目中不同的模块之间共享,但在项目外部通常不需要它们。当我们遇到可以跨不同团队和项目重用的服务或组件时,并且这些服务或组件在理想情况下不会经常更改,那么我们可能需要构建一个Angular库。

在Angular里面有几个比较重要模块归纳:

  • 根模块:作为入口启动模块,一个项目至少有一个根模块AppModule
  • 特性模块:特性模块应该是Angular核心,我们上面说的Angular实体,其实就是特性模块,特性模块扮演重要角色,特性模块的分类不同功能也不同。
  • 核心模块:核心模块包含的代码将用于实例化应用程序并加载一些核心功能(只实例化一次模块比如:HttpClientModule等模块、单例服务、单实例组件),核心模块还可以用于导出根模块中需要的任何第三方模块,这个想法主要是让根模块尽可能的精简。
  • 共享模块:共享模块同样包含了应用程序和功能模块之间使用的代码。但是不同之处在于,需要根据将这个共享模块导入到特性模块中。您不需要将共享模块导入主根模块或核心模块。共享模块应该包含通用的组件/管道/指令,并且还导出常用的Angular模块(例如@ ngular / common的* ngIf指令的CommonModule),更应该在共享模块中具有“哑组件(dumb components)”。

Angular 典型的模块结构

image

以前版本会有一些篇幅去强调核心模块重要性,因为angular单例服务原因,核心模块主要做全局服务申明,angular6以后版本注册服务使用providedIn更方便提供全局服务。核心模块依旧是一个很好实践,把管理初始化应用的工作交给核心模块吧,减轻根模块的工作。

Typescript 实体

如果你在Angular中使用Typescript(ps:早期Angular文档可以使用Typescript、Javascript、Dart,现在只剩Typescript,主推Typescript),我想你也会这样做,你也必须考虑到Typescript自身强大的实体,我们可以利用它们来构建一个结构化的、写得很好的代码库。

这里有一个Typescript实体的列表,你将在你的项目中使用最多:

  • classes 类
  • enums 枚举
  • interfaces 接口
  • types 类型

我建议为每个后端实体创建一个匹配的Typescript文件,它包括:

  • enum 枚举
  • dto(Data Transfer Object) 用于请求和响应接口
  • data classes 数据类

小技巧:Typescript里classes也是可以直接作为类型,还可以直接new,如果你这个类不new,尽量不要用了,占地方。

我喜欢将这些实体放到一个特性模块中,当然它们也可以有自己的文件夹,可以其称为core,但这在很大程度上取决于你和你的团队。

有时,我们将针对公司内多个团队共享的微服务进行开发,或者多个特性模块需要共享实体。在类似的情况下,我认为构建一个Angular库来托管匹配的类、接口和枚举是有意义的,而不是在本地开发模块。

Libraries, Monorepos and Microfrontends

当您使用高度可重用的服务或组件(可以分为服务模块和窗口小部件模块)时,您可能希望将这些模块构建为Angular库,可以在它们自己的存储库中创建,也可以在更大的monorepo中创建。

多亏了强大的Angular CLI,我们可以用这个简单的命令轻松生成Angular库,这些库将构建在一个名为projects的文件夹中

ng generate library my-lib

有关Angular库的完整描述,请参阅Angular.cn的官方文档

与本地模块相比,使用库具有一些优势:

  • 我们在考虑和构建这些模块时会考虑可重用性
  • 我们可以很容易地与其他团队/项目发布和共享这些库

当然,也有一些缺陷:

  • 你需要将你的库链接到你的项目中,并为每次更改重新构建它
  • 如果是通过NPM发布并在项目外部构建的,则需要保持项目与库的最新版本同步

例如:假设ButtonComponent使用所有团队都使用的按钮UI库,我们可能希望共享我们的抽象,以避免许多库实际上在做通常的基础工作。

因此,我们创建了一个称为按钮UI库,并将其作为@ui-kit/button发布到NPM。

我们在Github上面看到的很多优秀开源Angular资源库,大部分都是使用这种方式完成的。

但是,monoreposmicrofrontends呢?

这可能需要较长的文章,但是如果不提及其他两种方式,我们就不能谈论企业级项目。

我们这里可以简单介绍一下它们:

未完待续...

jiayisheji avatar Oct 25 '19 08:10 jiayisheji