首页 > 网页制作 >Angular 13+开发模式慢的原因及构建性能优化解析

Angular 13+开发模式慢的原因及构建性能优化解析

来源:互联网 2026-06-14 08:12:13

通常,Angular12+默认启用生产模式配置,导致开发构建缓慢且资源占用很高。解决方法是在development配置中关闭AOT编译和buildOptimizer等选项。此外,通过自定义webpack配置并启用DLL插件,能进一步大幅加快构建与热更新速度,有效提升开发效率。

1 Angular 13+ 开发模式太慢的原因与解决

最近将一个迭代了七年的 Angular 项目升级到 Angular 13 后,开发模式的构建速度变得非常慢,资源占用也异常高。平时仅用于会议的 Macbook Air(疫情期间被迫成为主力机),一跑构建就会风扇狂转、CPU 满载,热更新一次竟然超过一分钟,这种体验令人难以忍受。

经过排查,在 angular.json 的 schema(./node_modules/@angular/cli/lib/config/schema.json)中找到了线索,结合 Angular 12 的 release 文档才锁定问题根源:Angular 12 的一个关键改动,是将 aotbuildOptimizeroptimization 等参数的默认值从 false 改为 true

长期稳定更新的攒劲资源: >>>点此立即查看<<<

A number of browser and server builder options have had their default values changed. The aim of these changes is to reduce the configuration complexity and support the new "production builds by default" initiative.

简而言之,Angular 12 之后默认开启了生产模式,这对跨版本升级来说确实是一个陷阱。具体改动细节可以查看这个提交:656f8d7。

1.1 解决 Angular 12+ 开发模式慢的问题

解决方法很简单:在 development 配置中关闭这些生产模式相关的选项。示例配置如下:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "projects": {
    "front": {
      "architect": {
        "build": {
          "configurations": {
            "development": {
              "tsConfig": "./tsconfig.dev.json",
              "aot": false,
              "buildOptimizer": false,
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true,
              "vendorChunk": true,
              "namedChunks": true
            }
          }
        },
    }
  },
  "defaultProject": "front"
}

需要注意的是,aot 的开关在某些场景下会影响构建结果,具体取决于实际项目情况。

1.2 问题:开启 aot 后 pug 编译报错

该项目使用了 pug 编写 HTML。关闭 aot 时一切正常,但一开启就会报错。根据错误堆栈进行调试,发现编译结果变成了一个 esModule 对象。原因是使用了 raw-loader,它默认输出 esModule 模式,禁用这个选项即可解决。示例配置(自定义 webpack 的写法可以参考后面 dll 部分):

{
  test: /.pug$/,
  use: [
    {
      loader: 'raw-loader',
      options: {
        esModule: false,
      },
    },
    {
      loader: 'pug-html-loader',
      options: {
        doctype: 'html',
      },
    },
  ],
},

2 进一步优化:Angular 自定义 webpack 配置 dll 支持

该项目有自定义 webpack 配置的需求,使用了 @angular-builders/custom-webpack,但最初没有配置 dll。Angular 提供了 vendorChunk 参数,可以提取 package.json 中的公共依赖到独立 chunk,这确实能解决因热更新 bundle 过大而导致的更新慢问题,但内存占用仍然不低。实际对比测试表明,在有 webpack 5 缓存的情况下,dll 模式在构建编译速度和热更新速度上仍然略快一些。对于开发机器性能一般的情况,给开发模式配置 dll 还是有收益的。

2.1 Angular 支持自定义 webpack 配置

首先安装依赖:

npm i -D @angular-builders/custom-webpack

然后修改 angular.json 配置。参考格式如下:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "cli": {
    "analytics": false,
    "cache": {
      "path": "node_modules/.cache/ng"
    }
  },
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "front": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {
        "@schematics/angular:component": {
          "style": "less"
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./webpack.config.js"
            },
            "indexTransform": "scripts/index-html-transform.js",
            "outputHashing": "media",
            "deleteOutputPath": true,
            "watch": true,
            "sourceMap": false,
            "outputPath": "dist/dev",
            "index": "src/index.html",
            "main": "src/app-main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "./tsconfig.app.json",
            "baseHref": "./",
            "assets": [
              "src/assets/",
              {
                "glob": "**/*",
                "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
                "output": "/assets/"
              }
            ],
            "styles": [
              "node_modules/angular-tree-component/dist/angular-tree-component.css",
              "src/css/index.less"
            ],
            "scripts": []
          },
          "configurations": {
            "development": {
              "tsConfig": "./tsconfig.dev.json",
              "buildOptimizer": false,
              "optimization": false,
              "aot": false,
              "extractLicenses": false,
              "sourceMap": true,
              "vendorChunk": true,
              "namedChunks": true,
              "scripts": [
                {
                  "inject": true,
                  "input": "./dist/dll/dll.js",
                  "bundleName": "dll_library"
                }
              ]
            },
            "production": {
              "outputPath": "dist/prod",
              "baseHref": "./",
              "watch": false,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": {
                "scripts": true,
                "styles": {
                  "minify": true,
                  "inlineCritical": false
                },
                "fonts": true
              },
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": false,
              "vendorChunk": false,
              "buildOptimizer": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "options": {
            "browserTarget": "front:build",
            "liveReload": false,
            "open": false,
            "host": "0.0.0.0",
            "port": 3002,
            "servePath": "/",
            "publicHost": "localhost.gf.com.cn",
            "proxyConfig": "config/ngcli-proxy-config.js",
            "disableHostCheck": true
          },
          "configurations": {
            "production": {
              "browserTarget": "front:build:production"
            },
            "development": {
              "browserTarget": "front:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "test": {
          "builder": "@angular-builders/custom-webpack:karma",
          "options": {
            "customWebpackConfig": {
              "path": "./webpack.test.config.js"
            },
            "indexTransform": "scripts/index-html-transform.js",
            "main": "src/ngtest.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "./tsconfig.spec.json",
            "karmaConfig": "./karma.conf.js",
            "assets": [
              "src/assets/",
              {
                "glob": "**/*",
                "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
                "output": "/assets/"
              }
            ],
            "styles": [
              "node_modules/angular-tree-component/dist/angular-tree-component.css",
              "src/css/index.less"
            ],
            "scripts": []
          }
        }
      }
    }
  },
  "defaultProject": "front",
  "schematics": {
    "@schematics/angular:module": {
      "routing": true,
      "spec": false
    },
    "@schematics/angular:component": {
      "flat": false,
      "inlineStyle": true,
      "inlineTemplate": false
    }
  }
}

该示例包含许多自定义配置,关键要注意 webpack 相关部分,其他内容可根据项目实际情况对照参考。一些细节也可以参考相关的实践介绍。

2.2 为 Angular 配置 webpack dll 支持

新建 webpack.config.js 文件,内容参考如下:

const { existsSync } = require('node:fs');
const { resolve } = require('node:path');
const webpack = require('webpack');
// require('events').EventEmitter.defaultMaxListeners = 0;
/**
 * @param {import('webpack').Configuration} config
 * @param {import('@angular-builders/custom-webpack').CustomWebpackBrowserSchema} options
 * @param {import('@angular-builders/custom-webpack').TargetOptions} targetOptions
 */
