Angular-cli : How to ignore class names from being minified

Viewed 10298

For an application we need to keep the classname not minified because we use

var className = myObject.constructor.name;
export class myObject{ ... }

when we run

ng build -- pro

the class name gets minified in a random name.

8 Answers

Angular cli builder supports NG_BUILD_MANGLE, NG_BUILD_MINIFY, NG_BUILD_BEAUTIFY node environment params (checked in version 8).

You can set them when running npm script in following way: env NG_BUILD_MANGLE=false NG_BUILD_MINIFY=false NG_BUILD_BEAUTIFY=true ng build --prod

This will result in unminified output, yet tree shaking and other optimizations will still be applied (compared to just turning off optimization).

adding to yantrab's answer for angular 8.2+ using angular-builders

in angular.json

builder: @angular-builders/custom-webpack:browser

options:

    "customWebpackConfig": {
      "path": "./extra-webpack.config.js",
      "mergeStrategies": {
        "externals": "replace"
      }
    },

in extra-webpack.config.js:

module.exports = config => {
  config.optimization.minimizer.filter (({constructor: {name}}) => name === 'TerserPlugin')
  .forEach (terser => {
    terser.options.terserOptions.keep_classnames = true;
  });

  return config;
};

then either target es5 or es2015 with differential loading disabled per the browserslist discussed here: https://github.com/just-jeb/angular-builders/issues/144#issuecomment-568890065

For Angular 9-12, some of the other answers don't work anymore.

Similar to Rob's answer, we didn't want to worry about maintaining modified versions of @angular-devkit/build-angular, so I instead used ngx-build-plus as our builder in order to introduce a simple plugin to the build process that overrides the Terser config. This works for Angular 9-11.

I created a new file named build-plugin.js:

exports.default = {
  pre: function () {
  },
  config: function (cfg) {
    // Override Angular's internal configuration of Terser to preserve class names.
    // This won't work if you build multiple (differential) client bundles, so make sure you only target es5 builds in tsconfig.json.
    // See https://github.com/just-jeb/angular-builders/issues/144#issuecomment-576424615
    cfg.optimization.minimizer.forEach(function (it) {
      if (it.constructor.name === 'TerserPlugin') {
        it.options.terserOptions["keep_fnames"] = true;
        it.options.terserOptions["keep_classnames"] = true;
      }
    });
    return cfg;
  },
  post: function () {
  }
};

Then updated the project's angular.json:

{
  "projects": {
    "my-project": {
      "architect": {
        "build": {
          "builder": "ngx-build-plus:browser",
          "options": {
            "plugin": "~build-plugin.js",
            ...
          }
        }
      }
    }
  }
  ...
}

In Angular 12, they've changed the code optimizer from TerserPlugin to JavaScriptOptimizerPlugin for most projects. So just update build-plugin.js to include an override for it as well:

if (it.constructor.name === 'TerserPlugin') {
    it.options.terserOptions["keep_fnames"] = true;
    it.options.terserOptions["keep_classnames"] = true;
} else if (it.constructor.name === 'JavaScriptOptimizerPlugin') {
    it.options.keepNames = true;
}

angular 8 using terser.

this is my config for angular builder :

module.exports = cfg => {
    const options = cfg.optimization.minimizer[cfg.optimization.minimizer.length - 1].options.terserOptions;
    options.keep_classnames = true;
    return cfg;
};

and change terget to es6

It's possible to avoid mangling specific symbols by doing the following:

  • Use the NG_BUILD_MANGLE=false $NG build --prod ... approach to tell angular to avoid doing any mangling of names.
  • Use ngx-build-plus which merges a partial webpack config with the one used by angular. Use the terser plugin to perform mangling, but make sure to pass it the reserved list of names you don't want to be mangled.
ng add ngx-build-plus

An example of the sort of webpack.partial.js you could use is:

const terser = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new terser.TerserPlugin({
        terserOptions: {
          compress: false,
          mangle: {
            reserved: [
              "MyClassName1", ...
            ]
          }
        }
      })
    ]
  }
}

(n.b. make sure you've run npm install --save-dev webpack as well!)

And then amend your build command so it's of this form:

NG_BUILD_MANGLE=false ng build --prod ... --extra-webpack-config webpack.partial.js -o

These two things combined tell the angular build process to avoid mangling and lets your configured instance of TerserPlugin do the mangling at a later stage whilst respecting the classes you want to remain un-mangled.

Building off Andres M's answer, you can disable mangling without ng eject by directly modifying the Angular file that controls the settings for Webpack: node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/common.js

Note that this will be overwritten whenever @angular-devkit/build-angular is updated, and is not supported by Angular or NPM (and may kill your cat/cause a nuclear armageddon, don't say I didn't warn you!)

I disabled mangling entirely by changing uglifyOptions to uglifyOptions: Object.assign(uglifyOptions, {mangle: false}).

For reference, here's the relevant portion from my modified common.js file

...
new UglifyJSPlugin({
  sourceMap: buildOptions.sourceMap,
  parallel: true,
  cache: true,
  uglifyOptions: Object.assign(uglifyOptions, {"mangle": false})
}),
...
Related