(资料图片仅供参考)
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 一个主要的改动是将 aot
、buildOptimizer
、optimization
等参数由默认值 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 问题:开启 aot
后 pug
编译报错
该项目中使用 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 的缓存能力做了许多编译优化,一般情况下开发模式二次构建速度相比之前会有大幅的提升。但是相比 snowpack
和 vite
一类的 esm no bundles 方案仍有较大的差距。其从 Angular 13
开始已经在尝试引入 esbuild
,但由于其高度定制化的构建逻辑适配等问题,对一些配置参数的兼容支持相对较为复杂。在 Angular 15
中已经可以进行生产级配置尝试了,有兴趣也可作升级配置与尝试。
更多编程相关知识,请访问:编程教学!!
以上就是Angular13+ 开发模式太慢怎么办?原因与解决方法介绍的详细内容,更多请关注php中文网其它相关文章!