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

时间:2018-11-28 12:08:58

标签: node.js angular webpack angular6

我正在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的依赖项和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
}

2 个答案:

答案 0 :(得分:3)

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

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

背景

如您所知,在javascript应用程序中,build是一个导出小型捆绑包的过程,这些捆绑包来自于依赖项和您的个人代码。在某些情况下,依赖关系(不匹配版本)之间的兼容性问题可能会保留更多的内存!例如,用户发现了lodash v4.14.70 is not compatible with TS 2.7 out of the box。尽管您不使用 lodash ,但可以预期会出现这种情况。

此外,您必须熟悉cases in javascript where memory leaks happen,然后弄清应用程序中使用了过多的内存。我也建议您Record Heap Snapshots。我列出了一些可能发生内存泄漏的情况(一目了然):

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

调试

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

答案 1 :(得分:3)

我们已经看到Angular 5有时也会发生这种情况。我们的解决方案(解决方法?)是使用max_old_space_size参数运行ng服务。

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

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

这是我的解决方案,它要求人们在Windows上使用git bash作为终端,但是如果需要的话,它很容易更改(只需使用cmd文件即可):

在我的项目根目录中,有一个名为scripts的文件夹,其中有一个名为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"
}