搜索
首页web前端js教程项目过大怎么办?如何合理拆分Angular项目?

项目过大怎么办?如何合理拆分Angular项目?

Jul 26, 2022 pm 07:18 PM
javascriptangularangular.js

Angular项目过大,怎么合理拆分它?下面本篇文章给大家介绍一下合理拆分Angular项目的方法,希望对大家有所帮助!

项目过大怎么办?如何合理拆分Angular项目?

Angular 让人诟病的一点就是打包后体积很大,一不小心 main.js就大的离谱,其实遇到类似的问题,不管是体积大、数据大、还是流量大,就一个思路:拆分。再配合浏览器的缓存机制,能很好的优化项目访问速度。【相关教程推荐:《angular教程》】

本文相关代码在:https://github.com/Vibing/angular-webpack

拆分思路

  • 整个项目包括:强依赖库(Angular框架本身)、UI组件库及第三方库、业务代码部分;

  • 用户行为维度:用户的所有访问基于路由,一个路由一个页面;

从以上两点可以进行拆分,基于第 1 点可以把强依赖库和几乎不会变动的库打包成一个 vendor_library,里面可以包含@angular/common@angular/core@angular/forms@angular/router等类似的包,UI组件库或lodash这类库不建议一起打包,因为我们要运用 TreeShaking ,没必要把不用的代码也打包进来,否则只会增加体积。

强依赖包搞定了,下面基于第 2 点思路打包业务代码。我们使用基于路由的 code spliting来打包。思路很简单,用户访问哪个页面,就把该页面对应的 js 下载下来,没必要把没访问的页面一起打包,那样不仅造成体积增大,还会增加下载时间,用户体验也会随之变差。

自定义webpack配置

我们要想使用 DLL 将强依赖包打进一个 vendor 里就要使用 webpack 的功能,Angular CLI 中已经内嵌了 webpack,但这些配置对我们来说是黑盒子。

Angular 允许我们自定义 webpack 配置,步骤如下

  • 安装@angular-builders/custom-webpack@angular-devkit/build-angular

  • 新建一个 webpack.extra.config.ts 用于 webpack 配置

  • 在 angular.json 中做如下修改

