- PVSM.RU - https://www.pvsm.ru -

Мы относимся к технологиям, которые используем, как к покупкам на Яндекс маркете. Смотрим на спецификацию, читаем отзывы и, если проект получил много звездочек на гитхабе, проходит по спецификации, и к тому же внедрение стоит недорого, мы его покупаем устанавливаем. Такой подход иногда очень сильно бьет по голове ручкой от граблей, и тогда все-таки приходится разбираться, что происходит.
В статье [1] одного из авторов rollup [2] рассмотрены две оптимизации, одна называется dead code elimination, а вторая tree-shaking. Автор показывает, что у tree-shaking намного больше возможностей по сжатию кода. И в доказательство приводит несколько соображений о рецептах пирога и разбившихся яйцах. Ох уж эти метафоры!
Эту идею (про tree-shaking, не про пирог и яйца) подхватила команда разработчиков webpack и с версии 2.0 стала официально поддерживать.
Я не стал бы писать, если бы на реальных проектах технология приносила хоть какой-то результат. На практике размеры итоговой сборки либо не уменьшаются вовсе, либо уменьшаются на размер статистической погрешности.
Некоторые, конечно, догадываются о подвохе и даже пишут статьи на Хабр [3]. Но любителей порассуждать о преимуществах tree-shaking над dead code illumination в webpack вокруг меньше не становится, по крайней мере, среди посетителей конференций и среди моих коллег.
Идея проста, как танк [4].
...
/* unused harmony export square */
function square(x) { return x * x;}
...
Допустим, у нас все как в документации. Два файла index.js и module.js
// index.js
import {cube} from './module'
console.log(cube(x))
// module.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
Если мы сейчас запустим webpack в режиме оптимизации и минимизации, то все заработает как и ожидалось. (код [5])
webpack --optimize-minimize index.js out.js
Но если только в файл с модулями добавится любой, даже самый маленький класс с export при наличии babel-loader, то пиши пропало. Класс попадет в итоговую сборку в виде функции, которую выплюнул babel. (код [6])
//module.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
export class MyClass {
print(){
console.log('find me');
}
}
Все дело в том, что UglifyJS боится выкинуть что-то лишнее. Оно и понятно: пусть лучше на пару сотен байт больше, только бы не сломалось.
И вот, представьте себе, что UglifyJS получает на вход следующий код:
/* unused harmony export MyClass */
var MyClass = function () {
function MyClass() {
babelHelpers.classCallCheck(this, MyClass);
}
MyClass.prototype.turn = function print() {
console.log('find me');
};
return MyClass;
}();
MyClass после компиляции babel как-то выбрался за осознания себя как класса. И вообще UglifyJS мало что знает про то, как команда babel видит реализацию классов на ES5. Вот и пасует, оставляя это неведомое никем не используемое безобразие в вашей итоговой сборке.
На это даже есть баг в репозитории webpack [7], и ребята обещают починить все в 4й версии [8].
Rollup, кстати, не так давно тоже работал только на примерах с математикой, но в последних версиях ребята починили баг. (сломанный пример [9], работающий пример [10]).
Вот так, купив webpack в том числе и за tree-shaking, я получил околонулевую выгоду в этом направлении. И слово tree-shaking теперь вызывает у меня нервный смех, икоту и неконтролируемый сарказм.
Для начала бросьте webpack и пользуйтесь моим принципиально новым сборщиком [11], который лишен как этих недостатков, так и многих других. Помимо классической сборки, он варит крафтовое пиво и готовит бургеры на чиабате.
Извините, не удержался.
Если серьезно, есть очень простой способ починить ситуацию:
нужно, чтобы webpack добавлял специальную директиву /*#__PURE__*/, которая говорила бы UglifyJS, что вот это неведомое чудище в виде функции вполне себе можно выпиливать.
Ох уж эти костыли.
А выглядит это как-то так:
/* unused harmony export MyClass */
var MyClass = /*#__PURE__*/ function () {
function MyClass() {
babelHelpers.classCallCheck(this, MyClass);
}
MyClass.prototype.turn = function print() {
console.log('find me');
};
return MyClass;
}();
Еще пара итераций и мы изобретем статическую типизацию ;)
Работающий пример [12]
Кстати, новая версия babel 7 уже делает это. К сожалению, она пока еще в бете, что как бы намекает на невозможность использования прямо сейчас. Но если вы смелы и решительны, можно попробовать обновиться.
Забегая вперед, скажу, что решений, которые работают прямо сейчас, несколько. О них я расскажу в следующей статье. Возможно, кому-то они сэкономят нервы и время. А нам пора перейти к выводам.
P.S. Напишите в комментариях, если тоже попадались на удочку маркетологов и выбирали модную технологию вместо решения проблем.
Автор: Алексей Золотых
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/268763
Ссылки в тексте:
[1] статье: https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80
[2] rollup: https://rollupjs.org/
[3] статьи на Хабр: https://habrahabr.ru/company/Voximplant/blog/330148/
[4] танк: https://webpack.js.org/guides/tree-shaking/
[5] код: https://github.com/zolotyh/treeshaking-hell/tree/webpack-doc
[6] код: https://github.com/zolotyh/treeshaking-hell/tree/old-babel
[7] webpack: https://github.com/webpack/webpack/issues/2867
[8] 4й версии: https://github.com/webpack/webpack/milestone/15
[9] сломанный пример: https://github.com/zolotyh/treeshaking-hell/tree/old-rollup
[10] работающий пример: https://github.com/zolotyh/treeshaking-hell/tree/rollup-old-babel
[11] принципиально новым сборщиком: https://www.youtube.com/watch?v=f4uXBpP_xxY
[12] Работающий пример: https://github.com/zolotyh/treeshaking-hell/tree/new-babel
[13] Источник: https://habrahabr.ru/post/342686/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.