The story about a few imports

栏目: IT技术 · 发布时间: 4年前

内容简介:We all care about the performance of web applications that we build. We try to keep production bundles small and loading times low. That's a good thing! It definitely is for the user, who gets a great experience. But is it good for the developer? When we w

We all care about the performance of web applications that we build. We try to keep production bundles small and loading times low. That's a good thing! It definitely is for the user, who gets a great experience. But is it good for the developer? When we want the app to work fast, does it mean that creating it has to be slow? Can we still use external libraries and packages from NPM? Or do we have to write everything from scratch, counting each line of code?

Making a webpage fast may seem like a sacrifice from the developer's point of view. How could you keep a JavaScript bundle below 50 kB when almost any popular library or framework takes half of that budget or even exceeds it? There is a way to find a compromise and keep both performance and easiness given by the ecosystem. Everything we need is the right set of tools.

The story

Let's say we're writing dev.to news listing. For each article fetched from API we're supposed to display a title, beginning of the content, and a list of tags. Tags are returned from the API as a string, using a comma as a separator, so there are some transformations needed to parse and render them.

This code is fairly simple, but it may be even more readable when written using predefined functions, like those form lodash library. For many developers lodash is the very first choice when it comes to finding a comprehensive set of useful functions speeding-up the development.

import _ from "lodash/fp";

const renderTags = _.pipe(
  _.split(","),
  _.map(_.trim),
  _.reject(_.isEmpty),
  _.map(tag => <li className={styles.tag}>{tag}</li>)
);

That looks quite neat! But there is a problem - bundle size increased from 12.5 kB to almost 94 kB :scream: Even if code quality could be considered as significantly better, such change would be unacceptable because it simply harms the user.

When we dig into the production bundle in Webpack Stats Explorer , we can see a few modules were added, but there is one that should attract our attention - lodash.min.js . It takes almost 70 kB, the majority of our bundle!

The story about a few imports

Click on the screenshot to launch an interactive version of Webpack Stats Explorer

It turns out that by default, no matter how many functions we actually use from lodash , the whole library is sent down to the user. How to fix it? Named imports are the answer. Instead of importing the whole _ object, we could specify each function we use by name. In the process called "tree shaking", Webpack will extract only the code that we need.

The story about a few imports

There is some issue with this solution, though. lodash isn't really tree-shaking-friendly package, so by default switching to named imports changes nothing. To make it work as expected, we have to import each function from separate file.

import { pipe, map } from 'lodash/fp';

becomes

import pipe from 'lodash/fp/pipe';
import map from 'lodash/fp/map';

But this is a huge sacrifice, isn't it? The code doesn't look concise anymore and we start relying on the internal structure of lodash package instead of public API. Fortunately, instead of altering the code manually, it's enough to add a dedicated Babel plugin - babel-plugin-lodash and everything just works. We can keep using the named imports syntax.

{
  "presets": [
    "@babel/preset-env",
    "babel-preset-preact"
  ],
  "plugins": ["babel-plugin-lodash"]
}

The plugin does the trick - bundle size goes down by 34 kB. Webpack Stats Explorer shows , that instead of one big file, the bundle contains a lot of small modules. And those are the only ones we actually need.

The story about a few imports

So the bundle is now 57 kB. Is that good enough? Comparing to 12,5 kB we had before - not necessarily. There is another tool that may help - lodash-webpack-plugin .

const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
const WebpackEnhancedStatsPlugin = require('webpack-enhanced-stats-plugin');

module.exports = {
  plugins: [
    new LodashModuleReplacementPlugin({ currying: true }),
    new WebpackEnhancedStatsPlugin({
      filename: 'stats.json'
    }),

Without any changes to the application code, it shrinks the bundle by another 23 kB. What kind of sorcery is this?! The whole trick is based on substituting some of the internal library functions with simpler alternatives or even no-ops. There are plenty of options available but as our code is fairly simple, we need nothing more than currying.

The story about a few imports

After all those exertions, we managed to shrink the bundle to 34 kB - that's not bad. But it was 12,5 kB before. Is tripling the bundle size justified by better code readability and extensibility? I doubt! Fortunately, we can do better than that. lodash isn't the only library containing utility functions available on NPM and definitely not the tiniest one. nanoutils may be a very decent drop-in replacement. This library helped me a lot in my daily job and I can recommend it to all searching for a utility package that doesn't damage the user experience.

The story about a few imports

When we simply remove lodash with all build-time plugins and use raw nanoutils , the package shrinks by 4 kB. That's already a success, but not so impressive! We can do more than that. Similarly to lodash , by default nanoutils isn't tree-shakeable so we can shrink the bundle even more with a Babel plugin .

{
  "presets": [
    "@babel/preset-env",
    "babel-preset-preact"
  ],
  "plugins": [
    ["babel-plugin-transform-imports", {
      "nanoutils": {
        "transform": "nanoutils/lib/${member}",
        "preventFullImport": true
      }
    }]
  ]
}

Finally, the bundle has a size no bigger than 13,26 kB. It's only 700 B increase when comparing to the very first version which doesn't use any library. That looks more like a cost we can afford to increase code quality and not feel guilty about breaking user experience.

The story about a few imports

Conclusions

What lesson does the story tell to us? It is possible to have both performant (at least in terms of bundle size) and elegant code. There are a couple of things I wish you to remember.

Tree-shaking and named imports

Tree-shaking is one of the greatest ideas since the sliced bread, at least in the world of web bundlers. It's supported by Webpack , but also Rollup and Parcel . To take advantage of tree-shaking, you should use named imports in favor of default one. Unless API of the library requires otherwise (ex. because it uses this under the hood), always write

import { foo } from 'lib';

foo();

instead of

import obj from 'lib';

obj.foo();

Make this syntax your new default.

Build and analytic tools

A vast amount of modern libraries published to NPM is tree-shaking friendly. Unfortunately, for many of them, it isn't enabled by default. Use tools like Webpack Bundle Analyzer and Webpack Stats Explorer to dig deep inside your production bundle and get to know what's exactly in it. If you find modules or pieces of code you suspect you don't need, try to use plugins like babel-plugin-transform-imports to get rid of them.

Drop-in library replacements

For many packages, it's easy to find significantly smaller counterparts with similar functionality and API surface. It's very often the case for utility libraries, but also view frameworks. Think of Preact created to substitute React . To estimate the size of the package before adding it to your project, you can use Bundlephobia . For some libraries, the bottom section provides a shortlist of alternatives, which is also super helpful!

That's it! I hope you enjoyed the article and will have an opportunity to apply the described ideas to real web applications. Feel free to reach me in the comments if you have any questions!

Disclaimer

I am a creator of Webpack Stats Explorer - a free-to-use, open-source tool for developers who care about the performance of their web applications. Recently I also made some minor contributions to nanoutils .


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

琢石成器

琢石成器

罗云彬 / 电子工业出版社 / 2009-6 / 89.00元

Windows环境下32位汇编语言是一种全新的编程语言。它使用与C++语言相同的API接口,不仅可以开发出大型的软件,而且是了解操作系统运行细节的最佳方式。 本书从编写应用程序的角度,从“Hello,World!”这个简单的例子开始到编写多线程、注册表和网络通信等复杂的程序,通过70多个实例逐步深入Win32汇编语言编程的方方面面。 本书作者罗云彬拥有十余年汇编语言编程经验,是汇编编程......一起来看看 《琢石成器》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换