Heim  >  Artikel  >  Web-Frontend  >  Was soll ich tun, wenn das Projekt zu groß ist? Wie teilt man Angular-Projekte sinnvoll auf?

Was soll ich tun, wenn das Projekt zu groß ist? Wie teilt man Angular-Projekte sinnvoll auf?

青灯夜游
青灯夜游nach vorne
2022-07-26 19:18:053230Durchsuche

WinkeligDas Projekt ist zu groß, wie kann man es sinnvoll aufteilen? Der folgende Artikel zeigt Ihnen, wie Sie Angular-Projekte sinnvoll aufteilen. Ich hoffe, er ist hilfreich für Sie!

Was soll ich tun, wenn das Projekt zu groß ist? Wie teilt man Angular-Projekte sinnvoll auf?

Eine Sache, die die Leute an Angular kritisieren, ist, dass es nach dem Packen sehr groß ist. Wenn Sie nicht aufpassen, wird main.js lächerlich groß sein. Unabhängig davon, ob es sich um eine große Größe oder eine große Datenmenge handelt oder ob der Datenverkehr groß ist, gibt es nur eine Idee: Aufteilen. In Verbindung mit dem Caching-Mechanismus des Browsers kann die Projektzugriffsgeschwindigkeit optimiert werden. [Empfohlene verwandte Tutorials: „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“]

Der relevante Code für diesen Artikel befindet sich unter: https://github.com/Vibing/angular-webpack

Split ideas h3>
  • Das gesamte Projekt umfasst: eine starke Abhängigkeitsbibliothek (Angular Framework selbst), eine UI-Komponentenbibliothek und eine Drittanbieterbibliothek sowie Geschäftscode Teil; 🎜
  • 🎜Benutzerverhaltensdimension: Der gesamte Benutzerzugriff basiert auf einer Route pro Seite. 🎜
🎜Es kann aus den beiden oben genannten Punkten aufgeteilt werden Der erste Punkt: Stark abhängige Bibliotheken und Bibliotheken, die sich selten ändern, werden in eine vendor_library gepackt, die @angular/common, @angular/core enthalten kann >, @angular /forms, @angular/router und andere ähnliche Pakete, UI-Komponentenbibliotheken oder Bibliotheken wie lodash werden nicht empfohlen Zusammen packen, da wir TreeShaking verwenden müssen, nein Es ist notwendig, nicht verwendeten Code zu packen, da sonst nur die Größe zunimmt. 🎜🎜Da nun das starke Abhängigkeitspaket fertig ist, packen wir den Geschäftscode basierend auf Punkt 2. Für die Verpackung nutzen wir das routenbasierte Code-Splitting. Die Idee ist sehr einfach. Unabhängig davon, welche Seite der Benutzer besucht, werden die zu dieser Seite gehörenden JS nicht zusammengepackt. Dies erhöht nicht nur die Größe, sondern erhöht auch die Downloadzeit Auch die Erfahrung wird sich verschlechtern. 🎜

🎜Benutzerdefinierte Webpack-Konfiguration🎜

🎜Wenn wir DLL verwenden möchten, um stark abhängige Pakete in einen Anbieter zu importieren, müssen wir die Webpack-Funktion verwenden. Webpack ist bereits in Angular CLI eingebettet, aber diese Konfigurationen sind nicht nützlich Für uns ist es eine Blackbox. 🎜🎜Angular ermöglicht es uns, die Webpack-Konfiguration anzupassen. Die Schritte sind wie folgt: 🎜
  • 🎜Installieren Sie @angular-builders/custom-webpack und @angular-devkit/build-angular🎜
  • 🎜Erstellen Sie eine neue webpack.extra.config.ts für die Webpack-Konfiguration🎜
  • 🎜In Angular .json Nehmen Sie die folgenden Änderungen vor🎜
@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)
      );  
  }
}

🎜Verwenden Sie DLL🎜

🎜Nachdem Sie die Webpack-Konfiguration anpassen können, erstellen Sie eine neue webpack.dll.js-Datei, um die DLL-Konfiguration zu schreiben :🎜
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 {}
🎜Dann in Importieren Sie die DLL in 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 {}
🎜Fügen Sie schließlich einen Befehl hinzu, um die DLL in package.json zu packen: "dll": "rm -rf dll && webpack --config webpack.dll.js" , nach der Ausführung von npm run dll befindet sich im Stammverzeichnis des Projekts ein DLL-Ordner, der den gepackten Inhalt enthält: 🎜🎜import { NzTableModule } from 'ng-zorro-antd/table'; @NgModule({   ...   imports: [..., NzTableModule] }) export class WelcomeModule {}🎜Nach dem Packen können Sie Sehen Sie, dass vendor_library.js eingeführt wurde. Kommt herein: 🎜🎜🎜🎜DLL wird zum Packen und Zusammenführen stark abhängiger Pakete, die nicht häufig aktualisiert werden, in einer js-Datei verwendet. Dies wird im Allgemeinen verwendet Packen Sie das Angular-Framework selbst. Wenn der Benutzer zum ersten Mal zugreift, lädt der Browser vendor_library.js herunter und speichert sie bei jedem weiteren Besuch direkt aus dem Cache wird es nicht erneut herunterladen. Framework-bezogener Code verbessert die Ladegeschwindigkeit der Anwendung erheblich und verbessert die Benutzererfahrung. 🎜🎜ps: Der Hash hinter seller_library wird nur geändert, wenn der darin enthaltene Code während des Packens geändert wird, andernfalls ändert er sich nicht. 🎜

🎜CodeSpliting auf Routenebene🎜

🎜DLL hat den Code im Framework-Teil verwaltet. Sehen wir uns an, wie das Laden von Seiten auf Routing-Ebene bei Bedarf in Angular implementiert wird. 🎜🎜Wie führen Sie für einen Moment die Codeaufteilung auf Routenebene in React oder Vue durch? Es ist wahrscheinlich so: 🎜rrreee🎜Das Home hier zeigt auf die entsprechende Komponente, aber diese Methode kann nicht in Angular verwendet werden. Der Code kann nur in Moduleinheiten aufgeteilt werden: 🎜rrreee🎜Dann verwenden Sie den Routing-Zugriff in bestimmten Modulen. Spezifische Komponenten: 🎜rrreee🎜Obwohl Sie die Komponente im Router nicht direkt import() können, bietet Angular die Funktion des 🎜dynamischen Importierens von Komponenten🎜: 🎜rrreee🎜Auf diese Weise, wenn die Route auf eine Seite zugreift, solange Wenn der Seiteninhalt, auf den zugegriffen wird, 🎜import() verwenden soll, um die Komponente dynamisch zu importieren 🎜, wäre es dann nicht möglich, den Effekt von lazyLoad auf der Seite zu erzielen? 🎜

答案是可以的。但是这样会有一个大问题:被 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等角度来阐述。

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

Das obige ist der detaillierte Inhalt vonWas soll ich tun, wenn das Projekt zu groß ist? Wie teilt man Angular-Projekte sinnvoll auf?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen