本文档描述了 npm 上当前可用的 Angular 框架包的结构和格式。 这种格式适用于分发 Angular 组件的包(如 Angular Material)以及在@angular 命名空间下发布的核心框架包,如@angular/core 和@angular/forms。
此处描述的格式使用独特的文件布局和元数据配置,使包能够在使用 Angular 的大多数常见场景下无缝工作,并使其与 Angular 团队和社区本身提供的工具兼容。 出于这个原因,也强烈鼓励第三方库开发人员遵循相同的结构。
格式的版本控制与 Angular 本身的版本控制一致,我们希望格式以向前兼容(forward-compatible)的方式发展,以支持 Angular 组件和工具生态系统的需求。
Package format 的目的
在当今的 JavaScript 环境中,开发人员将以多种不同的方式使用包。 例如,有些可能使用 SystemJS,有些可能使用 Webpack。 尽管如此,其他人可能会在 Node 或浏览器中使用包作为 UMD 包或通过全局变量访问。
Angular 分发包支持所有常用的开发工具和工作流,并强调优化,从而缩小应用程序有效负载大小或加快开发迭代周期(构建时间)。
Library File layout
库一般应该使用相同的布局,但库中存在与 Angular 框架不同的特性。
通常,库是在组件或功能级别拆分的。我们以 Angular 的 Material 项目为例。
Angular Material 发布了组件集,例如 Button(单个组件)、Tabs(一组协同工作的组件)等。共同点是将这些功能区域绑定在一起的 NgModule。 Button 有一个 NgModule,Tabs 有另一个,依此类推。
Angular Package Format 的一般规则是为最小的逻辑连接代码集生成 FESM 文件。例如,Angular 包有一个用于@angular/core 的 FESM。当开发人员使用来自@angular/core 的 Component 符号时,他们很可能也会直接或间接使用诸如 Injectable、Directive、NgModule 等符号。因此,所有这些部分都应该捆绑在一起形成一个 FESM。对于大多数库情况,应该将单个逻辑组组合到一个 NgModule 中,并且所有这些文件应该捆绑在一起作为包中的单个 FESM 文件,代表 npm 包中的单个入口点。
以下是 Angular Material 项目在这种格式下的外观示例:
再看 Spartacus core build 出来的输出:
Primary Entry point
包的主要入口点是模块 id 与包名称匹配的模块(例如,对于“@angular/core”包,从主要入口点导入的内容如下: import {Component, ...} from '@ 角度/核心')。
Secondary Entry point
除了主要入口点,包可以包含零个或多个次要入口点(例如@angular/common/http)。 这些包含我们不想与主入口点中的符号组合在一起的符号,原因有两个:
(1)用户通常认为它们与主要符号组不同,并且如果它们与主要符号组相关,那么他们就已经在那里了。
(2)次要组中的符号通常仅用于特定场景(例如,在编写和运行测试时)。 可能不会在主入口点中包含这些符号,因此我们减少了它们被意外错误使用的机会(例如,在@angular/core/testing 中使用的生产代码中使用测试模拟)。
辅助入口点导入的模块 ID 将模块加载器定向到辅助入口点名称的目录。 例如,“@angular/core/testing”解析为一个同名的目录,“@angular/core/testing”。 该目录包含一个 package.json 文件,该文件将加载器定向到它正在寻找的正确位置。 这允许我们在单个包中创建多个入口点。
Compilation and transpilation
为了生成所有必需的构建工件,我们强烈建议您使用 Angular 编译器 (ngc) 使用 tsconfig.json 中的以下设置编译您的代码:
{
"compilerOptions": {
...
"declaration": true,
"module": "es2015",
"target": "es2015"
},
"angularCompilerOptions": {
"strictMetadataEmit": true,
"skipTemplateCodegen": true,
"flatModuleOutFile": "my-ui-lib.js",
"flatModuleId": "my-ui-lib",
}
}
优化相关
Flattening of ES Modules
我们强烈建议您在将构建工件发布到 npm 之前,通过扁平化 ES 模块来优化构建工件。这显着减少了 Angular 应用程序的构建时间以及最终应用程序包的下载和解析时间。
Angular 编译器支持生成索引 ES 模块文件,然后可以使用这些文件使用 Rollup 等工具生成扁平化模块,从而生成我们称为扁平化 ES 模块或 FESM 的文件格式。
FESM 是一种文件格式,通过将所有可从入口点访问的 ES 模块扁平化为单个 ES 模块。它是通过跟踪包中的所有导入并将该代码复制到单个文件中而形成的,同时保留所有公共 ES 导出并删除所有私有导入。
缩写名称“FESM”(发音为“phesom”)后面可以有一个数字,例如“FESM5”或“FESM2015”。数字是指模块内 JavaScript 的语言级别。所以 FESM5 文件将是 ESM+ES5(导入/导出语句和 ES5 源代码)。
要生成扁平化的 ES 模块索引文件,请在 tsconfig.json 文件中使用以下配置选项:
{
"compilerOptions": {
...
"module": "es2015",
"target": "es2015",
...
},
"angularCompilerOptions": {
...
"flatModuleOutFile": "my-ui-lib.js",
"flatModuleId": "my-ui-lib"
}
}
一旦索引文件(例如 my-ui-lib.js)由 ngc 生成,捆绑器和优化器(如 Rollup)可用于生成扁平化的 ESM 文件。
Inlining of templates and stylesheets
组件库通常使用存储在单独文件中的样式表和 html 模板来实现。 虽然不是必需的,但我们建议组件作者通过将 styleUrls 和 templateUrl 分别替换为样式和模板元数据属性,将模板和样式表内联到他们的 FESM 文件以及 *.metadata.json 文件中。 这简化了应用程序开发人员对组件的使用。
从 APF v10 开始,我们建议添加 tslib 作为主要入口点的直接依赖项,这是因为 tslib 版本与用于编译库的 TypeScript 版本相关联。
一些术语
-
package: 发布到 NPM 并安装在一起的最小文件集,例如 @angular/core。 该包包含一个名为 package.json 的清单、编译后的源代码、TypeScript files、源映射、元数据等。该包通过 npm install @angular/core 安装。
-
Symbols:包含在模块中的类、函数、常量或变量,并可选择通过模块导出对外部世界可见。
-
module ID: 导入语句中使用的模块的标识符,例如 “@spartacus/core”。 ID 通常直接映射到文件系统上的路径,但由于各种模块解析策略,情况并非总是如此。
-
module format:模块语法规范,至少涵盖用于从文件导入和导出的语法。 常见的模块格式是 CommonJS(CJS,通常用于 Node.js 应用程序)或 ECMAScript 模块(ESM)。 模块格式仅表示单个模块的封装,而不表示用于构成模块内容的 JavaScript 语言特性。 因此,Angular 团队经常使用语言级别说明符作为模块格式的后缀,例如 ESM+ES5 指定模块采用 ESM 格式并包含下级到 ES5 的代码。 其他常用组合:ESM+ES2015、CJS+ES5、CJS+ES2015。
-
bundle: 由构建工具生成的单个 JS 文件形式的工件,例如 WebPack 或 Rollup,包含源自一个或多个模块的符号。 捆绑包是一种特定于浏览器的解决方法,可减少浏览器开始下载数百甚至数万个文件时可能造成的网络压力。 Node.js 通常不使用包。 常见的捆绑格式是 UMD 和 System.register。
-
language level: 代码的语言(ES5 或 ES2015)。 独立于模块格式。
-
entry point: 打算由用户导入的模块。 它由唯一的模块 ID 引用,并导出该模块 ID 引用的公共 API。 一个例子是@angular/core 或@angular/core/testing。 @angular/core 包中存在两个入口点,但它们导出不同的符号。 一个包可以有许多入口点。
-
deep import: 从不是入口点的模块中检索符号的过程。 这些模块 ID 通常被认为是私有 API,它们可以在项目的生命周期内或在创建给定包的包时更改。
-
top level import: 来自入口点的导入。 可用的顶级导入定义了公共 API,并在“@angular/name”模块中公开,例如 @angular/core 或 @angular/common。
-
tree shaking: 识别和删除应用程序未使用的代码的过程 - 也称为死代码消除。 这是使用 Rollup、Closure Compiler 或 Uglify 等工具在应用程序级进行操作。
更多Jerry的原创文章,尽在:"汪子熙":