[Из песочницы] 4 совета для оптимизации webpack-приложения
Всем привет!
За время моей работы с вебпаком у меня накопилась пара интересных советов, которые помогут вам приготовить отлично оптимизированное приложение. Приступим!
1. Используйте fast-async вместо regenerator-runtime
Обычно, разработчики используют @babel/preset-env, чтобы преобразовывать весь современный синтаксис в ES5.
С этим пресетом пайплайн преобразований асинхронных функций выглядит так:
Исходная асинхронная функция → Генератор → Функция, использующая regenerator-runtime
const test = async () => {
await fetch('/test-api/', { method: 'GET' });
}
2. Генератор
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
const test = (() => {
var _ref = _asyncToGenerator(function* () {
yield fetch('/test-api/', { method: 'GET' });
});
return function test() {
return _ref.apply(this, arguments);
};
})();
3. Функция, использующая regenerator-runtime
'use strict';
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
var test = function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return fetch('/test-api/', { method: 'GET' });
case 2:
case 'end':
return _context.stop();
}
}
}, _callee, undefined);
}));
return function test() {
return _ref.apply(this, arguments);
};
}();
С fast-async пайплайн упрощается до:
Исходная асинхронная функция → Функция, использующая промисы
const test = async () => {
await fetch('/test-api/', { method: 'GET' });
}
2. Функция, использующая промисы
var test = function test() {
return new Promise(function ($return, $error) {
return Promise.resolve(fetch('/test-api/', {
method: 'GET'
})).then(function ($await_1) {
try {
return $return();
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
});
};
Благодаря этому, теперь у нас нет regenerator-runtime на клиенте и лишних оберток от трансформаций.
Чтобы подвести fast-async в свой проект, надо:
1. Установить его
npm i fast-async
2. Обновить конфиг бабеля
// .babelrc.js
module.exports = {
"presets": [
["@babel/preset-env", {
/* ... */
"exclude": ["transform-async-to-generator", "transform-regenerator"]
}]
],
/* ... */
"plugins": [
["module:fast-async", { "spec": true }],
/* ... */
]
}
У меня эта оптимизация уменьшила размер js файлов на 3.2%. Мелочь, а приятно :)
2. Используйте loose трансформации
Без специальной настройки @babel/preset-env пытается сгенерировать как можно более близкий к спецификации код.
Но, скорее всего, ваш код не настолько плох и не использует все возможные крайние случаи ES6+ спецификации. Тогда весь лишний оверхед можно убрать, включив loose трансформации для preset-env:
// .babelrc.js
module.exports = {
"presets": [
["@babel/preset-env", {
/* ... */
"loose": true,
}]
],
/* ... */
}
Пример того, как это работает, можно найти тут.
В моем проекте это уменьшило размер бандла на 3.8%.
3. Настройте минификацию js и css руками
Дефолтные настройки для минификаторов содержат только те трансформации, которые не смогут ничего сломать у программиста. Но мы ведь любим доставлять себе проблемы?
Попробуйте почитать настройки минификатора js и своего минификатора css (я использую cssnano).
Изучив доки, я сделал такой конфиг:
// webpack.config.js
const webpackConfig = {
/* ... */
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
unsafe: true,
inline: true,
passes: 2,
keep_fargs: false,
},
output: {
beautify: false,
},
mangle: true,
},
}),
new OptimizeCSSPlugin({
cssProcessorOptions: {
"preset": "advanced",
"safe": true,
"map": { "inline": false },
},
}),
],
},
};
/* ... */
В результате размер js файлов уменьшился на 1.5%, а css — на 2%.
Может, у вас получится лучше?
4. Используйте null-loader для удаления ненужных зависимостей
У разработчиков gsap получилась отличная библиотека для создания анимаций. Но из-за того, что она берет свое начало еще из 2008 года, в ней остались некоторые особенности.
А именно вот эта. Благодаря ней TweenMax тянет за собой 5 плагинов и easePack, которые юзать совершенно необязательно.
У себя я заметил три лишних плагина и выпилил их с помощью null-loader:
// webpack.config.js
const ignoredGSAPFiles = ['BezierPlugin', 'DirectionalRotationPlugin', 'RoundPropsPlugin'];
const webpackConfig = {
/* ... */
module: {
rules: [
/* ... */
{
test: /\.js$/,
include: ignoredGSAPFiles.map(fileName => resolve('node_modules/gsap/' + fileName)),
loader: 'null-loader',
},
]
},
};
/* ... */
И 106 кб превращаются в 86. Та-да!
Null-loader еще можно использовать для удаления ненужных полифиллов, которые авторы библиотек заботливо нам подложили.