Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack's removes classnames when minifying/uglifying ES6 code with inheritance

Webpack's removes classnames when minifying/uglifying ES6 code with inheritance:

There's MVCE code which we try to minify/uglify:

Class Child:

const ParentClass = require('parent');

class Child extends ParentClass{
    constructor(){
        super();
    }
}

module.exports = Child;

index.js which invokes Child class:

const Child = require('./classes_so/child');

let child = new Child();

console.log(child.constructor.name);

Module Parent inside node_modules:

class Parent {
    constructor() {
        if (this.constructor.name === 'Parent'){
            throw new TypeError("Parent class is abstract - cant be instance");
        }
    }

}

module.exports = Parent;

The whole output I'll post to the end of the question, here I want to post only relevant lines which I think cause a wrong behavior (lines 33-37 from original output):

n.exports = class extends r {
        constructor() {
            super();
        }
    };

Why a classname is missing here: class extends r? I expect that value will be mangled but will exist, can I consider it as a bug? I tried to use keep_classnames flag but it keeps original class names which unacceptable.

We're using:

  • Webpack: 3.11.0 (tried with 4, same behavior)
  • uglifyjs-webpack-plugin: 1.2.4 (tried with different plugins)
  • NodeJS: v6.9.1 and v8.9.1 (same output)
  • Complete project which demonstrate the issue: webpack-uglify-inheritence

Update 1:

  • Open an issue at uglifyjs-webpack-plugin repository

Our webpack.config.js:

const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const path = require('path');
const fs = require('fs');

const nodeModules = {};
const localDependencies = ['.bin'];
fs.readdirSync('node_modules')
    .filter(function (x) {
        return localDependencies.indexOf(x) === -1;
    })
    .forEach(function (mod) {
        nodeModules[mod] = 'commonjs ' + mod;
    });

try {


    module.exports = {
        target: 'node',
        node: {
            console: false,
            global: false,
            process: false,
            Buffer: false,
            __filename: true,
            __dirname: true
        },

        entry: './index_so.js',

        output: {
            path: path.join(__dirname, 'build'),
            filename: 'index.js'
        },

        externals: nodeModules,
        plugins: [
            new webpack.IgnorePlugin(/\.(css|less)$/),
            new webpack.BannerPlugin({
                banner: 'require("source-map-support").install();',
                raw: true,
                entryOnly: false
            })
        ],
        devtool: 'sourcemap',

        module: {
            loaders: [
                {test: /\.json$/, loader: "json-loader"}
            ]
        },

        plugins: [
            new UglifyJsPlugin({
                uglifyOptions: {
                    compress: {
                        warnings: false
                    },
                    keep_classnames: false,
                    mangle: true,
                    output: {
                        beautify: true
                    }
                }
            })
        ]
    };
}
catch (e) {
    console.error(e);
}

The whole minified/uglified code from the example above:

!function(n) {
    var t = {};
    function e(r) {
        if (t[r]) return t[r].exports;
        var o = t[r] = {
            i: r,
            l: !1,
            exports: {}
        };
        return n[r].call(o.exports, o, o.exports, e), o.l = !0, o.exports;
    }
    e.m = n, e.c = t, e.d = function(n, t, r) {
        e.o(n, t) || Object.defineProperty(n, t, {
            configurable: !1,
            enumerable: !0,
            get: r
        });
    }, e.n = function(n) {
        var t = n && n.__esModule ? function() {
            return n.default;
        } : function() {
            return n;
        };
        return e.d(t, "a", t), t;
    }, e.o = function(n, t) {
        return Object.prototype.hasOwnProperty.call(n, t);
    }, e.p = "", e(e.s = 0);
}([ function(n, t, e) {
    let r = new (e(1))();
    console.log(r.constructor.name);
}, function(n, t, e) {
    const r = e(2);
    n.exports = class extends r {
        constructor() {
            super();
        }
    };
}, function(n, t) {
    n.exports = require("parent");
} ]);
like image 330
Anatoly Avatar asked Apr 01 '18 12:04

Anatoly


Video Answer


1 Answers

The problem in the given setup is not in the code of webpack or uglify but in this part of the code:

class Parent {
  constructor() {
    if (this.constructor.name === 'Parent') {
      throw new TypeError("Parent class is abstract - cant be instance");
    }
  }

}

module.exports = Parent;

The this.constructor.name === 'Parent' relays on the the class/function name, to test if Parent was directly instanced.

Instead of relaying one the name which can result in various problems, it would be a better idea to test if the constructor equals the class.

class Parent {
  constructor() {
    if (this.constructor === Parent) {
      throw new TypeError("Parent class is abstract - cant be instance");
    }
  }

}

module.exports = Parent;
like image 76
t.niese Avatar answered Sep 29 '22 01:09

t.niese