⬆️ ⬇️

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 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 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 the 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 have no regenerator-runtime on the client and no extra transformation wrappers.



To bring fast-async to 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 }], /* ... */ ] } 


My 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 ES6 + specifications. Then all the extra overhead projector can be removed by including a loose transformation 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 can not break anything from the programmer. 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 turns into 86. Ta-da!



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

Source: https://habr.com/ru/post/425215/



All Articles