乐趣区

关于javascript: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 为咱们的应用程序提供了更好的性能,并缩小了创立可注入服务所需的样板代码量。

退出移动版