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 負(fù)荷被打滿,而在構(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ù)由默認(rèn)值 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 后的默認(rèn)生產(chǎn)模式,對于跨版本升級來說是比較坑爹的。我們可以從這個提交中了解變動細(xì)節(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é)果默認(rèn)為 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)容可視自身項目具體情況對比參考。一些細(xì)節(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)化解析的詳細(xì)內(nèi)容,更多關(guān)于Angular 13+性能優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Angularjs material 實現(xiàn)搜索框功能
這篇文章主要介紹了Angularjs material 實現(xiàn)搜索框功能的相關(guān)資料,需要的朋友可以參考下2016-03-03
利用JavaScript的AngularJS庫制作電子名片的方法
這篇文章主要介紹了利用JavaScript的AngularJS庫制作電子名片的方法,其中需要使用到HTML5的canvas畫布,需要的朋友可以參考下2015-06-06
如何解決手機瀏覽器頁面點擊不跳轉(zhuǎn)瀏覽器雙擊放大網(wǎng)頁
這篇文章主要介紹了如何解決手機瀏覽器頁面點擊不跳轉(zhuǎn)瀏覽器雙擊放大網(wǎng)頁的相關(guān)資料,需要的朋友可以參考下2016-07-07
AngularJS基礎(chǔ) ng-class-odd 指令示例
本文主要介紹AngularJS ng-class-odd 指令,這里對ng-class-odd基礎(chǔ)知識做了詳細(xì)整理,并有示例代碼和效果圖,學(xué)習(xí)AngularJS的同學(xué)可以參考下2016-08-08
詳解關(guān)于Angular4 ng-zorro使用過程中遇到的問題
這篇文章主要介紹了詳解關(guān)于Angular4 ng-zorro使用過程中遇到的問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
AngularJS基礎(chǔ)學(xué)習(xí)筆記之簡單介紹
AngularJS 不僅僅是一個類庫,而是提供了一個完整的框架。它避免了您和多個類庫交互,需要熟悉多套接口的繁瑣工作。它由Google Chrome的開發(fā)人員設(shè)計,引領(lǐng)著下一代Web應(yīng)用開發(fā)。也許我們5年或10年后不會使用AngularJS,但是它的設(shè)計精髓將會一直被沿用。2015-05-05

