Node.js在使用--watch构建Angular 6时崩溃



我在Mac上使用Node v8.12.0(尽管我在Node 9.x版本和Linux上都看到过这个问题(。

我正在开发Angular 6应用程序,并使用--watch标志运行开发构建。手表将运行,可以重建应用程序4或5次,然后Node崩溃,输出如下:

<--- Last few GCs --->
[34201:0x104000000]   273927 ms: Mark-sweep 1309.4 (1430.5) -> 1309.2 (1431.0) MB, 1296.0 / 0.0 ms  allocation failure GC in old space requested
[34201:0x104000000]   275358 ms: Mark-sweep 1309.2 (1431.0) -> 1309.2 (1424.0) MB, 1430.8 / 0.0 ms  last resort GC in old space requested
[34201:0x104000000]   276946 ms: Mark-sweep 1309.2 (1424.0) -> 1309.2 (1423.5) MB, 1587.7 / 0.0 ms  last resort GC in old space requested

<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x1c5f3a825879 <JSObject>
1: fromString(aka fromString) [buffer.js:~298] [pc=0x2234a1ca140b](this=0x1c5ffcc022d1 <undefined>,string=0x1c5f6f8dffa1 <Very long string[784654]>,encoding=0x1c5ffcc022d1 <undefined>)
2: from [buffer.js:177] [bytecode=0x1c5f43e4aac9 offset=11](this=0x1c5f8a5b5c51 <JSFunction Buffer (sfi = 0x1c5f3a87e159)>,value=0x1c5f6f8dffa1 <Very long string[784654]>,encodingOrOffset=0x1c5ffcc022d1 <u...
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
1: node::Abort() [/usr/local/bin/node]
2: node::FatalException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>) [/usr/local/bin/node]
3: v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) [/usr/local/bin/node]
4: v8::internal::Factory::NewRawTwoByteString(int, v8::internal::PretenureFlag) [/usr/local/bin/node]
5: v8::internal::String::SlowFlatten(v8::internal::Handle<v8::internal::ConsString>, v8::internal::PretenureFlag) [/usr/local/bin/node]
6: v8::String::WriteUtf8(char*, int, int*, int) const [/usr/local/bin/node]
7: node::StringBytes::Write(v8::Isolate*, char*, unsigned long, v8::Local<v8::Value>, node::encoding, int*) [/usr/local/bin/node]
8: node::Buffer::New(v8::Isolate*, v8::Local<v8::String>, node::encoding) [/usr/local/bin/node]
9: node::Buffer::(anonymous namespace)::CreateFromString(v8::FunctionCallbackInfo<v8::Value> const&) [/usr/local/bin/node]
10: 0x2234a02d4067
11: 0x2234a1ca140b
12: 0x2234a023d1d6
13: 0x2234a018535f

我也尝试过添加--max_old_space_size=12000,但似乎没有任何区别。我不知道从哪里查找问题的原因,也不知道如何在Node中开始调试。如有任何协助,我们将不胜感激!

一些背景:它是Angular 5应用程序,带有弹出的配置,我已经将其更新为Angular 6,带有相同的配置,应用程序本身也按预期工作。直到更新到Angular 6之后,这个问题才开始出现。

以下是package.json:的dependency和devDependency部分,供参考

"dependencies": {
"@angular/animations": "6.1.10",
"@angular/cdk": "6.4.7",
"@angular/common": "6.1.10",
"@angular/compiler": "6.1.10",
"@angular/core": "6.1.10",
"@angular/forms": "6.1.10",
"@angular/http": "6.1.10",
"@angular/material": "6.4.7",
"@angular/platform-browser": "6.1.10",
"@angular/platform-browser-dynamic": "6.1.10",
"@angular/router": "6.1.10",
"@ng-idle/core": "6.0.0-beta.3",
"@ng-idle/keepalive": "6.0.0-beta.3",
"@ngrx/effects": "6.1.2",
"@ngrx/entity": "6.1.2",
"@ngrx/router-store": "6.1.2",
"@ngrx/store": "6.1.2",
"@ngrx/store-devtools": "6.1.2",
"@swimlane/ngx-datatable": "14.0.0",
"@types/crypto-js": "3.1.37",
"@types/moment": "2.13.0",
"angular2-toaster": "6.1.0",
"angulartics2": "7.2.0",
"core-js": "2.5.7",
"crypto-js": "3.1.9-1",
"hammerjs": "2.0.8",
"immutable": "3.8.2",
"jquery": "2.2.4",
"moment": "2.19.1",
"ng2-charts": "1.6.0",
"ngx-zendesk-webwidget": "0.1.3",
"node-waves": "0.7.6",
"normalize.css": "3.0.3",
"rxjs": "6.3.3",
"sass": "1.15.1",
"zone.js": "0.8.26"
},
"devDependencies": {
"@angular-builders/custom-webpack": "7.0.0",
"@angular-devkit/build-angular": "0.11.0",
"@angular/cli": "7.0.6",
"@angular/compiler-cli": "6.1.10",
"@angular/language-service": "6.1.10",
"@types/jasmine": "2.5.53",
"@types/jasminewd2": "2.0.2",
"@types/node": "6.0.60",
"autoprefixer": "9.3.1",
"chromedriver": "2.38.2",
"clean-webpack-plugin": "1.0.0",
"codelyzer": "4.5.0",
"copy-webpack-plugin": "4.6.0",
"css-loader": "1.0.1",
"cssnano": "4.1.7",
"exports-loader": "0.7.0",
"file-loader": "2.0.0",
"istanbul-instrumenter-loader": "2.0.0",
"jasmine-allure-reporter": "1.0.2",
"jasmine-core": "2.6.2",
"jasmine-marbles": "0.4.0",
"jasmine-spec-reporter": "4.2.1",
"karma": "3.0.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "2.0.1",
"karma-jasmine": "1.1.2",
"karma-jasmine-html-reporter": "0.2.2",
"karma-spec-reporter": "0.0.32",
"lint-staged": "8.1.0",
"loader-utils": "1.1.0",
"mini-css-extract-plugin": "0.4.5",
"npm-run-all": "4.1.5",
"postcss-custom-properties": "8.0.9",
"postcss-loader": "3.0.0",
"postcss-url": "8.0.0",
"pre-commit": "1.2.2",
"process": "0.11.10",
"protractor": "5.4.1",
"protractor-console": "3.0.0",
"protractor-jasmine2-html-reporter": "0.0.7",
"puppeteer": "1.6.0",
"raw-loader": "0.5.1",
"rxjs-tslint": "0.1.5",
"sass-loader": "7.1.0",
"selenium-server-standalone-jar": "3.8.1",
"source-map-loader": "0.2.4",
"style-loader": "0.23.1",
"stylelint": "9.6.0",
"stylelint-config-recommended": "2.1.0",
"ts-mockito": "2.3.1",
"ts-node": "3.2.0",
"tslint": "5.7.0",
"typescript": "2.9.2",
"uglifyjs-webpack-plugin": "2.0.1",
"url-loader": "1.1.2",
"webpack": "4.24.0",
"webpack-bundle-analyzer": "3.0.3",
"webpack-cli": "3.1.2",
"webpack-concat-plugin": "3.0.0",
"webpack-dev-server": "3.1.10",
"webpack-filter-warnings-plugin": "^1.2.1",
"yargs": "8.0.1"
}

最后,这里是我正在使用的自定义webpack配置(它在Angular 5中完美地工作(:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const autoprefixer = require('autoprefixer');
const postcssUrl = require('postcss-url');
const cssnano = require('cssnano');
const customProperties = require('postcss-custom-properties');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const { NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, NormalModuleReplacementPlugin } = require('webpack');
const { AngularCompilerPlugin } = require('@ngtools/webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const FilterWarningsPlugin = require('webpack-filter-warnings-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const postcssPlugins = function (env) {
// safe settings based on: https://github.com/ben-eb/cssnano/issues/358#issuecomment-283696193
const importantCommentRe = /@preserve|@license|[@#]s*source(?:Mapping)?URL|^!/i;
const baseHref = '';
const deployUrl = '';
const minimizeOptions = {
preset: [
'default',
{
mergeLonghand: false,
discardComments: { remove: (comment) => !importantCommentRe.test(comment) }
}
]
};
return [
postcssUrl({
url: (URL) => {
// Only convert root relative URLs, which CSS-Loader won't process into require().
if (!URL.url.startsWith('/') || URL.url.startsWith('//')) {
return URL.url;
}
if (deployUrl.match(/:///)) {
// If deployUrl contains a scheme, ignore baseHref use deployUrl as is.
return `${deployUrl.replace(//$/, '')}${URL.url}`;
}
else if (baseHref.match(/:///)) {
// If baseHref contains a scheme, include it as is.
return baseHref.replace(//$/, '') +
`/${deployUrl}/${URL.url}`.replace(///+/g, '/');
}
else {
// Join together base-href, deploy-url and the original URL.
// Also dedupe multiple slashes into single ones.
return `/${baseHref}/${deployUrl}/${URL.url}`.replace(///+/g, '/');
}
}
}),
autoprefixer(),
customProperties({ preserve: true })
].concat(env === 'prod' ? [cssnano(minimizeOptions)] : []);
};
const builder = (customer, prodEnv) => {
let plugins = [
new ProgressPlugin(),
new NoEmitOnErrorsPlugin(),
new FilterWarningsPlugin({
exclude: /System.import/
}),
new CleanWebpackPlugin(['target/classes/static/' + customer]),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
}),
new CopyWebpackPlugin([
{
context: 'src/main/angular',
to: '',
from: {
glob: 'assets/**/*',
dot: true
}
},
{
context: 'src/main/angular',
to: '',
from: {
glob: 'favicon.ico',
dot: true
}
}
], {
ignore: [
'.gitkeep',
'**/.DS_Store'
],
debug: 'warning'
}),
//Replace the actual environment file with the correct one passed in via env args
new NormalModuleReplacementPlugin(/(.*)environments/environment(.*)/, function(resource) {
resource.request = resource.request.replace('environments/environment',
`environments/${customer}/environment.${prodEnv}`);
}),
//Replace the actual chart-colors file with the correct one based on customer
new NormalModuleReplacementPlugin(/(.*)environments/chart-colors.json/, function(resource) {
resource.request = resource.request.replace('environments/chart-colors.json',
`environments/${customer}/chart-colors.json`);
}),
//Replace the actual lang file with the correct one based on customer
new NormalModuleReplacementPlugin(/(.*)environments/lang.json/, function(resource) {
resource.request = resource.request.replace('environments/lang.json',
`environments/${customer}/lang.json`);
}),
//Replace the actual scss file with the correct one based on customer
new NormalModuleReplacementPlugin(/(.*)environments/styles.scss/, function(resource) {
resource.request = resource.request.replace('environments/styles.scss',
`environments/${customer}/styles.scss`);
}),
new AngularCompilerPlugin({
mainPath: 'main.ts',
platform: 0,
sourceMap: (prodEnv === 'dev') ? true : false,
tsConfigPath: 'src/main/angular/tsconfig.app.json',
skipCodeGeneration: true,
compilerOptions: {}
}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
"window.jQuery": 'jquery',
Hammer: 'hammerjs/hammer'
})
];
let devPlugins = [
new CircularDependencyPlugin({
exclude: /(\|/)node_modules(\|/)/,
failOnError: false
}),
new SourceMapDevToolPlugin({
filename: '[file].map[query]',
moduleFilenameTemplate: '[resource-path]',
fallbackModuleFilenameTemplate: '[resource-path]?[hash]',
sourceRoot: 'webpack:///',
exclude: ['vendor.js']
}),
new BundleAnalyzerPlugin({
generateStatsFile: true
})
];
let prodPlugins = [
new UglifyJsPlugin({
parallel: true,
sourceMap: false
})
];
plugins = prodEnv === 'dev'
? plugins.concat(devPlugins)
: plugins.concat(prodPlugins);
return  {
resolve: {
extensions: [
'.ts',
'.js'
],
modules: [
'./node_modules'
],
symlinks: true,
alias: {
"rxjs/" : './node_modules/rxjs/_esm2015/'
},
mainFields: [
'browser',
'module',
'main'
]
},
resolveLoader: {
modules: [
'./node_modules'
]
},
entry: {
main: [
'./src/main/angular/main.ts'
],
polyfills: [
'./src/main/angular/polyfills.ts'
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
test: path.join(process.cwd(), 'node_modules'),
name: 'vendor',
enforce: true,
filename: 'vendor.chunk.js'
}
}
}
},
output: {
path: path.join(process.cwd(), 'target', 'classes', 'static', customer),
filename: '[name].bundle.js',
chunkFilename: '[id].chunk.js',
crossOriginLoading: false
},
module: {
rules: [
{
test: /.html$/,
loader: 'raw-loader'
},
{
test: /.(eot|svg|cur)$/,
loader: 'file-loader',
options: {
name: '[name].[hash:20].[ext]',
limit: 10000
}
},
{
test: /.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
loader: 'url-loader',
options: {
name: '[name].[hash:20].[ext]',
limit: 10000
}
},
{
test: /.css$/,
use: [
'exports-loader?module.exports.toString()',
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 1
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: postcssPlugins(prodEnv)
}
}
]
},
{
test: /.css$/,
include: [
path.join(process.cwd(), `src/main/angular/environments/${customer}/styles.scss`)
],
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 1
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: postcssPlugins(prodEnv)
}
}
]
},
{
test: /.scss$/,
include: [
path.join(process.cwd(), `src/main/angular/environments/${customer}/styles.scss`)
],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 1
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: postcssPlugins(prodEnv)
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false,
precision: 8,
includePaths: [path.join(process.cwd(), 'src', 'main', 'angular')]
}
}
]
},
{
test: /.ts$/,
loader: '@ngtools/webpack'
}
]
},
mode: (prodEnv === 'prod') ? 'production' : 'development',
plugins: plugins,
node: {
fs: 'empty',
global: true,
crypto: 'empty',
tls: 'empty',
net: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
},
devServer: {
historyApiFallback: true
},
watchOptions: {
aggregateTimeout: 500
}
};
};
module.exports = {
build: builder
}

