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

简介: 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 为我们的应用程序提供了更好的性能,并减少了创建可注入服务所需的样板代码量。


相关文章
|
7天前
|
缓存 JavaScript 前端开发
Angular 应用打包和部署
Angular 应用打包和部署
31 1
|
20天前
|
应用服务中间件 Java Maven
掌控视图的力量!深入解析 JSF 视图管理,揭秘视图生命周期的秘密,让你的应用更高效!
【8月更文挑战第31天】JavaServer Faces (JSF) 是一种强大的框架,用于管理 Web 应用程序的视图。本文通过具体案例介绍 JSF 视图管理的基础知识,包括创建、管理和销毁视图的过程。首先,在 Eclipse 中创建一个新 JSF 项目,并配置 Maven 依赖。接着,在 `WEB-INF` 目录下配置 `web.xml` 文件,设置 JSF servlet。
31 0
|
20天前
|
Java 数据库 API
JSF与JPA的史诗级联盟:如何编织数据持久化的华丽织锦,重塑Web应用的荣耀
【8月更文挑战第31天】JavaServer Faces (JSF) 和 Java Persistence API (JPA) 分别是构建Java Web应用的用户界面组件框架和持久化标准。结合使用JSF与JPA,能够打造强大的数据驱动Web应用。首先,通过定义实体类(如`User`)和配置`persistence.xml`来设置JPA环境。然后,在JSF中利用Managed Bean(如`UserBean`)管理业务逻辑,通过`EntityManager`执行数据持久化操作。
32 0
|
20天前
|
Java Spring
🔥JSF 与 Spring 强强联手:打造高效、灵活的 Web 应用新标杆!💪 你还不知道吗?
【8月更文挑战第31天】JavaServer Faces(JSF)与 Spring 框架是常用的 Java Web 技术。本文介绍如何整合两者,发挥各自优势,构建高效灵活的 Web 应用。首先通过 `web.xml` 和 `ContextLoaderListener` 配置 Spring 上下文,在 `applicationContext.xml` 定义 Bean。接着使用 `@Autowired` 将 Spring 管理的 Bean 注入到 JSF 管理的 Bean 中。
31 0
|
20天前
|
开发者 Java 开发框架
JSF与EJB,打造企业级应用的神器!让你的Web应用更加稳定、高效!
【8月更文挑战第31天】在现代企业级应用开发中,JSF(JavaServer Faces)与EJB(Enterprise JavaBeans)是两大核心技术。JSF作为一款基于Java的Web应用框架,以其丰富的UI组件和表单处理功能著称;EJB则专注于提供分布式事务处理及远程调用等企业级服务。两者的结合为企业应用带来了高效便捷的开发模式。下文将通过一个简单的示例展示如何利用JSF进行用户信息的输入与保存,并借助EJB实现相关业务逻辑。尽管这一组合具有明显优势,但在实际应用中还需考虑其局限性并作出合理选择。
34 0
|
20天前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
34 0
|
20天前
|
容器 iOS开发 Linux
震惊!Uno Platform 响应式 UI 构建秘籍大公开!从布局容器到自适应设计,带你轻松打造跨平台完美界面
【8月更文挑战第31天】Uno Platform 是一款强大的跨平台应用开发框架,支持 Web、桌面(Windows、macOS、Linux)及移动(iOS、Android)等平台,仅需单一代码库。本文分享了四个构建响应式用户界面的最佳实践:利用布局容器(如 Grid)适配不同屏幕尺寸;采用自适应布局调整 UI;使用媒体查询定制样式;遵循响应式设计原则确保 UI 元素自适应调整。通过这些方法,开发者可以为用户提供一致且优秀的多设备体验。
32 0
|
20天前
|
开发者 Windows Android开发
跨平台开发新选择:揭秘Uno Platform与.NET MAUI优劣对比,帮你找到最适合的框架,告别选择困难症!
【8月更文挑战第31天】本文对比了备受关注的跨平台开发框架Uno Platform与.NET MAUI的特点、优势及适用场景。Uno Platform基于WebAssembly和WebGL技术,支持Windows、iOS、Android及Web平台,而.NET MAUI由微软推出,旨在统一多种UI框架,支持Windows、iOS和Android。两者均采用C#和XAML进行开发,但在性能、平台支持及社区生态方面存在差异。Uno Platform在Web应用方面表现出色,但性能略逊于原生应用;.NET MAUI则接近原生性能,但不支持Web平台。开发者应根据具体需求选择合适的框架。
39 0
|
20天前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
24 0
|
20天前
|
前端开发 JavaScript 开发者
JSF与WebSockets,打造实时通信魔法!让你的Web应用秒变聊天室,用户体验飞升!
【8月更文挑战第31天】在现代Web应用开发中,实时通信对于提升用户体验至关重要。本文探讨了如何在主要面向Web应用开发的JSF(JavaServer Faces)框架中引入WebSockets支持,以实现客户端与服务器之间的全双工通信。通过具体示例展示了在JSF应用中实现WebSockets的基本步骤:添加依赖、创建服务器端点以及在前端页面中嵌入JavaScript客户端代码。尽管这一过程中可能会遇到一些挑战,如复杂代码编写和额外配置需求,但借助AWS等云服务平台,开发者仍能高效地完成部署和管理工作,从而增强Web应用的实时通信能力。
29 0