首頁  >  文章  >  web前端  >  Angular13+ 開發模式太慢怎麼辦?原因與解決方法介紹

Angular13+ 開發模式太慢怎麼辦?原因與解決方法介紹

青灯夜游
青灯夜游轉載
2022-12-22 21:15:562378瀏覽

Angular13 開發模式太慢怎麼辦?以下這篇文章跟大家介紹一下Angular 13 開發模式太慢的原因與建構效能優化的方法,希望對大家有幫助!

Angular13+ 開發模式太慢怎麼辦?原因與解決方法介紹

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

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 buil 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 問題:開啟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中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除