Angular 应用里的摇树优化 - tree shaking

Tree Shakeable Providers and Services in Angular

Angular 最近推出了一项新功能,Tree Shakeable Providers。 Tree Shakeable Providers 是一种定义服务和其他东西的方式,以一种可以提高 Angular 应用程序性能的方式被 Angular 的依赖注入系统使用。

首先,在我们深入挖掘之前,让我们先定义一下摇树。摇树是构建过程中的一个步骤,它从代码库中删除未使用的代码。删除未使用的代码可以被认为是“摇树”,或者您可以想象一棵树的物理摇晃和剩余的枯叶从树上掉下来。通过使用摇树,我们可以确保我们的应用程序只包含我们的应用程序运行所需的代码。

例如,假设我们有一个实用程序库,其中包含函数 a()、b() 和 c()。在我们的应用程序中,我们导入并使用函数 a () 和 c () 但不使用 b ()。我们希望 b() 的代码不会被捆绑并部署给我们的用户。摇树是从我们发送到用户浏览器的已部署生产代码中删除函数 b() 的机制。

为什么过去版本的 Angular 中,服务已经不能被摇树优化?这其实又回到了我们如何在 Angular 的早期版本中注册 Service 的问题。让我们看一个示例,说明我们如何在以前的 Angular 版本中注册一个用于依赖注入的服务。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { SharedService } from './shared.service';

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [SharedService]
})
export class AppModule {}

如您所见,我们导入了服务并将其添加到我们的 Angular AppModule。 这会将服务注册到 Angular 的依赖注入系统。 每当组件请求使用此服务时,Angular 的 DI 将确保创建 Service 及其任何依赖项并将其传递给组件的构造函数。

此注册系统的问题在于,构建工具和编译器很难确定我们的应用程序中是否使用了此代码。

摇树系统删除代码的主要方式之一是查看我们定义的导入路径。 如果类或函数未导入,则不会包含在我们提供给用户的生产代码包中。如果它是导入的,则摇树器假定它正在应用程序中使用。在我们上面的示例中,我们在 AppModule 中导入和引用我们的服务,导致显式依赖项不能被摇树优化掉。

Angular Tree Shaking Providers

使用 Tree Shaking Providers (TSP),我们可以使用不同的机制来注册我们的服务。 使用这种新的 TSP 机制将提供摇树性能和依赖注入的好处。 我们有一个带有特定代码的演示应用程序来演示我们如何注册这些服务的不同性能特征。 让我们来看看新的 TSP 语法是什么样的。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class SharedService {
  constructor() {}
}

在@Injectable 装饰器中,我们有一个名为providedIn 的新属性。有了这个属性,我们可以告诉 Angular 将我们的服务注册到哪个模块,而不必导入模块并将其注册到 NgModule 的提供者。也就是说,不需要像旧版本的 Angular 那样,在 AppModule 里显式 import 服务,并添加到 NgModule 的 providers 数组里。

默认情况下,此语法将其注册到根注入器,这将使我们的服务成为应用程序范围的单例。 对于大多数用例,根提供程序是合理的默认值。 如果您仍然需要控制服务实例的数量,Angular 模块和组件上的常规提供程序 API 仍然可用。

使用这个新 API,您可以看到,由于我们不必将服务导入 NgModule 进行注册,因此我们没有明确依赖。 因为没有 import 语句,构建工具可以确保该服务仅在组件使用时才捆绑在我们的应用程序中。 让我们看一个示例应用程序。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { Shared3Service } from './shared3.service';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot([
      { path: '', component: HelloComponent },
      {
        path: 'feature-1',
        loadChildren: () => import('./feature-1/feature-1.module').then(m => m.Feature1Module)
      },
      {
        path: 'feature-2',
        loadChildren: () => import('./feature-2/feature-2.module').then(m => m.Feature2Module) }
      }
    ])
  ],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent],
  providers: [Shared3Service]
})
export class AppModule {}

在这个示例应用程序中,我们有三个组件; 两个是延迟加载的模块,而一个是我们的着陆主页组件。 我们还将在应用程序中使用三种不同的服务。 让我们从第一个服务开始,看看它是如何使用的。

import { Injectable } from '@angular/core';

console.log('SharedService bundled because two components use it');

@Injectable({
  providedIn: 'root'
})
export class SharedService {
  constructor() {
    console.log('SharedService instantiated');
  }
}

我们的第一个服务使用 tree shakable providers API。 我们在每个延迟加载的功能模块中导入此服务两次,如下所示。

import { Component, OnInit } from '@angular/core';
import { SharedService } from './../shared.service';

@Component({
  selector: 'app-feature-1',
  templateUrl: './feature-1.component.html',
  styleUrls: ['./feature-1.component.css']
})
export class Feature1Component implements OnInit {
  constructor(private sharedService: SharedService) {}
  ngOnInit() {}
}

因为我们的两个组件中都使用了服务 1,所以代码被加载并捆绑到我们的应用程序中。 如果我们检查控制台,我们会看到以下消息:

SharedService bundled because two components use it

第二个服务:

import { Injectable } from '@angular/core';

console.log('Shared2Service is not bundled because it not used');

@Injectable({
  providedIn: 'root'
})
export class Shared2Service {
  constructor() {}
}

如果我们检查控制台,我们不会看到日志消息。 这是因为我们的功能模块或组件中均未使用此服务。 由于它没有被使用,所以没有捆绑和加载代码。

最后,我们类似于前两个服务的第三个服务如下所示:

import { Injectable } from '@angular/core';

console.log('Shared3Service bundled even though not used');

@Injectable()
export class Shared3Service {
  constructor() {}
}

console 信息:

Shared3Service bundled even though not used

因为 Shared3Service 是使用较旧的提供程序 API 注册的,所以由于需要注册的 import 语句,它会创建显式依赖项。即使没有组件使用它,import 语句也会导致构建系统包含并加载此代码。

在这三个服务之间,我们可以看到摇树系统如何在我们的应用程序中包含或删除代码的特征。使用 TSP API,我们的服务仍然是单例的,即使对于我们示例中的延迟加载模块中使用的服务。如果我们加载示例应用程序,我们会注意到,如果我们在功能一和功能二之间路由,则 SharedService 中的控制台日志只会被调用一次。一旦请求模块,Angular 将实例化并确保该实例在应用程序的剩余生命周期中使用。

Angular Tree Shakeable Providers 为我们的应用程序提供了更好的性能,并减少了创建可注入服务所需的样板代码量。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容