module.exports = (config, options, targetOptions) => {
  if (!config.devServer) config.devServer = {};
  config.plugins.push(
    new webpack.DefinePlugin({ LZWME_DEV: config.mode === 'development' }),
  );
  const dllDir = resolve(__dirname, './dist/dll');
  if (
    existsSync(dllDir) &&
    config.mode === 'development' &&
    options.scripts?.some((d) => d.bundleName === 'dll_library')
  ) {
    console.log('use dll:', dllDir);
    config.plugins.unshift(
      new webpack.DllReferencePlugin({
        manifest: require(resolve(dllDir, 'dll-manifest.json')),
        context: __dirname,
      })
    );
  }
  config.module.rules = config.module.rules.filter((d) => {
    if (d.test instanceof RegExp) {
      // 使用 less,移除 sass/stylus loader
      return !(d.test.test('x.sass') || d.test.test('x.scss') || d.test.test('x.styl'));
    }
    return true;
  });
  config.module.rules.unshift(
    {
      test: /.pug$/,
      use: [
        {
          loader: 'raw-loader',
          options: {
            esModule: false,
          },
        },
        {
          loader: 'pug-html-loader',
          options: {
            doctype: 'html',
          },
        },
      ],
    },
    {
      test: /.html$/,
      loader: 'raw-loader',
      exclude: [helpers.root('src/index.html')],
    },
    {
      test: /.svg$/,
      loader: 'raw-loader',
    },
    {
      test: /.(t|les)s/,
      loader: require.resolve('@lzwme/strip-loader'),
      exclude: /node_modules/,
      options: {
        disabled: config.mode !== 'production',
      },
    }
  );
  // AngularWebpackPlugin,用于自定义 index.html 处理插件
  const awPlugin = config.plugins.find((p) => p.options?.hasOwnProperty('directTemplateLoading'));
  if (awPlugin) awPlugin.pluginOptions.directTemplateLoading = false;
  // 兼容上古遗传逻辑,禁用部分插件
  config.plugins = config.plugins.filter((plugin) => {
    const pluginName = plugin.constructor.name;
    if (/CircularDependency|CommonJsUsageWarnPlugin/.test(pluginName)) {
      console.log('[webpack][plugin] disabled: ', pluginName);
      return false;
    }
    return true;
  });
  // console.log('[webpack][config]', config.mode, config, options, targetOptions);
  return config;
};

再新建 webpack.dll.mjs 文件,用于 dll 构建:

import { join } from 'node:path';
import webpack from 'webpack';
const rootDir = process.cwd();
const isDev = process.argv.slice(2).includes('--dev') || process.env.NODE_ENV === 'development';
/** @type {import('webpack').Configuration} */
const config = {
  context: rootDir,
  mode: isDev ? 'development' : 'production',
  entry: {
    dll: [
      '@angular/common',
      '@angular/core',
      '@angular/forms',
      '@angular/platform-browser',
      '@angular/platform-browser-dynamic',
      '@angular/router',
      '@lzwme/asmd-calc',
      // more...
    ],
  },
  output: {
    path: join(rootDir, 'dist/dll'),
    filename: 'dll.js',
    library: '[name]_library',
  },
  plugins: [
    new webpack.DllPlugin({
      path: join(rootDir, 'dist/dll/[name]-manifest.json'),
      name: '[name]_library',
    }),
    new webpack.IgnorePlugin({
      resourceRegExp: /^./locale$/,
      contextRegExp: /moment$/,
    }),
  ],
  cache: { type: 'filesystem' },
};
webpack(config).run((err, result) => {
  console.log(err  `Failed!` : `Success!`, err || `${result.endTime - result.startTime}ms`);
});

然后在 angular.jsondevelopment.scripts 中添加上面提到的 dll.js 注入配置,格式参考前文示例。

最后在 package.json 里加上启动脚本:

{
    "scripts": {
        "ng:serve": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve",
        "dll": "node config/webpack.dll.mjs",
        "dev": "npm run dll -- --dev && npm run ng:serve -- -c development",
    }
}

之后执行 npm run dev 测试效果,检查是否符合预期。

3 小结

Angular CLI 升级到 webpack 5 后,利用 webpack 5 的缓存能力进行了大量编译优化,正常情况下二次构建速度提升明显。但与 snowpack、vite 这类 esm no-bundles 方案相比,差距仍然不小。从 Angular 13 开始已经在尝试引入 esbuild,不过由于构建逻辑高度定制化,配置参数的兼容适配较为复杂。到 Angular 15 已经可以在生产环境尝试,感兴趣的话可以升级试试看。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。