Webpack удаляет имена классов при минимизации/ухудшении кода ES6 с наследованием

Webpack удаляет имена классов при минимизации/ухудшении кода ES6 с наследованием:

Есть код MVCE, который мы пытаемся уменьшить/уменьшить:

Дочерний класс:

const ParentClass = require('parent');

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

module.exports = Child;

index.js, который вызывает класс Child:

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

let child = new Child();

console.log(child.constructor.name);

Родительский модуль внутри node_modules:

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

}

module.exports = Parent;

Весь вывод я опубликую в конце вопроса, здесь я хочу опубликовать только соответствующие строки, которые, по моему мнению, вызывают неправильное поведение (строки 33-37 из исходного вывода):

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

Почему здесь отсутствует имя класса: class extends r? Я ожидаю, что значение будет искажено, но будет существовать, могу ли я считать это ошибкой? Я пытался использовать флаг keep_classnames, но он сохраняет исходные имена классов, что неприемлемо.

Мы используем:

  • Webpack: 3.11.0 (пробовал с 4, то же самое)
  • uglifyjs-webpack-plugin: 1.2.4 (пробовал с разными плагинами)
  • NodeJS: v6.9.1 и v8.9.1 (тот же результат)
  • Полный проект, демонстрирующий проблему: webpack-uglify-inheritence

Обновление 1:

Наш 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);
}

Весь минимизированный/углифицированный код из приведенного выше примера:

!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");
} ]);

person Anatoly    schedule 01.04.2018    source источник
comment
n.exports = class extends r { - правильный синтаксис. Вы получаете какую-либо ошибку при запуске кода из-за отсутствующего имени class?   -  person t.niese    schedule 02.04.2018
comment
Я имею в виду, почему вы в порядке, что имя класса искажено, но не в порядке, если оно отсутствует? Если вас не волнует имя, почему вас волнует его существование?   -  person t.niese    schedule 02.04.2018
comment
Да, я получаю ошибку. Мы полагаемся на код this.constructor.name. Если код не минифицирован, то он работает, иначе он возвращает либо пустую строку (при запуске кода в NodeJS v6.9.1), либо имя класса, от которого он был унаследован, в нашем случае это Parent класс, что точно не так. Последнее происходит, когда мы запускаем минимизированный код на NodeJS v8.9.1.   -  person Anatoly    schedule 02.04.2018
comment
Я создал простой проект, демонстрирующий проблему, вы можете скачать его здесь: github.com/ anatoly314/webpack-uglify-inheritence   -  person Anatoly    schedule 02.04.2018
comment
Но почему вы хотите использовать this.constructor.name === 'Parent' в контексте, где имена могут быть искажены? Я не вижу бага с минификацией, баг на мой взгляд в вашем коде, и вы должны написать что-то вроде if (this.constructor == Parent){   -  person t.niese    schedule 02.04.2018
comment
Наш node_modules не искажен, поэтому, если кто-то будет его использовать, он будет содержать полное имя. В примере Parent внутри node_modules, чтобы он не был искажен.   -  person Anatoly    schedule 02.04.2018
comment
Кстати, сопровождающий uglifyjs-webpack-plugin предполагает, что это, скорее всего, ошибка, почему вы думаете, что это правильный синтаксис?   -  person Anatoly    schedule 02.04.2018
comment
Синтаксис действителен, поскольку стандарт определил его как действующий. Даже если ошибка возникает из-за минификации, причина ошибки не в этом. Причина в том, что вы используете код, который, на первый взгляд, работает, но не должен использоваться таким образом в целом, особенно если код или код, который его использует, можно минимизировать.   -  person t.niese    schedule 02.04.2018


Ответы (2)


Проблема в данной настройке не в коде webpack или uglify, а в этой части кода:

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

}

module.exports = Parent;

this.constructor.name === 'Parent' передает имя class/function, чтобы проверить, было ли Parent непосредственно создано.

Вместо того, чтобы передавать одно имя, что может привести к различным проблемам, было бы лучше проверить, равен ли конструктор классу.

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

}

module.exports = Parent;
person t.niese    schedule 02.04.2018
comment
Это не работает в искаженной версии. this.constructor возвращает Parent в обоих случаях, когда объект был создан из Parent или Client. - person Anatoly; 02.04.2018
comment
Ответ зависит от версии NodeJS, в версии 6.9.1 он возвращает пустое имя класса в расширенном конструкторе и Parent, если объект создается из объекта abstract. В версии 8.9.1 он возвращает Parent, когда объект создан как из унаследованного, так и из абстрактного класса. - person Anatoly; 02.04.2018
comment
@ Анатолий Анатолий Я тестировал его с 6.9.1 и новее и 8.9.1, а this.constructor === Parent возвращает false, если new Child, и true, если new ParentClass. Вы уверены, что правильно обновили и перезагрузили свой webpack-parent-class. - person t.niese; 02.04.2018

Попробуйте мою библиотеку 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

При этом вы можете минимизировать имена своих классов, и все будет в порядке.

person Dariusz Filipiak    schedule 09.05.2019