4 tips for optimizing a webpack application

Hello!

During my work with the webpack, I have a couple of interesting tips that will help you to prepare a perfectly optimized application. Let's get started!

The cat fronder looks at the webpack and says 'Belissimo'



1. Use fast-async instead of regenerator-runtime


Usually, developers use @ babel / preset-env to convert all modern syntax to ES5.

With this preset, the asynchronous function transformation mappings look like this:
Initial asynchronous function -> Generator -> Function using regenerator-runtime

Example
1. Initial asynchronous function

const test = async () => {
  await fetch('/test-api/', { method: 'GET' });
}

2. Generator

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. Function using the 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);
  };
}();


With fast-async, the pipeline is simplified to:
Initial asynchronous function -> Function using promises

Example
1. Initial asynchronous function

const test = async () => {
  await fetch('/test-api/', { method: 'GET' });
}

2. Function using promises

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);
  });
};


Due to this, now we do not have a regenerator-runtime on the client and no extra transformation wrappers.

To bring fast-async into your project, you need:

1. Install it

npm i fast-async

2. Update Babel's config

// .babelrc.js
module.exports = {
  "presets": [
    ["@babel/preset-env", {
      /* ... */
      "exclude": ["transform-async-to-generator", "transform-regenerator"]
    }]
  ],
  /* ... */
  "plugins": [
    ["module:fast-async", { "spec": true }],
    /* ... */
  ]
}

I have this optimization reduced the size of js files by 3.2%. Trifle, but nice :)

2. Use loose transformation


Without a special setting, @ babel / preset-env attempts to generate a code as close as possible to the specification.

But, most likely, your code is not so bad and does not use all possible extreme cases of the ES6 + specification. Then you can remove all the extra overhead by including loose transformations for the preset env:

// .babelrc.js
module.exports = {
  "presets": [
    ["@babel/preset-env", {
      /* ... */
      "loose": true,
    }]
  ],
  /* ... */
}

An example of how this works can be found here .

In my project, this reduced the size of the bundle by 3.8%.

3. Configure the minification of js and css by hands


The default settings for minifiers contain only those transformations that the programmer cannot break. But we love to give ourselves problems?
Try reading the settings of the minifiers js and your minifiers css (I use cssnano ).

Having studied the docks, I made this config:

// 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 },
        },
      }),
    ],
  },
};
/* ... */

As a result, the size of js files decreased by 1.5%, and css - by 2%.

Maybe you can do better?

UPD 11.01.2019 : UglifyJsPlugin is outdated, webpack now uses TerserWebpackPlugin . Use it.

4. Use null-loader to remove unwanted dependencies.


The gsap developers have a great library for creating animations. But due to the fact that it originates from 2008, there are still some peculiarities in it.

Namely , this one . Thanks to it, TweenMax draws 5 plug-ins and easePack, which are completely optional.

I noticed three extra plugins in my home and sawed them out using the 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',
      },
    ]
  },
};
/* ... */

And 106 kb turn into 86. Ta-da!

Null-loader can still be used to remove unnecessary polyfill, which the authors of the libraries carefully planted to us.

Also popular now: