Angular Ivy:详细介绍

2025-06-08

Angular Ivy:详细介绍

目录

Angular Ivy 是Angular 9 版本默认提供的渲染架构。Angular渲染架构并不是一次彻底的改造,Angular 2.0、Angular 4.0 以及现在的 Angular 9.0 都引入了新的编译器运行时引擎

目前,Angular 稳定版本为 8.2.14,Angular 9 为 RC5。

免责声明:
本文包含对 Angular 工作原理的初步探究,包括阅读部分源代码、调试一个简单应用程序以及了解编译器的工作原理。部分术语或定义可能存在错误。

幻灯片

这篇文章附带一个用markdown 编写的演示文稿reveal.js,通​​过 GitHub 呈现并可在 GitHub 上获取。

行话

  • 渲染架构:允许执行 Angular 应用程序的编译器和运行时引擎管道。
  • 运行时渲染函数集/指令集:运行时、模板、装饰器可理解的JavaScript函数集,被转换成一系列指令。
  • 虚拟 DOM 和增量 DOM:在 DOM 中创建和更新组件的技术。
  • 视图引擎: Angular 4 中引入的渲染架构,
  • angular.json是工作区或构建配置文件。
  • tsconfig.app.json是项目配置文件。
  • .ngfactory.js装饰器工厂文件的后缀,类装饰器之类的@Component被编译器翻译成外部文件。
  • 局部性:编译器应该只使用来自组件装饰器及其类的信息。
  • 全局编译:编译过程需要全局静态分析来发出应用程序代码。

渲染架构

什么是渲染架构?它是“编译器:运行时”对。Angular 框架由两个主要部分组成:

  • 编译器将用 Angular 声明语法编写的模板转换为富含变化检测的 JavaScript 指令;
  • 运行时执行编译器生成的应用程序代码。

目前,Angular 8 使用称为View Engine 的渲染架构:

  • 视图引擎已在 Angular 版本 4 中引入,并在版本 8 中仍在使用,但已发现一些限制
    • 不可摇树优化:应用Hello World程序和非常复杂的应用程序都由相同的完整运行时执行。例如,如果不使用国际化模块,它无论如何都是运行时的一部分,基本上运行时就无法摇树优化;
    • 没有增量编译: Angular 编译是全局的,它不仅涉及应用程序,还涉及库。
  • Ivy将从版本 9 开始成为新的默认渲染引擎,并解决视图引擎当前的问题:
    • 简化Angular 内部的工作方式;
    • 可摇树的应用Hello World程序不需要完整的 Angular 运行时,并且仅捆绑在 4.7 KB 中;
    • 增量编译是不可能的,因此编译比以往更快,--aot现在甚至可以在开发模式下使用(来自 Angular 团队的建议)。

常春藤是一个推动者。

Igor Minar - Angular 团队

增量DOM是新渲染引擎的基础。

增量 DOM 与虚拟 DOM

每个组件都会被编译成一系列指令。这些指令会创建 DOM 树,并在数据发生变化时进行更新。

维克托·萨夫金- Nrwl

每个编译的组件都有两组主要指令

  • 组件首次渲染时执行的视图创建指令;
  • 变更检测指令,当组件发生变化时更新 DOM。

变更检测本质上是一组在编译时添加的指令。由于开发人员只需关注Angular 模板声明中的变量绑定,因此他们的工作变得更加轻松。

增量 DOM 可以实现更好的捆绑包大小和内存占用,从而使应用程序能够在移动设备上表现良好。

虚拟 DOM

React 和 Vue 都基于虚拟 DOM的概念来创建组件并在检测到变化时重新渲染它。

渲染 DOM 是一项非常耗时的操作。当组件添加到 DOM 或发生更改时,必须进行重绘操作。虚拟 DOM 策略旨在减少真实 DOM 上的工作量,从而减少用户界面需要重绘的次数。

提示:
终端用户有时并未意识到用户界面渲染背后的复杂性。一个简单的点击就可能引发 HTTP 请求、组件的变更、其他组件的变更等等。对于用户来说,单个变更可能意味着一系列必须应用于 DOM 的复杂变更。

每次在 DOM 中添加、移除或更改新组件时,都会发生 DOM 操作。因此,它不是直接操作 DOM,而是操作一个称为虚拟 DOM 的 JSON 对象。添加新组件或移除现有组件时,会创建一个新的虚拟 DOM,添加或移除节点,并计算虚拟 DOM 之间的差异。一系列转换操作会应用于真实 DOM。

替代文本

React 文档建议使用 JSX( JavaScript 的语法扩展)来定义React 元素。JSX 不是模板语言。模板是一种丰富的 JavaScript 表达式,在运行时进行解释。您也可以使用纯 JavaScript 来代替 JSX。

虚拟 DOM 技术有一些缺点:

  • 每次发生变化(添加或删除节点)时都会创建一整棵树,因此内存占用非常重要;
  • 只要diff算法能够计算虚拟 DOM 之间的差异,就需要一个解释器。在编译时,我们并不知道渲染应用程序需要哪些功能,因此必须将整个过程交付给浏览器

增量 DOM

它是新渲染引擎的基础。每个组件模板都会被编译成创建和变更检测指令:一个用于将组件添加到 DOM,另一个用于就地更新 DOM 。

指令不由Angular 运行时(渲染引擎)解释,但指令就是渲染引擎

维克托·萨夫金- Nrwl

由于运行时不会解释模板组件的指令,而是解释组件引用的指令,因此很容易删除那些未被引用的指令。在编译时,可以将未使用的指令从 bundle 中排除。

渲染 DOM 所需的内存量与组件的大小成正比。

提示:
编译后的模板组件引用了保存实现的 Angular 运行时的一些指令。

启用 Angular Ivy

可以在具有最新 Angular 版本的现有项目中启用 Ivy,也可以直接使用 Ivy 搭建项目。

在现有项目中启用 Ivy

运行现有的Angular(8.1.x)项目:

$ ng update @angular/cli@next @angular/core@next
Enter fullscreen mode Exit fullscreen mode

Angular 核心和 CLI 都将在最新的候选版本中更新。需要注意的是工作区配置文件"aot": true中的以下内容angular.json

使用 Ivy 进行 AOT 编译速度更快,应该默认使用。

Angular 文档

然后在中添加角度编译器选项tsconfig.app.json

{
  "compilerOptions": { ... },
  "angularCompilerOptions": {
    "enableIvy": true
  }
}
Enter fullscreen mode Exit fullscreen mode

与 Ivy 合作的新项目

要使用 Ivy 运行启动新项目:

$ new my-app --enable-ivy
Enter fullscreen mode Exit fullscreen mode

禁用 Ivy

要禁用 Ivy:

  • angular.json集合中"aot": false
  • 删除tsconfig.app.json选项angularCompilerOptions或设置"enableIvy": false

Angular Ivy 编译

考虑以下Hello World Angular 组件:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div style="text-align:center">
      <h1>
        Welcome to {{ title }}!
      </h1>
    </div>
  `,
  styleUrls: []
})
export class AppComponent {
  @Input() title = 'Angular!';
}
Enter fullscreen mode Exit fullscreen mode

在启用 Ivy 的 Angular 8 中,它会被编译成以下代码:

class AppComponent {
  constructor() {
    this.title = 'Angular!';
  }
}
AppComponent.ngComponentDef = defineComponent({
        selectors: [['app-root']],
        factory: function() { return new AppComponent();}
    },
    template: function(flags, context) {
        if (flags & 1) {
            elementStart(0, "div", 0);
            elementStart(1, "h1");
            text(2);
            elementEnd();
            elementEnd();
        } if (flags & 2) {...}
    },
    directives: [...]
  });
Enter fullscreen mode Exit fullscreen mode

在 Angular 8 中使用 Ivy 时,Angular 装饰器会被编译成被装饰类中的静态字段@Component。因此,它变成了ngComponentDef静态字段。回到视图引擎,ngc编译器会为每个组件和模块生成.ngfactory单独的文件。使用 Ivy 时,编译器生成的代码会被移到组件类的静态字段中。

elementStart()elementEnd()等是组件引用的说明每个组件都是自己的工厂,框架不会解释组件。

所有在编译时未引用的指令都将从最终的应用程序包中删除。

替代文本

提示:
View Engine 运行时是一个单一的单体解释器,不支持 tree-shaking,必须完全迁移到浏览器。与之不同的是,Angular Ivy 运行时是一个指令集,它是一组渲染函数,类似于模板的汇编语言。

在 Angular 9 RC5 和 Ivy 中,编译稍有不同:

export class AppComponent {
    constructor() {
        this.title = 'Angular';
    }
}
AppComponent.ɵfac = function AppComponent_Factory(t) { return new (t || AppComponent)(); };
AppComponent.ɵcmp = i0.ɵɵdefineComponent({ type: AppComponent, selectors: [["app-root"]], 
  inputs: { title: "title" }, decls: 3, vars: 1, 
  consts: [[2, "text-align", "center"]], 
  template: function AppComponent_Template(rf, ctx) { 
    if (rf & 1) {
        i0.ɵɵelementStart(0, "div", 0);
        i0.ɵɵelementStart(1, "h1");
        i0.ɵɵtext(2);
        i0.ɵɵelementEnd();
        i0.ɵɵelementEnd();
    } if (rf & 2) {
        i0.ɵɵadvance(2);
        i0.ɵɵtextInterpolate1(" Welcome to ", ctx.title, "! ");
    } }, encapsulation: 2 });
Enter fullscreen mode Exit fullscreen mode

Angular Ivy 的功能

Angular Ivy 是一个推动者。它简化了 Angular 的内部工作方式和编译过程,解决了当前视图引擎的限制,并使 Angular 能够轻松扩展新功能。

新的 Ivy 工程由三个主要目标驱动:摇树、局部性和灵活性

摇树

树摇动是从包中删除死代码的操作,因此如果应用程序不引用某些运行时渲染函数,则可以从包中省略它们,从而使包变得更小。

死代码来自库,包括 Angular。Angular CLI 由以下工具提供支持: Webpack uglify 插件Webpack Terser 插件作为 tree-shaker,反过来,它会从Angular Build Optimizer 插件接收关于哪些代码被使用、哪些代码未被使用的信息。Angular 编译器不会发出这些指令,但该插件可以收集组件引用指令的信息,从而指示 丑化更简洁地说明在捆绑包中包含/排除的内容。

虽然@angular/core框架是可摇树的,但视图引擎运行时却不是,它不能分解成小块,这主要是由于静态Map<Component, ComponentFactory> 变量。

增量编译

Angular 8 编译管道ng build prod --aot由五个阶段组成,其中tscngc生成模板工厂ngc此外,它还会编译库。Ivy 支持增量编译,也就是说,库可以在 npm 上编译和部署。

位置

目前 Angular 依赖于全局编译。编译过程需要对整个应用程序进行全局静态分析,以组合不同的编译输出(应用程序、来自 monorepo 的库以及来自 npm 的库),然后再生成 bundle。此外,将 AOT 库组合到 JIT 应用程序中非常复杂。

提示:
编译器应该只使用组件装饰器及其类提供的信息,而不使用任何其他信息。这简化了整个编译过程,无需component.metadata.jsoncomponent.ngfactory.json编译流水线中进行复杂的管理。

局部性是一种规则。Ivy 编译引入了组件/指令公共 API 的概念 Angular 应用程序可以安全地引用组件和指令公共 API,而不再需要了解太多依赖关系,因为组件文件中添加了额外的信息.d.ts

示例:Ivy 库编译

将库添加到运行应用程序的 monorepo ng generate library mylib

使用 编译该库ng build mylib,将生成以下文件:

├── bundles
├── ...
├── lib
│   ├── mylib.component.d.ts
│   ├── mylib.module.d.ts
│   └── mylib.service.d.ts
├── mylib.d.ts
├── package.json
└── public-api.d.ts
Enter fullscreen mode Exit fullscreen mode

还要注意,由于 Ivy 激活,版本 9 中会显示以下新消息:

Building Angular Package
******************************************************************************
It is not recommended to publish Ivy libraries to NPM repositories.
Read more here: https://next.angular.io/guide/ivy#maintaining-library-compatibility
******************************************************************************
Enter fullscreen mode Exit fullscreen mode
生成的组件

这是 Angular CLI 生成的组件:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'lib-mylib',
  template: `
    <p>mylib works!</p>
  `,
  styles: []
})
export class MylibComponent implements OnInit {

  constructor() { }

  ngOnInit() { }
}
Enter fullscreen mode Exit fullscreen mode
编译库代码

mylib.metadata.json不再生成元数据文件,元数据现在是定义文件的一部分

组件的定义文件:

import { OnInit } from "@angular/core";
import * as i0 from "@angular/core";
export declare class MylibComponent implements OnInit {
  constructor();
  ngOnInit(): void;
  static ɵfac: i0.ɵɵFactoryDef<MylibComponent>;
  static ɵcmp: i0.ɵɵComponentDefWithMeta<MylibComponent,"lib-mylib",never,{},{},never>;
}
Enter fullscreen mode Exit fullscreen mode

模块的定义文件:

import * as i0 from "@angular/core";
import * as i1 from "./mylib.component";
export declare class MylibModule {
    static ɵmod: i0.ɵɵNgModuleDefWithMeta<MylibModule, [typeof i1.MylibComponent], never, [typeof i1.MylibComponent]>;
    static ɵinj: i0.ɵɵInjectorDef<MylibModule>;
}
Enter fullscreen mode Exit fullscreen mode

以及服务的定义文件:

import * as i0 from "@angular/core";
export declare class MylibService {
    constructor();
    static ɵfac: i0.ɵɵFactoryDef<MylibService>;
    static ɵprov: i0.ɵɵInjectableDef<MylibService>;
}
Enter fullscreen mode Exit fullscreen mode
向组件添加属性

向库组件添加一个输入字段:

@Component({
  selector: 'lib-mylib',
  template: `
    <p>Please input your phone</p>
    <input #phone placeholder="phone number" />
  `,
  styles: []
})
export class MylibComponent implements OnInit {

  @Input('phone-number') private phone: string;

  constructor() { }

  ngOnInit() {
  }
}
Enter fullscreen mode Exit fullscreen mode

该别名phone-number将被添加到输入属性,为公共 API 提供额外的元数据。编译器会生成以下定义文件:

import { OnInit } from '@angular/core';
import * as i0 from "@angular/core";
export declare class MylibComponent implements OnInit {
    private phone;
    constructor();
    ngOnInit(): void;
    static ɵfac: i0.ɵɵFactoryDef<MylibComponent>;
    static ɵcmp: i0.ɵɵComponentDefWithMeta<MylibComponent, "lib-mylib", never, { 'phone': "phone-number" }, {}, never>;
}
Enter fullscreen mode Exit fullscreen mode

一个装饰器,将类字段标记为输入属性,并提供配置元数据。输入属性会绑定到模板中的 DOM 属性。在变更检测期间,Angular 会自动用该 DOM 属性的值更新数据属性。

输入装饰器- Angular 文档

属性phone-number是公共 API 的名称部分,而phone是私有名称,属于实现细节。由于属性名称可能会发生变化,因此每次都必须编译代码,以便在属性名称不匹配时发出错误。因此,当前的 Angular 版本必须依赖于全局编译

Angular Ivy 依赖于公共 API,因此可以编译库代码并安全地运送到 npm。

浏览器属性

输入属性绑定到模板中的 DOM 属性。

基本上

当浏览器加载页面时,它会“读取”(或者说“解析”)HTML并从中生成DOM对象。对于元素节点,大多数标准HTML属性会自动成为DOM对象的属性。

属性和特性- javascript.info

Angular 编译器将装饰器和模板转换为 JavaScript 指令,不仅可以在 DOM 中创建元素,还可以创建运行时用来“保持”应用程序活动的额外内容属性和特性。

替代文本

灵活性

Angular Ivy 比 View Engine 更灵活,因为如果Angular 引入了新的功能,它会在指令集中实现新的指令。Ivy 更容易扩展和优化。

Angular Ivy 构建管道

Angular 应用程序的编译只是整个过程的一半,因为应用程序所依赖的库必须与新的运行时兼容。

ngcc(Angular 兼容编译器)是一种新的编译器,用于转换和编译库。与AngularViewEngine之前的渲染引擎兼容的库将被转换为 Ivy 指令,以便“库可以参与 Ivy 运行时”并实现完全兼容。

新的编译器已经实现,使得库与新格式兼容,而无需维护人员重写其中的重要部分,而且,并非所有应用程序都需要与 Ivy 兼容。

在 Angular 9 版本中,Ivy 仅适用于应用程序,ngcc用于转换现有库使其兼容 Ivy。随着时间的推移,应用程序将越来越兼容 Ivy,因此这些库ngcc将不再必要。在构建或安装过程中,可以动态地将库转换为兼容 Ivy 的库

从版本 9 到版本 11 的增量过渡ngcc仅在少数情况下是必要的:

角度版本 ngcc
9 Ivy 上的应用程序(选择退出)和 VE 兼容库
10 稳定 Ivy 指令集,库发布 Ivy 代码
11 ngcc备份过时的库或尚未更新的库

ngcc-validation 项目是 Angular 团队测试库兼容性的方式。

组件延迟加载功能

Angular 是一个推动者,它不仅能提升构建的性能,还能提升应用程序的性能。从 2.0 版本开始,Angular 就增加了组件延迟加载功能,但仅限于路由器级别。组件级别的延迟加载需要大量的样板代码和一些补丁才能实现。

使用 Angular Ivy 会简单得多。考虑以下示例:点击图片,延迟加载 bundle,并将组件添加到视图。延迟加载可以提高应用程序的速度。 理想情况下,它会:

@Component(...)
export class AppComponent{
  constructor(
      private viewContainer: ViewContainer,
      private cfr: ComponentFactoryResolver) {

    // lazy click handler
    async lazyload() {
      // use the dynamic import
      const {LazyComponent} = await import('./lazy/lazy.component');
      this.viewContainer.createComponent(LazyComponent);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

视图引擎必须通过ComponentFactoryResolver将惰性组件解析为工厂并加载它来传递:

this.viewContainer.createComponent(this.cfr.resolveComponentFactory(LazyComponent));
Enter fullscreen mode Exit fullscreen mode

捆绑包大小

为了评估打包文件大小的改进,Angular 团队使用了Hello World应用程序作为衡量指标使用 Angular Ivy 构建后,最终最小化的打包文件大小约为 4.5kB,使用 Closure Compiler 构建后约为 2.7kB。

然后可以更有效地捆绑Angular Elements,而且 Ivy 已为未来的捆绑器/优化器做好了准备。

调试

全局对象中添加了一个新的 API ng。在 ChromeDevTools 中,只需打开控制台并输入以下命令ng即可查看新选项:

替代文本

考虑从 Angular Material 库中获取一个<mat-drover></mat-drover>组件,可以直接从控制台对该组件进行操作(感谢 Juri Strumpflohner 在他的教程中提供的示例):

// grab the component instance of the DOM element stored in $0
let matDrawer = ng.getComponent($0);

// interact with the component's API
matDrawer.toggle();

// trigger change detection on the component
ng.markDirty(matDrawer);
Enter fullscreen mode Exit fullscreen mode

从“元素”选项卡中,只需选择调试操作的元素,$0就会在其附近出现一个,它可以用作控制台中元素的选择器/占位符。

替代文本

NgProbe可能不再受支持:

在 Ivy 中,我们不支持 NgProbe,因为我们有自己的一套功能更强大的测试实用程序。

我们不应该引入 NgProbe,因为它会阻止 DebugNode 和朋友正确地进行树状摇动。

平台浏览器- Angular 源代码

结论

Angular 团队做得非常出色,很高兴参加 Angular Connect 2019 并看到去年推出的新渲染架构的改进。

现在可以在默认启用编译的情况下进行开发,aot以避免开发和生产环境之间可能出现的不匹配。

另一个有趣的点是 Angular Elements。我认为得益于新的编译器和渲染引擎,这个项目现在确实可以加速了。目前,无法创建一个库项目并将其编译为 Web 组件,这真的是一个致命的功能。此外,生成的 Web 组件“内部包含太多 Angular 元素”,它们有点太大了,Ivy 应该减少包装 Angular 组件的框架体积。

真正令人印象深刻的是可以以非常简单的方式实现延迟加载,功能强大,但保持代码的可读性简单。

特别感谢

特别感谢

感谢同行评审,也感谢在启用 Ivy 的情况下发现 Angular 8 和 Angular 9 之间存在一些不准确之处。

参考

鏂囩珷鏉ユ簮锛�https://dev.to/eugeniolentini/angular-ivy-a-detailed-introduction-oj1
PREV
使用 Electron、React Native 和 Expo 制作桌面应用程序
NEXT
Angular 9 Ivy 编译器简介