Home  >  Article  >  Web Front-end  >  What should I do if the project is too big? How to split Angular projects reasonably?

What should I do if the project is too big? How to split Angular projects reasonably?

青灯夜游
青灯夜游forward
2022-07-26 19:18:053219browse

AngularThe project is too large, how to split it reasonably? The following article will introduce to you how to reasonably split Angular projects. I hope it will be helpful to you!

What should I do if the project is too big? How to split Angular projects reasonably?

One of the criticisms about Angular is that it is very large after being packaged. If you are not careful, main.js will become ridiculously large. In fact, when you encounter similar problems Whether the problem is large volume, large data, or large traffic, there is only one idea: splitting. Coupled with the browser's caching mechanism, the project access speed can be optimized. [Recommended related tutorials: "angular tutorial"]

The relevant code for this article is at: https://github.com/Vibing/angular-webpack

Split ideas

  • The entire project includes: strong dependency library (Angular framework itself), UI component library and third-party library, and business code part;

  • User behavior dimension: all user access is based on routing, one route is one page;

It can be split from the above two points, based on the first You can package strongly dependent libraries and libraries that rarely change into a vendor_library, which can contain @angular/common, @angular/core, @angular/forms, @angular/router and other similar packages, UI component libraries or libraries such as lodash are not recommended to be packaged together, because we need to use TreeShaking, There is no need to package unused code, otherwise it will only increase the size.

Now that the strong dependency package is done, let’s package the business code based on point 2. We use route-based code spliting for packaging. The idea is very simple. Whichever page the user visits, the js corresponding to the page is downloaded. There is no need to package the unvisited pages together. This will not only increase the size, but also increase the download time, and the user experience will also deteriorate. .

Custom webpack configuration

If we want to use DLL to import strongly dependent packages into a vendor, we must use the webpack function, which is already embedded in Angular CLI webpack, but these configurations are black boxes to us.

Angular allows us to customize webpack configuration, the steps are as follows

  • Installation@angular-builders/custom-webpackand@angular- devkit/build-angular

  • Create a new webpack.extra.config.ts for webpack configuration

  • In angular.json Make the following modifications

...
"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"
    }
  }
  ...

Use DLL

After you can customize the webpack configuration, create a new webpack.dll.js file to write the DLL configuration:

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",
    }),
  ],
};

Then introduce the dll in webpack.extra.config.ts

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;

Finally add a command to package the dll in package.json: "dll": "rm - rf dll && webpack --config webpack.dll.js", after executing npm run dll, there will be a dll folder at the root of the project, which contains the packaged content:

After the packaging is completed, we need to use vendor.dll.js in the project and configure it in angular.json:

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

After packaging, you can see that vendor_library.js has been introduced:

The purpose of DLL is to remove strong dependencies that will not be updated frequently. Packages are packaged and merged into a js file, which is generally used to package the Angular framework itself. When the user visits for the first time, the browser will download vendor_library.js and cache it. Every subsequent visit will be taken directly from the cache. The browser will only download the js of the business code and will not download the framework. The relevant code greatly improves the application loading speed and improves the user experience.

ps: The hash behind vendor_library will only be changed if the code inside is changed during packaging, otherwise it will not change.

Routing-level CodeSpliting

The DLL has managed the code in the framework. Now let’s see how to implement on-demand loading of routing-level pages in Angular.

Let’s take a break here. How to do routing level code splitting in React or Vue? It's probably like this:

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

The home here points to the corresponding component, but this method cannot be used in Angular. The code can only be split in units of modules:

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

Then Use routing in a specific module to access specific components:

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

{
  path:'',
  component: HomeComponent
}

Although you cannot import() the component directly in the router, Angular provides dynamic import of components Function:

@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)
      );  
  }
}

In this way, when routing to access a certain page, as long as the content of the page being accessed uses import() and dynamically imported with the component, wouldn't it be possible to achieve the effect of lazyLoad on the page? ?

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

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

The above is the detailed content of What should I do if the project is too big? How to split Angular projects reasonably?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete