[Из песочницы] 4 совета для оптимизации webpack-приложения

Всем привет!

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

Кот-фронтендер смотрит на webpack и говорит 'Белиссимо'


1. Используйте fast-async вместо regenerator-runtime


Обычно, разработчики используют @babel/preset-env, чтобы преобразовывать весь современный синтаксис в ES5.

С этим пресетом пайплайн преобразований асинхронных функций выглядит так:
Исходная асинхронная функция → Генератор → Функция, использующая regenerator-runtime

Пример
1. Исходная асинхронная функция
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. Исходная асинхронная функция
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 еще можно использовать для удаления ненужных полифиллов, которые авторы библиотек заботливо нам подложили.

© Habrahabr.ru