Оптимизация фронтенда. Часть 2. Чиним tree-shaking в проекте на webpack
Итак, если специально не чинить, tree-shaking в webpack не работает. Кто не верит, читайте мою предыдущую статью. Если починить очень хочется, то добро пожаловать под кат. Тут есть несколько вариантов, которые я смог подсмотреть, найти придумать.
Тестовый код
Все эксперименты я делал на двух файлах, которые выглядят вот так:
// module.js
class Wheel {
pump(){ console.log('puuuuf');}
}
class Rudder {
turn(){ console.log('turn');}
}
export {Wheel, Rudder}
// index.js
import {Wheel} from './module.js';
class Car {
constructor() {
this.wheel = new Wheel();
}
}
const car = new Car();
car.wheel.pump();
Вариант очевидный, кривой: uglify → babel → uglify
Код
В комментах к предыдущей статье мне предложили очевидное решение. Если все ломается из-за того, что babel вмешивается в сборку и мешает UglifyJS выкидывать лишний код, то почему бы не запустить сначала UglifyJS и не выкинуть лишний код, а уже потом babel?
Было лень что-то специально писать для webpack, поэтому все шаги я выполнял в консоли через npm-скрипты.
Я скомпилировал файлы из примера без babel-loader и попробовал пропустить через UglifyJS. UglifyJS упал. Ну не понимает нового синтаксиса javascript!
Ничего страшного, подумал я, и откопал стюардессу uglify-es. Проблема в том, что babel ничего не знает про минификацию, и на выходе получается неминифицированный код.
Придется после babel еще раз прогонять UglifyJS.
Я думаю, что кто-нибудь, наверное, будет использовать этот подход, но мне самому он не очень нравится.
Вариант разумный: ждать babel 7
Код
Вашим кодом пользуются клиенты? Он в продакшне? Если да, то просто запланируйте обновление на новый babel после его выхода. Катастрофы не случится, ведь ваше приложение работало все это время и без tree-shaking.
Если вы только начинаете разрабатывать проект с нуля и при этом достаточно смелы, то можно уже сейчас подключить тестовую версию. Пока вы работаете над своим приложением, выйдет новая версия babel.
В любом случае babel — опенсорсный, а значит, вы сами сможете помочь ему выйти раньше.
Вариант радикальный: Rollup
Код
Если вы не сильно погрязли в webpack, возможно, еще не поздно соскочить на Rollup. В случае простых приложений и библиотек Rollup — хороший выбор.
Вариант радикальный и с костылями, но иногда работает: typescript
Код
Можно выкинуть babel и компилировать ваше приложение при помощи typescript. Ведь нам обещали, что любой javascript это уже typescript;)
В том месте, где должна стоять директива #__PURE__
typescript оставляет директиву @class
, и, как вы понимаете, ничего не стоит написать загрузчик, который меняет этот самый @class
на то, что нам нужно.
module.exports = function(content) {
return content.replace(/\/\*\* @class \*\//g, '\n /*#__PURE__*/ \n');
};
Думаю, что с flow тоже можно что-то придумать. Кстати, пока писал, подумал, что, возможно, и babel тоже можно как-то помочь уже сейчас. Может кто-то из вас знает ответ?
Вариант радикальный, для фанатов оптимизации: google closure compiler и advanced mode
Код
Это то, что больше всего мне нравится. Чтобы понимать, о чем я говорю, давайте сравним по одной, лучшей версии бандла, получившихся после компиляции для:
!function(n){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}var t={};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,e){return Object.prototype.hasOwnProperty.call(n,e)},e.p="",e(e.s=0)}([function(n,e,t){"use strict";function r(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var o=t(1);(new function n(){r(this,n),this.wheel=new o.a}).wheel.pump()},function(n,e,t){"use strict";function r(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")}function o(n,e){for(var t=0;t
!function(){"use strict";var classCallCheck=function(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")},Wheel=function(){function Wheel(){classCallCheck(this,Wheel)}return Wheel.prototype.pump=function(){console.log("puuuuf")},Wheel}();(new function Car(){classCallCheck(this,Car),this.wheel=new Wheel}).wheel.pump()}();
console.log('puuuuf');
Думаю, дальше объяснять ничего не нужно. Но есть и проблемы. Основных 2:
- Требует Java
- Неудобно настраивать Плохо интегрируется в webpack, который, как вы понимаете, любят далеко не только за tree-shaking.
Вообще, Google closure compiler (GCC) требует анализа всего вашего кода. Это накладывает свои ограничения, но и дает плюшки в виде оптимизаций, недоступных из Webpack по умолчанию.
Запускается GCC вот так:
java -jar node_modules/google-closure-compiler/compiler.jar --compilation_level ADVANCED --language_in=ES6 --js ./src/index.js ./src/module.js > out.dev.js
Первая проблема вполне решаема, тем более что есть хоть и тормозная, но все-таки версия на javascript.
О второй проблеме как раз и будет моя следующая статья в этой серии, в ней я постараюсь интегрировать google closure compiler в сборку.
PS:
Совсем недавно коллега подсказал, что для GCC вышел плагин для webpack, я запустил, проверил, кода стало явно меньше, но вот tree-shaking так и не заработал. Видимо статья про настройку GCC все-таки нужна.
var __wpcc;void 0===__wpcc&&(__wpcc={}),function(c){"use strict";var n;void 0===n&&(n=function(){}),n.p="",n.src=function(c){return n.p+""+c+".out.dev.js"}}.call(this,__wpcc);var __wpcc;void 0===__wpcc&&(__wpcc={}),function(c){"use strict";var n=function(){},o=function(){},t={};n.prototype.pump=function(){window.console.log("puuuuf")},o.prototype.turn=function(){window.console.log("turn")},t.Wheel=n,t.Rudder=o,(new function(){this.wheel=new t.Wheel}).wheel.pump()}.call(this,__wpcc);
Вместо вывода
Возможно, у вас возникнет вопрос:, а как конкретно все это tree-shaking повлияет на мою сборку?
Если честно, прямо сейчас я не могу ответить на этот вопрос со всей ответственностью. Есть мнение, что хуже не будет, будет лучше. Насколько? Вопрос сложный. Если вы попробуете, поделитесь в комментариях. Я же, в свою очередь, обещаю держать вас в курсе событий, и как только появятся заслуживающие внимания мысли на этот счет, с удовольствием ими поделюсь.