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
- 报错截图:


- 已自行排查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
相关产品推荐
相关产品推荐

