Angular 13+開發(fā)模式慢的原因及構(gòu)建性能優(yōu)化解析
1 Angular 13+ 開發(fā)模式太慢的原因與解決
近期在某個高頻迭代七年的 Angular 項目升級至 Angular 13 后,其開發(fā)模式的構(gòu)建速度慢、資源占用高,開發(fā)體驗相當(dāng)差。在一臺僅在開會時偶爾使用的 Macbook air
(近期居家辦公期間轉(zhuǎn)換為了主要生產(chǎn)力工具) 中啟動構(gòu)建時,它的風(fēng)扇會呼呼作響,CPU 負荷被打滿,而在構(gòu)建完成后,熱更新一次的時間在一分鐘以上。
在經(jīng)過各種原因分析與排查后,最終在 angular.json
的 schema(./node_modules/@angular/cli/lib/config/schema.json
) 中發(fā)現(xiàn)了問題,再結(jié)合 Angular 12 release 文檔定位到了具體原因: Angular 12 一個主要的改動是將 aot
、buildOptimizer
、optimization
等參數(shù)由默認值 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 后的默認生產(chǎn)模式,對于跨版本升級來說是比較坑爹的。我們可以從這個提交中了解變動細節(jié):656f8d7
1.1 解決 Angular 12+ 開發(fā)模式慢的問題
解決辦法則是在 development
配置中禁用生產(chǎn)模式相關(guān)的配置項。示例:
{ "$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
開啟與關(guān)閉時,在構(gòu)建結(jié)果表現(xiàn)上可能會有一些差異,需視具體問題而分析。
1.2 問題:開啟 aot 后 pug 編譯報錯
該項目中使用 pug
開發(fā) html 內(nèi)容。關(guān)閉 aot
時構(gòu)建正常,開啟后則會報錯。
根據(jù)報錯內(nèi)容及位置進行 debugger 調(diào)試,可以看到其編譯結(jié)果為一個 esModule 的對象。這是由于使用了 raw-loader
,其編譯結(jié)果默認為 esModule
模式,禁用 esModule
配置項即可。示例(自定義 webpack 配置可參考下文的 dll 配置相關(guān)示例):
{ test: /\.pug$/, use: [ { loader: 'raw-loader', options: { esModule: false, }, }, { loader: 'pug-html-loader', options: { doctype: 'html', }, }, ], },
2 進一步優(yōu)化:Angular 自定義 webpack 配置 dll 支持
該項目項目構(gòu)建上有自定義 webpack
配置的需求,使用了 @angular-builders/custom-webpack
庫實現(xiàn),但是沒有配置 dll。
Angular
提供了 vendorChunk
參數(shù),開啟它會提取在 package.json
中的依賴等公共資源至獨立 chunk 中,其可以很好的解決熱更新 bundles 過大導(dǎo)致熱更新太慢等的問題,但仍然存在較高的內(nèi)存占用,而且實際的對比測試中,在存在 webpack5 緩存的情況下,其相比 dll 模式的構(gòu)建編譯速度以及熱更新速度都稍微慢一些。故對于開發(fā)機器性能一般的情況下,給開發(fā)模式配置 dll 是會帶來一定的收益的。
2.1 Angular 支持自定義 webpack 配置
首先需要配置自定義 webpack 配置的構(gòu)建支持。執(zhí)行如下命令添加依賴:
npm i -D @angular-builders/custom-webpack
修改 angluar.json
配置。內(nèi)容格式參考:
{ "$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 } } }
該示例中涉及多處自定義配置內(nèi)容,主要需注意 webpack 相關(guān)的部分, 其他內(nèi)容可視自身項目具體情況對比參考。一些細節(jié)也可參考以前的這篇文章中的實踐介紹:lzw.me/a/update-to…
2.2 為 Angular 配置 webpack dll 支持
新建 webpack.config.js
文件。內(nèi)容參考:
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 構(gòu)建。內(nèi)容示例:
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
中的配置內(nèi)容格式。
在 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", } }
最后,可執(zhí)行 npm run dev
測試效果是否符合預(yù)期。
3 小結(jié)
angular-cli
在升級至 webpack 5 以后,基于 webpack 5 的緩存能力做了許多編譯優(yōu)化,一般情況下開發(fā)模式二次構(gòu)建速度相比之前會有大幅的提升。但是相比 snowpack
和 vite
一類的 esm no bundles 方案仍有較大的差距。其從 Angular 13
開始已經(jīng)在嘗試引入 esbuild
,但由于其高度定制化的構(gòu)建邏輯適配等問題,對一些配置參數(shù)的兼容支持相對較為復(fù)雜。在 Angular 15
中已經(jīng)可以進行生產(chǎn)級配置嘗試了,有興趣也可作升級配置與嘗試。
以上就是Angular 13+開發(fā)模式慢的原因及構(gòu)建性能優(yōu)化解析的詳細內(nèi)容,更多關(guān)于Angular 13+性能優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spartacus CMS Feature selector的實現(xiàn)解析
這篇文章主要為大家介紹了Spartacus CMS Feature selector的實現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07Angualrjs和bootstrap相結(jié)合實現(xiàn)數(shù)據(jù)表格table
這篇文章主要介紹了Angualrjs和bootstrap相結(jié)合實現(xiàn)數(shù)據(jù)表格table,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-03-03angularjs+bootstrap實現(xiàn)自定義分頁的實例代碼
本篇文章主要介紹了angularjs+bootstrap實現(xiàn)自定義分頁的實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06AngularJS使用ng-class動態(tài)增減class樣式的方法示例
這篇文章主要介紹了AngularJS使用ng-class動態(tài)增減class樣式的方法,結(jié)合具體實例形式分析了ng-class操作頁面class樣式的相關(guān)技巧,需要的朋友可以參考下2017-05-05Angular應(yīng)用Bootstrap過程步驟邏輯詳解
這篇文章主要為大家介紹了Angular應(yīng)用Bootstrap過程步驟邏輯詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07AngularJs Understanding the Controller Component
本文主要介紹AngularJs Understanding the Controller Component的內(nèi)容,這里整理了相關(guān)資料,及簡單示例代碼,有興趣的小伙伴可以參考下2016-09-09