Всем привет!
За время моей работы с вебпаком у меня накопилась пара интересных советов, которые помогут вам приготовить отлично оптимизированное приложение. Приступим!

Обычно, разработчики используют @babel/preset-env, чтобы преобразовывать весь современный синтаксис в ES5.
С этим пресетом пайплайн преобразований асинхронных функций выглядит так:
Исходная асинхронная функция -> Генератор -> Функция, использующая regenerator-runtime
С fast-async пайплайн упрощается до:
Исходная асинхронная функция -> Функция, использующая промисы
Благодаря этому, теперь у нас нет regenerator-runtime на клиенте и лишних оберток от трансформаций.
Чтобы подвести fast-async в свой проект, надо:
1. Установить его
2. Обновить конфиг бабеля
У меня эта оптимизация уменьшила размер js файлов на 3.2%. Мелочь, а приятно :)
Без специальной настройки @babel/preset-env пытается сгенерировать как можно более близкий к спецификации код.
Но, скорее всего, ваш код не настолько плох и не использует все возможные крайние случаи ES6+ спецификации. Тогда весь лишний оверхед можно убрать, включив loose трансформации для preset-env:
Пример того, как это работает, можно найти тут.
В моем проекте это уменьшило размер бандла на 3.8%.
Дефолтные настройки для минификаторов содержат только те трансформации, которые не смогут ничего сломать у программиста. Но мы ведь любим доставлять себе проблемы?
Попробуйте почитать настройки минификатора js и своего минификатора css (я использую cssnano).
Изучив доки, я сделал такой конфиг:
В результате размер js файлов уменьшился на 1.5%, а css — на 2%.
Может, у вас получится лучше?
UPD 11.01.2019: UglifyJsPlugin устарел, webpack сейчас использует TerserWebpackPlugin. Используйте его.
У разработчиков gsap получилась отличная библиотека для создания анимаций. Но из-за того, что она берет свое начало еще из 2008 года, в ней остались некоторые особенности.
А именно вот эта. Благодаря ней TweenMax тянет за собой 5 плагинов и easePack, которые юзать совершенно необязательно.
У себя я заметил три лишних плагина и выпилил их с помощью null-loader:
И 106 кб превращаются в 86. Та-да!
Null-loader еще можно использовать для удаления ненужных полифиллов, которые авторы библиотек заботливо нам подложили.
За время моей работы с вебпаком у меня накопилась пара интересных советов, которые помогут вам приготовить отлично оптимизированное приложение. Приступим!

1. Используйте fast-async вместо regenerator-runtime
Обычно, разработчики используют @babel/preset-env, чтобы преобразовывать весь современный синтаксис в ES5.
С этим пресетом пайплайн преобразований асинхронных функций выглядит так:
Исходная асинхронная функция -> Генератор -> Функция, использующая regenerator-runtime
Пример
1. Исходная асинхронная функция
2. Генератор
3. Функция, использующая 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 пайплайн упрощается до:
Исходная асинхронная функция -> Функция, использующая промисы
Пример
1. Исходная асинхронная функция
2. Функция, использующая промисы
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%.
Может, у вас получится лучше?
UPD 11.01.2019: UglifyJsPlugin устарел, webpack сейчас использует TerserWebpackPlugin. Используйте его.
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 еще можно использовать для удаления ненужных полифиллов, которые авторы библиотек заботливо нам подложили.
