You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular10转single-spa构建报Maximum call stack size exceeded

single-spa Angular 10 项目 npm run build 栈溢出报错排查方案

问题描述

  • 执行生产构建命令 npm run build 时触发 Maximum call stack size exceeded 栈溢出错误
  • 项目背景:原有Angular应用改造为single-spa微前端架构,同步将Angular版本升级至10.2.5
  • 报错截图:
    构建报错截图1
    构建报错截图2
  • 已自行排查3天未定位根因

现有项目配置

package.json 依赖配置

{
  "name": "fmp",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "ng": "ng",
    "build": "npm run env -s && ng build --prod",
    "start": "npm run env -s && ng serve --proxy-config proxy.conf.js",
    "lint": "ng lint && stylelint \"src/**/*.scss\" --syntax scss && htmlhint \"src\" --config .htmlhintrc",
    "test": "npm run env -s && ng test",
    "test:ci": "npm run env -s && npm run lint -s && ng test --configuration=ci",
    "e2e": "npm run env -s && ng e2e",
    "translations:extract": "ngx-translate-extract --input ./src --output ./src/translations/template.json --format=json --clean --sort --marker extract",
    "docs": "hads ./docs -o",
    "env": "ngx-scripts env npm_package_version",
    "generate": "ng generate"
  },
  "dependencies": {
    "@angular/animations": "~10.2.0",
    "@angular/cdk": "~10.2.0",
    "@angular/common": "~10.2.0",
    "@angular/compiler": "~10.2.0",
    "@angular/core": "~10.2.0",
    "@angular/elements": "~10.2.0",
    "@angular/flex-layout": "^8.0.0-beta.26",
    "@angular/forms": "~10.2.0",
    "@angular/material": "^8.1.3",
    "@angular/material-moment-adapter": "^8.2.3",
    "@angular/platform-browser": "~10.2.0",
    "@angular/platform-browser-dynamic": "~10.2.0",
    "@angular/router": "~10.2.0",
    "@fortawesome/fontawesome-free": "5.11.2",
    "@ng-bootstrap/ng-bootstrap": "^5.1.1",
    "@ngx-translate/core": "^11.0.1",
    "@swimlane/ngx-datatable": "16.0.2",
    "@webcomponents/custom-elements": "^1.2.4",
    "ag-grid-angular": "21.2.1",
    "ag-grid-community": "21.2.1",
    "angular-tree-component": "8.4.0",
    "angular2-text-mask": "9.0.0",
    "bootstrap": "^4.5.0",
    "classlist.js": "1.1.20150312",
    "codemirror": "5.49.0",
    "core-js": "^2.5.7",
    "enhanced-resolve": "4.1.0",
    "flot": "3.2.10",
    "ika.jvectormap": "1.0.0",
    "intl": "1.2.5",
    "jqcloud2": "2.0.3",
    "jquery": "3.4.1",
    "jquery-slimscroll": "1.3.8",
    "jquery-sparkline": "2.4.0",
    "jquery.browser": "0.1.0",
    "jquery.flot.tooltip": "0.9.0",
    "jqwidgets-ng": "^8.2.0",
    "lodash": "^4.17.15",
    "modernizr": "3.7.1",
    "moment": "2.24.0",
    "ng2-dnd": "5.0.2",
    "ng2-file-upload": "1.3.0",
    "ng2-material-dropdown": "0.10.1",
    "ng2-table": "1.3.2",
    "ng2-validation": "4.2.0",
    "ngx-bootstrap": "5.1.2",
    "ngx-infinite-scroll": "8.0.0",
    "ngx-select-ex": "3.7.2",
    "ngx-toastr": "^11.0.0",
    "ngx-virtual-scroller": "^3.0.3",
    "npm": "^6.11.3",
    "popper.js": "1.15.0",
    "rxjs": "~6.6.0",
    "screenfull": "5.0.0",
    "simple-line-icons": "2.4.1",
    "spinkit": "1.2.5",
    "summernote": "0.8.12",
    "sweetalert": "2.1.2",
    "ts-helpers": "1.1.2",
    "tslib": "1.10.0",
    "web-animations-js": "2.3.2",
    "zone.js": "0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1002.0",
    "@angular/cli": "~10.2.0",
    "@angular/compiler-cli": "~10.2.0",
    "@angular/language-service": "8.2.8",
    "@material/top-app-bar": "^3.2.0",
    "@ngneat/spectator": "^4.11.1",
    "@ngx-rocket/scripts": "^3.0.0",
    "@types/codemirror": "0.0.77",
    "@types/jasmine": "3.4.0",
    "@types/jasminewd2": "2.0.6",
    "@types/lodash": "4.14.139",
    "@types/node": "^12.11.1",
    "codelyzer": "^6.0.0",
    "hammerjs": "^2.0.8",
    "jasmine-core": "3.5.0",
    "jasmine-spec-reporter": "4.2.1",
    "karma": "5.0.9",
    "karma-chrome-launcher": "3.1.0",
    "karma-cli": "2.0.0",
    "karma-coverage-istanbul-reporter": "2.1.0",
    "karma-jasmine": "2.0.1",
    "karma-jasmine-html-reporter": "1.4.2",
    "karma-read-json": "1.1.0",
    "karma-spec-reporter": "0.0.32",
    "loaders.css": "0.1.2",
    "material-design-icons-iconfont": "^5.0.1",
    "ng-bullet": "^1.0.3",
    "prettier": "^1.14.0",
    "protractor": "5.4.4",
    "stylelint": "~9.6.0",
    "stylelint-config-prettier": "^4.0.0",
    "stylelint-config-recommended-scss": "~3.2.0",
    "stylelint-config-standard": "~18.2.0",
    "stylelint-scss": "~3.3.0",
    "ts-node": "8.4.1",
    "tslint": "5.20.0",
    "tslint-config-prettier": "^1.14.0",
    "typescript": "~4.0.2",
    "webdriver-manager": "12.1.7"
  },
  "prettier": {
    "singleQuote": true,
    "overrides": [
      {
        "files": "*.scss",
        "options": {
          "singleQuote": false
        }
      }
    ]
  }
}

