天天快消息!Angular13+ 开发模式太慢怎么办?原因与解决方法介绍

时间:2022-12-22 21:02:00       来源:转载
Angular13+ 开发模式太慢怎么办?下面本篇文章给大家介绍一下Angular 13+ 开发模式太慢的原因与构建性能优化的方法,希望对大家有所帮助!


(资料图片仅供参考)

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

近期在某个高频迭代七年的 Angular 项目升级至 Angular 13 后,其开发模式的构建速度慢、资源占用高,开发体验相当差。在一台仅在开会时偶尔使用的 Macbook air(近期居家办公期间转换为了主要生产力工具) 中启动构建时,它的风扇会呼呼作响,CPU 负荷被打满,而在构建完成后,热更新一次的时间在一分钟以上。【相关教程推荐:《angular教程》】

在经过各种原因分析与排查后,最终在 angular.json的 schema(./node_modules/@angular/cli/lib/config/schema.json) 中发现了问题,再结合 Angular 12 release 文档定位到了具体原因: Angular 12 一个主要的改动是将 aotbuildOptimizeroptimization等参数由默认值 false改为了 true

可以看到 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 问题:开启 aotpug编译报错

该项目中使用 pug开发 html 内容。关闭 aot时构建正常,开启后则会报错。

根据报错内容及位置进行 debugger 调试,可以看到其编译结果为一个 esModule 的对象。这是由于使用了 raw-loader,其编译结果默认为 esModule模式,禁用 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 中,其可以很好的解决热更新 bundles 过大导致热更新太慢等的问题,但仍然存在较高的内存占用,而且实际的对比测试中,在存在 webpack5 缓存的情况下,其相比 dll 模式的构建编译速度以及热更新速度都稍微慢一些。故对于开发机器性能一般的情况下,给开发模式配置 dll 是会带来一定的收益的。

2.1 Angular 支持自定义 webpack 配置

首先需要配置自定义 webpack 配置的构建支持。执行如下命令添加依赖:

npm i -D @angular-builders/custom-webpack
登录后复制

修改 angluar.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 相关的部分, 其他内容可视自身项目具体情况对比参考。一些细节也可参考以前的这篇文章中的实践介绍:lzw.me/a/update-to…

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.json中添加 dll.js 文件的注入配置,可参考前文示例中 development.scripts中的配置内容格式。

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 的缓存能力做了许多编译优化,一般情况下开发模式二次构建速度相比之前会有大幅的提升。但是相比 snowpackvite一类的 esm no bundles 方案仍有较大的差距。其从 Angular 13开始已经在尝试引入 esbuild,但由于其高度定制化的构建逻辑适配等问题,对一些配置参数的兼容支持相对较为复杂。在 Angular 15中已经可以进行生产级配置尝试了,有兴趣也可作升级配置与尝试。

更多编程相关知识,请访问:编程教学!!

以上就是Angular13+ 开发模式太慢怎么办?原因与解决方法介绍的详细内容,更多请关注php中文网其它相关文章!

关键词: 内容格式 一般情况下 具体情况