...
"architect": {
  "build": {
    "builder": "@angular-builders/custom-webpack:browser",
    "options": {
      ...
      "customWebpackConfig": {
        // 引用要拓展的 webpack 配置
        "path": "./webpack.extra.config.ts",
        // 是否替换重复插件
        "replaceDuplicatePlugins": true
      }
    }
  },
  "serve": {
    "builder": "@angular-builders/custom-webpack:dev-server",
    "options": {
      "browserTarget": "angular-webpack:build"
    }
  }
  ...

使用DLL

可以自定义 webpack 配置后,新建 webpack.dll.js 文件来写 DLL 的配置:

const path = require("path");
const webpack = require("webpack");

module.exports = {
  mode: "production",
  entry: {
    vendor: [
      "@angular/platform-browser",
      "@angular/platform-browser-dynamic",
      "@angular/common",
      "@angular/core",
      "@angular/forms",
      "@angular/router"
    ],
  },
  output: {
    path: path.resolve(__dirname, "./dll"),
    filename: "[name].dll.js",
    library: "[name]_library",
  },

  plugins: [
    new webpack.DllPlugin({
      context: path.resolve(__dirname, "."),
      path: path.join(__dirname, "./dll", "[name]-manifest.json"),
      name: "[name]_library",
    }),
  ],
};

然后在 webpack.extra.config.ts 中进行 dll 引入

import * as path from 'path';
import * as webpack from 'webpack';

export default {
  plugins: [
    new webpack.DllReferencePlugin({
      manifest: require('./dll/vendor-manifest.json'),
      context: path.resolve(__dirname, '.'),
    })
  ],
} as webpack.Configuration;

最后在 package.json 中添加一条打包 dll 的命令:"dll": "rm -rf dll && webpack --config webpack.dll.js",执行 npm run dll后在项目根部就会有 dll 的文件夹,里面就是打包的内容:

打包完成后,我们要在项目中使用 vendor.dll.js,在 angular.json 中进行配置:

"architect": {
  ...
  "build": {
    ...
    "options": {
      ...
       "scripts": [
         {
            "input": "./dll/vendor.dll.js",
            "inject": true,
            "bundleName": "vendor_library"
         }
       ]
    }
  }
}

打包后可以看到讲 vendor_library.js 已经引入进来了:

DLL 的用处是将不会频繁更新的强依赖包打包合并为一个 js 文件,一般用于打包 Angular 框架本身的东西。用户第一次访问时浏览器会下载 vendor_library.js并会将其缓存,以后每次访问直接从缓存里拿,浏览器只会下载业务代码的 js 而不会再下载框架相关的代码,大大提升应用加载速度,提升用户体验。

ps: vendor_library 后面的 hash 只有打包时里面代码有变动才会重新改变 hash,否则不会变。

路由级CodeSpliting

DLL 把框架部分的代码管理好了,下面我们看看如何在 Angular 中实现路由级别的页面按需加载。

这里打个岔,在 React 或 Vue 中,是如何做路由级别代码拆分的?大概是这样:

{
  path:'/home',
  component: () => import('./home')
}

这里的 home 指向的是对应的 component,但在 Angular 中无法使用这种方式,只能以 module 为单位进行代码拆分:

{
  path:'/home',
  loadChild: ()=> import('./home.module').then(m => m.HomeModule)
}

然后在具体的模块中使用路由访问具体的组件:

import { HomeComponent } from './home.component'

{
  path:'',
  component: HomeComponent
}

虽然不能直接在 router 中 import() 组件,但 Angular 提供了组件动态导入的功能:

@Component({
  selector: 'app-home',
  template: ``,
})
export class HomeContainerComponent implements OnInit {
  constructor(
      private vcref: ViewContainerRef,
      private cfr: ComponentFactoryResolver
  ){}
  
  ngOnInit(){
    this.loadGreetComponent()
  }

  async loadGreetComponent(){
      this.vcref.clear();
      // 使用 import() 懒加载组件
      const { HomeComponent } = await import('./home.component');
      let createdComponent = this.vcref.createComponent(
        this.cfr.resolveComponentFactory(HomeComponent)
      );  
  }
}

这样在路由访问某个页面时,只要让被访问的页面内容使用 import() 配合组件动态导入,不就能达到页面 lazyLoad 的效果了吗?

答案是可以的。但是这样会有一个大问题:被 lazyLoad 的组件中,其内容仅仅是当前组件的代码,并不包含引用的其他模块中组件的代码。

原因是 Angular 应用由多个模块组成,每个模块中需要的功能可能来自其他模块,比如 A 模块里要用到 table 组件,而 table 需取自于 ng-zorro-antd/table 模块。打包时 Angular 不像 React 或 Vue 可以把当前组件和用到的其他包一起打包,以 React 为例:在 A 组件引入 table 组件,打包时 table 代码会打包到 A 组件中。而 Angular 中,在 A 组件中使用 table 组件时,并且使用 imprt() 对 A 组件进行动态加载,打包出来的 A 组件并不包含 table 的代码, 而是会把 table 代码打包到当前模块中去,如果一个模块中包含多个页面,这么多页面用了不少UI组件,那么打包出来的模块肯定会很大。

那么就没有别的方法了吗?答案是有的,那就是把每个页面拆成一个 module,每个页面所用到的其他模块或组件由当前页面对应的模块所承担。

上图中 dashboard 作为一个模块,其下有两个页面,分别是 monitorwelcome

dashboard.module.ts:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'welcome',
    loadChildren: () => import('./welcome/welcome.module').then((m) => m.WelcomeModule),
  },
  {
    path: 'monitor',
    loadChildren: () => import('./monitor/monitor.module').then((m) => m.MonitorModule),
  },
];

@NgModule({
  imports: [CommonModule, RouterModule.forChild(routes)],
  exports: [RouterModule],
  declarations: [],
})
export class DashboardModule {}

在模块中使用路由 loadChildren 来 lazyLoad 两个页面模块,现在再看看 WelcomeModule:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WelcomeComponent } from './welcome.component';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', component: WelcomeComponent }
];
@NgModule({
  declarations: [WelcomeComponent],
  imports: [RouterModule.forChild(routes), CommonModule]
})
export class WelcomeModule {}

就是这么简单,就把页面级的 lazyLoad 完成了。当需要使用外部组件时,比如 table 组件,只要在 imports 引入即可:

import { NzTableModule } from 'ng-zorro-antd/table';

@NgModule({
  ...
  imports: [..., NzTableModule]
})
export class WelcomeModule {}

题外话:我更喜欢 React 的拆分方式,举个例子:React 中使用 table 组件,table 组件本身代码量比较大,如果很多页面都使用 table,那么每个页面都会有 table 代码,造成不必要的浪费。所以可以配合 import()table组件单拉出来,打包时 table 作为单独的 js 被浏览器下载并提供给需要的页面使用,所有页面共享这一份 js即可。但 Angular 做不到,它无法在模块的 imports 中使用 import()的模块 。

后续

以上都是对项目代码做了比较合理的拆分,后续会对 Angular 性能上做合理的优化,主要从编译模式、变更检测、ngFor、Worker等角度来阐述。

更多编程相关知识,请访问:编程视频!!

以上是项目过大怎么办?如何合理拆分Angular项目?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:segmentfault。如有侵权,请联系admin@php.cn删除
JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript的角色:使网络交互和动态JavaScript的角色:使网络交互和动态Apr 24, 2025 am 12:12 AM

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C和JavaScript:连接解释C和JavaScript:连接解释Apr 23, 2025 am 12:07 AM

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具