angular.json 构建配置

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "mfe-fmp": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "outputPath": "docker/dist",
            "index": "src/index.html",
            "main": "src\\main.single-spa.ts",
            "tsConfig": "src/tsconfig.app.json",
            "polyfills": "src/polyfills.ts",
            "aot": true,
            "assets": [
              "src/favicon.ico",
              "src/apple-touch-icon.png",
              "src/assets"
            ],
            "styles": [],
            "scripts": [],
            "customWebpackConfig": {
              "path": "extra-webpack.config.js",
              "libraryName": "mfe-fmp",
              "libraryTarget": "umd"
            }
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.dev.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "none",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            }
          }
        },
        "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "options": {
            "browserTarget": "mfe-fmp:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "mfe-fmp:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "mfe-fmp:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "karmaConfig": "src/karma.conf.js",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "scripts": [],
            "styles": [],
            "assets": [
              "src/favicon.ico",
              "src/apple-touch-icon.png",
              "src/robots.txt",
              "src/manifest.json",
              "src/assets"
            ]
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "src/tsconfig.app.json",
              "src/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    },
    "e2e": {
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "mfe-fmp:serve"
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "e2e/tsconfig.e2e.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "mfe-fmp"
}

排查解决步骤(按优先级从高到低)

1. 修复依赖版本错配

现有依赖存在3处明确的版本兼容问题,是AOT构建栈溢出的高发诱因:

  • Angular核心包为10.2.x版本,但@angular/material、@angular/flex-layout、@angular/material-moment-adapter仍为8.x大版本,跨大版本的Angular生态包和核心编译逻辑不兼容,会在元数据解析阶段触发无限递归
  • 手动锁定的enhanced-resolve@4.1.0版本和Angular 10内置的webpack模块解析逻辑存在已知循环解析bug
  • TypeScript版本为4.0.2,Angular 10官方兼容的TS版本范围为3.9.x,高版本TS处理装饰器元数据时会出现递归解析异常

执行以下命令修复依赖版本:

# 升级不兼容的Angular生态包、降级TS到兼容版本
npm i @angular/material@10.2.x @angular/flex-layout@10.0.0-beta.32 @angular/material-moment-adapter@10.2.x typescript@~3.9.10 -S
# 移除手动锁定的enhanced-resolve,使用Angular内置的对应版本
npm uninstall enhanced-resolve -S
# 清除缓存重装依赖
rm -rf node_modules package-lock.json
npm i

2. 校验single-spa自定义webpack配置

检查extra-webpack.config.js配置,规避以下问题:

  • 不要通过resolve.alias将@angular/core、rxjs等核心包指向本地绝对路径,会和single-spa的externals配置冲突,触发模块解析循环
  • UMD打包模式下,不要把@angular/*系列包同时配置进externals和打包入口依赖,重复的模块声明会导致递归引用
  • 检查src/main.single-spa.ts入口文件,singleSpaAngular配置项中不要重复引入根模块的路由、根级providers,避免启动逻辑递归调用

3. 调整构建配置缩小排查范围

修改angular.json中production构建配置,逐个开关定位触发点:

"production": {
  // 暂时关闭构建优化器,Angular 10的buildOptimizer对部分旧版第三方库处理存在递归bug
  "buildOptimizer": false,
  // 打开sourceMap,让报错栈可以定位到具体文件
  "sourceMap": true,
  // 开启vendorChunk拆分,方便定位是哪个依赖包触发递归
  "vendorChunk": true
}

修改后重新执行构建,如果某一项开关关闭后构建恢复正常,即可锁定问题范围。

4. 扫描源码循环依赖

Angular 10的AOT编译器对循环依赖检测比旧版本严格,隐式循环依赖(比如服务间交叉注入、index.ts桶文件跨模块循环导出)在开发阶段不会报错,但生产构建压缩阶段会触发无限递归:

  • 执行npx madge --circular src/main.single-spa.ts扫描源码中的显式循环依赖并修复
  • 排查所有index.ts多文件重导出的写法,避免跨模块的循环导出逻辑

内容的提问来源于stack exchange,提问作者codeDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:57:10