>  기사  >  웹 프론트엔드  >  프로젝트가 너무 크면 어떻게 해야 하나요? Angular 프로젝트를 합리적으로 분할하는 방법은 무엇입니까?

프로젝트가 너무 크면 어떻게 해야 하나요? Angular 프로젝트를 합리적으로 분할하는 방법은 무엇입니까?

青灯夜游
青灯夜游앞으로
2022-07-26 19:18:053228검색

Angular프로젝트가 너무 큰데 어떻게 합리적으로 분할할 수 있나요? 다음 글에서는 Angular 프로젝트를 합리적으로 분할하는 방법을 소개하겠습니다. 도움이 되셨으면 좋겠습니다!

프로젝트가 너무 크면 어떻게 해야 하나요? Angular 프로젝트를 합리적으로 분할하는 방법은 무엇입니까?

사람들이 Angular에 대해 비판하는 것 중 하나는 패키징 후 매우 크다는 것입니다. 조심하지 않으면 main.js도 실제로 비슷한 문제에 직면하게 됩니다. 크기가 크든, 데이터가 크든, 트래픽이 크든 상관없이 아이디어는 하나뿐입니다. 브라우저의 캐싱 메커니즘과 결합하여 프로젝트 액세스 속도를 최적화할 수 있습니다. [추천 관련 튜토리얼: "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 tutorial"]

이 글의 관련 코드는 https://github.com/Vibing/angular-webpack

아이디어 분할 h3>
  • 전체 프로젝트에는 강력한 종속성 라이브러리(Angular 프레임워크 자체), UI 구성 요소 라이브러리 및 타사 라이브러리, 비즈니스 코드가 포함됩니다. 🎜
  • 🎜사용자 행동 차원: 모든 사용자 액세스는 페이지당 하나의 경로를 기반으로 합니다. 🎜
🎜 위의 두 지점을 기준으로 분할할 수 있습니다. 첫 번째 요점, 강력한 종속 라이브러리와 거의 변경되지 않는 라이브러리는 @angular/common, @angular/core를 포함할 수 있는 vendor_library로 패키징됩니다. , @angular /forms, @angular/router 및 기타 유사한 패키지, UI 구성요소 라이브러리 또는 lodash와 같은 라이브러리는 패키징하는 것이 권장되지 않습니다. 함께, TreeShaking을 사용해야 하기 때문에, 사용하지 않는 코드를 패키징해야 합니다. 그렇지 않으면 크기만 커질 뿐입니다. 🎜🎜이제 강력한 종속성 패키지가 완성되었으니 포인트 2를 기반으로 비즈니스 코드를 패키지해 보겠습니다. 패키징에는 라우팅 기반 코드 분할을 사용합니다. 아이디어는 매우 간단합니다. 사용자가 어떤 페이지를 방문하든 해당 페이지에 해당하는 js가 다운로드됩니다. 방문하지 않은 페이지를 함께 패키지할 필요가 없습니다. 이렇게 하면 크기가 늘어날 뿐만 아니라 사용자도 늘어납니다. 경험치도 저하됩니다. 🎜

🎜사용자 정의 웹팩 구성🎜

🎜DLL을 사용하여 종속성이 강한 패키지를 공급업체로 가져오려면 Webpack 기능이 이미 Angular CLI에 내장되어 있지만 이러한 구성은 유용하지 않습니다. 우리에게는 블랙박스입니다. 🎜🎜Angular를 사용하면 웹팩 구성을 사용자 정의할 수 있습니다. 단계는 다음과 같습니다🎜
  • 🎜 @angular-builders/custom-webpack설치 > 및 @angular-devkit/build-angular🎜
  • 🎜웹팩 구성을 위한 새 webpack.extra.config.ts를 생성하세요🎜
  • 🎜Angular에서 .json 다음과 같이 수정하세요🎜
@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)
      );  
  }
}

🎜DLL 사용🎜

🎜 webpack 구성을 사용자 정의한 후 새 webpack.dll.js 파일을 생성하여 DLL 구성을 작성하세요. :🎜
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 {}
🎜그런 다음 dll을 webpack.extra.config.ts로 가져오기🎜
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 {}
🎜마지막으로 package.json에 dll을 패키징하는 명령을 추가합니다: "dll": "rm -rf dll && webpack --config webpack.dll.js" , npm run dll을 실행하면 프로젝트 루트에 패키지된 콘텐츠가 포함된 dll 폴더가 있게 됩니다: 🎜🎜🎜🎜 패키징이 완료되면 프로젝트에 추가해야 합니다. vendor.dll.js를 사용하여 angular.json에서 구성합니다. 🎜
import { NzTableModule } from 'ng-zorro-antd/table';

@NgModule({
  ...
  imports: [..., NzTableModule]
})
export class WelcomeModule {}
🎜패키징 후 다음을 수행할 수 있습니다. vendor_library.js가 도입되었는지 확인하세요. 들어오는: 🎜🎜🎜🎜DLL은 자주 업데이트되지 않는 종속성이 강한 패키지를 js 파일로 패키징하고 병합하는 데 사용됩니다. 일반적으로 다음과 같은 용도로 사용됩니다. Angular 프레임워크 자체를 패키징합니다. 사용자가 처음 방문하면 브라우저는 vendor_library.js를 다운로드하고 캐시합니다. 이후 방문할 때마다 브라우저는 비즈니스 코드의 js만 다운로드하고 캐시합니다. 다시 다운로드하지 않습니다. 프레임워크 관련 코드는 애플리케이션 로딩 속도와 사용자 경험을 크게 향상시킵니다. 🎜🎜ps: Vendor_library 뒤의 해시는 패키징 중에 내부 코드가 변경된 경우에만 변경되며, 그렇지 않으면 변경되지 않습니다. 🎜

🎜Route-level CodeSpliting🎜

🎜DLL은 프레임워크 부분에서 코드를 관리했습니다. Angular에서 라우팅 수준 페이지의 주문형 로딩을 구현하는 방법을 살펴보겠습니다. 🎜🎜잠시 React나 Vue에서 경로 수준 코드 분할을 어떻게 수행하나요? 아마도 다음과 같습니다: 🎜rrreee🎜여기의 홈은 해당 구성 요소를 가리키지만 이 방법은 Angular에서 사용할 수 없습니다. 코드는 모듈 단위로만 분할할 수 있습니다. 🎜rrreee🎜그런 다음 특정 모듈에서 라우팅 액세스를 사용합니다. 🎜rrreee🎜라우터의 구성 요소를 직접 import()할 수는 없지만 Angular는 🎜동적 구성 요소 가져오기🎜 기능을 제공합니다. 🎜rrreee🎜이런 방식으로 경로가 페이지에 액세스할 때 액세스된 페이지 콘텐츠가 🎜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.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제