最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Webpack's removes classnames when minifyinguglifying ES6 code with inheritance - Stack Overflow

programmeradmin2浏览0评论

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] = 'monjs ' + 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: {
                    press: {
                        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");
} ]);

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] = 'monjs ' + 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: {
                    press: {
                        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");
} ]);
Share Improve this question edited Apr 2, 2018 at 11:11 Anatoly asked Apr 1, 2018 at 12:54 AnatolyAnatoly 5,25111 gold badges68 silver badges147 bronze badges 8
  • n.exports = class extends r { is a correct syntax. Do you get any error when you run your code, because of the missing class name? – t.niese Commented Apr 2, 2018 at 10:51
  • I mean why are you ok that the class name is mangled, but it is not ok if it is missing? If you don't care about the name why do you care about its existence? – t.niese Commented Apr 2, 2018 at 10:59
  • Yes, I get an error. We rely on a code this.constructor.name. If a code isn't minified it works otherwise it returns or an empty string (when the code runs in NodeJS v6.9.1) or a name of the class from which it was inherited in our case, it's Parent class what is definitely wrong. The last one happens when we run minified code on NodeJS v8.9.1 – Anatoly Commented Apr 2, 2018 at 11:08
  • I've created a simple project which demonstrate an issue, you can download it here: github./anatoly314/webpack-uglify-inheritence – Anatoly Commented Apr 2, 2018 at 11:12
  • But why do you want to realy on this.constructor.name === 'Parent' in a context where names can be mangled? I don't see that there is any bug with the minification, the bug is in my opinion in your code, and you should write somethign like if (this.constructor == Parent){ – t.niese Commented Apr 2, 2018 at 11:17
 |  Show 3 more ments

2 Answers 2

Reset to default 4 +100

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;

Try my lib typescript-class-helpers

import { CLASS } from 'typescript-class-helpers/browser';

@CLASS.NAME('Parent')
class Parent {
    constructor() {
        if (CLASS.getNameFromObject(child) === 'Parent'){
            throw new TypeError("Parent class is abstract - cant be instance");
        }
    }

}

@CLASS.NAME('Child')
class Child extends ParentClass{
    constructor(){
        super();
    }
}

let child = new Child();

console.log(CLASS.getNameFromObject(child)); // Child

With this you can minify your classes names and everything will be ok.

发布评论

评论列表(0)

  1. 暂无评论