这被称为内存泄漏,它表示您试图保留大量内存

请注意,分配更多内存(--max_new_space_size和/或--max_old_space_size(并不能解决主要问题,但可能有助于继续处理消耗内存的应用程序。

背景

正如您所知,在javascript应用程序中,构建是一个导出缩小捆绑包的过程,这些捆绑包来自依赖项和您的单个代码。在某些情况下,依赖项之间的兼容性问题(版本不匹配(可能会保留更多内存!例如,用户发现lodashv4.14.70与TS2.7开箱即用不兼容。虽然您不使用lodash,但这样的问题是可以预料的。

此外,您必须熟悉javascript中发生内存泄漏的情况,然后找出应用程序中使用过多内存的原因。我建议您也录制堆快照。我列出了一些可能发生内存泄漏的情况(一目了然(:

  • 将元素推送到数组,但不释放或重置数组
  • 将闭包附加为事件侦听器处理程序函数
  • 将回调存储到即使存在回调的对象中不再需要
  • 将符号(具有相同名称(存储为对象的属性
  • 不断创建新的对象属性(每个属性都有不同的name(,而不删除以前的
  • 向集合、映射或WeakMap添加属性
  • 将未解决的承诺存储在阵列中
  • 解析一个巨大的JSON对象

调试

由于您的应用程序在更新到Angular6之前运行良好,主要影响来自您的新依赖项。我认为这对于从未使用的依赖项中清除项目是必要的,这是第一步,也许可以通过一些类似依赖项检查的工具(我从未测试过它(然后尝试使用Angular Update Guide再次将项目从Angular5迁移到Angular6然后检查依赖项之间的兼容性。在写这个答案的时候,我还没有找到任何工具来找到兼容的版本,但作为一个技巧,你可以使用在同一时间段(并发(发布的版本。

我们在Angular 5中也看到过这种情况。我们的解决方案(解决方法?(是使用max_old_space_size参数运行ng serve。

node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --aot

设置--max_old_space_size=8192对我来说很有效,但对于单部分开发人员来说,它确实是一个很好的解决方案。另一方面,我们可以尝试另一种解决方案

这是我的解决方案,如果在windows上,它需要人们使用gitbash作为终端,但如果需要,它很容易更改(只需使用cmd文件(:

在我的项目根目录中,我有一个名为script的文件夹,其中有一个称为ng.sh的文件,它是node_modules/.bin/ng的副本,但允许使用更多的RAM

#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" --max_old_space_size=8192 "./node_modules/@angular/cli/bin/ng" "$@"
ret=$?
else
node --max_old_space_size=8192 "./node_modules/@angular/cli/bin/ng" "$@"
ret=$?
fi
exit $ret

然后在我的package.json中我做:

"scripts": {
"build-prod": "bash ./scripts/ng.sh build --prod --aot --env=prod"
}

最新更新