Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example Analysis of Webpack4 Tree Shaking Optimization

2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article will explain in detail the example analysis of Webpack4 Tree Shaking optimization for you. The editor thinks it is very practical, so I share it for you as a reference. I hope you can get something after reading this article.

Let's talk about the benefits first.

Before we discuss the technical details, let me summarize the benefits. Different applications will see varying degrees of benefits. The main determining factor is the amount of dead code in the application. If you don't have much dead code, you won't see much of the benefits of tree-shaking. There's a lot of dead code in our project.

The biggest problem in our department is the number of shared libraries. From a simple custom component library, to an enterprise standard component library, to a large amount of code inexplicably crammed into a library. A lot of it is technical debt, but the big problem is that all of our applications are importing all of these libraries, when in fact each application only needs a small portion of them.

Overall, once tree-shaking is implemented, our application will have a reduction rate of 25% to 75%, depending on the application. The average reduction rate is 52%, mainly driven by these huge shared libraries, which are the main code in small applications.

Again, the details will be different, but if you think there may be a lot of unwanted code in the package you pack, this is how to eliminate them.

There is no sample code repository

I'm sorry, guys, but the project I do is the property of the company, so I can't share the code to the GitHub warehouse. However, I will provide simplified code examples in this article to illustrate my point.

So, to cut the crap, let's take a look at how to write the best webpack 4 configuration that implements tree-shaking.

What is dead code?

It's simple: Webpack doesn't see the code you're using. Webpack tracks the import/export statements of the entire application, so if it sees that the import ends up not being used, it will consider it "dead code" and tree-shaking it.

Dead code is not always that clear. Here are some examples of dead code and "live" code, which I hope will make you understand better. Keep in mind that in some cases, Webpack treats something as dead code, even though it's not. See the "side effects" section to learn how to deal with it.

/ / Import and assign to the JavaScript object, then be used in the following code / / this will be regarded as "live" code, will not do tree-shakingimport Stuff from'. / stuff';doSomething (Stuff); / / import and assign to the JavaScript object, but will not be used in the next code / / this will be used as "dead" code, will be tree-shakingimport Stuff from'. / stuff';doSomething () / / Import but does not assign a value to the JavaScript object, nor is it used in the code / / this will be regarded as "dead" code, will be tree-shakingimport'. / stuff';doSomething (); / / import the entire library, but not assigned to the JavaScript object, nor used in the code / / very strange, this is regarded as "live" code, because Webpack handles library import differently from native code import. Import 'my-lib';doSomething ()

Write import in a way that supports tree-shaking

When writing code that supports tree-shaking, the import method is very important. You should avoid importing the entire library into a single JavaScript object. When you do this, you are telling Webpack that you need the entire library, and Webpack will not shake it.

Take the popular library Lodash as an example. Importing the entire library at once is a big mistake, but it is much better to import a single module. Of course, Lodash needs other steps to do tree-shaking, but this is a good starting point.

/ / Import all (does not support tree-shaking) import _ from 'lodash';// named import (support tree-shaking) import {debounce} from' lodash';// direct import specific module (support tree-shaking) import debounce from 'lodash/lib/debounce'

Basic Webpack configuration

The first step in using Webpack for tree-shaking is to write an Webpack configuration file. You can do a lot of custom configuration for your webpack, but if you want to tree-shaking your code, you need the following.

First of all, you must be in production mode. Webpack only tree-shaking when compressing the code, and this only happens in production mode.

Second, the optimization option "usedExports" must be set to true. This means that Webpack will identify the code it believes is not being used and mark it in the initial packaging step.

Finally, you need to use a compressor that supports the removal of dead code. This compressor will recognize how Webpack marks code that it believes is not being used and strips it off. TerserPlugin supports this feature and is recommended.

The following is the basic configuration for Webpack to enable tree-shaking:

/ / Base Webpack Config for Tree Shakingconst config = {mode: 'production', optimization: {usedExports: true, minimizer: [new TerserPlugin ({...})]}}

What are the side effects?

Just because Webpack can't see a piece of code in use doesn't mean it can safely tree-shaking. Some module imports, as long as they are introduced, will have a significant impact on the application. A good example is the global stylesheet, or the JavaScript file that sets the global configuration.

Webpack believes that such files have "side effects". Files with side effects should not be tree-shaking, as this will destroy the entire application. The designers of Webpack are well aware of the risk of packaging code without knowing which files have side effects, so they treat all code as having side effects by default. This protects you from deleting the necessary files, but it means that the default behavior of Webpack is actually not to tree-shaking.

Fortunately, we can configure our project to tell Webpack that it has no side effects and that we can do tree-shaking.

How to tell Webpack that your code has no side effects

Package.json has a special attribute, sideEffects, that exists for this purpose. It has three possible values:

True is the default value, if no other value is specified. This means that all files have side effects, that is, no file can be tree-shaking.

False tells Webpack that no files have side effects and that all files can be tree-shaking.

The third value [...] Is an array of file paths. It tells webpack that except for the files contained in the array, any of your files have no side effects. Therefore, all files except those specified can be safely tree-shaking.

Each project must set the sideEffects property to false or an array of file paths. In my company's work, our basic application and all the shared libraries I mentioned need to be properly configured with the sideEffects flag.

Here are some code examples of the sideEffects flag. Although there are JavaScript comments, this is the JSON code:

/ / all files have side effects, all cannot tree-shaking {"sideEffects": true} / / No files have side effects, all can tree-shaking {"sideEffects": false} / / only these files have side effects, all other files can be tree-shaking, but these files {"sideEffects": [. / src/file1.js ",". / src/file2.js "]}

Global CSS and side effects

First, let's define the global CSS in this context. A global CSS is a stylesheet (which can be CSS, SCSS, and so on) imported directly into an JavaScript file. It is not converted into a CSS module or anything like that. Basically, the import statement looks like this:

/ / Import global CSSimport'. / MyStylesheet.css'

So, if you make the side-effect changes mentioned above, you will immediately notice a thorny problem when you run the webpack build. Any styles imported in the above manner will be removed from the output. This is because such imports are considered dead code by webpack and are deleted.

Fortunately, there is a simple solution to this problem. Webpack uses its modular rule system to control the loading of various types of files. Each rule for each file type has its own sideEffects flag. This overrides all sideEffects flags previously set for files that match the rules.

So, to keep the global CSS file, we just need to set this special sideEffects flag to true, like this:

/ / Webpack configuration related to global CSS side effect rules const config = {module: {rules: [{test: / regex/, use: [loaders], sideEffects: true}]}}

This attribute is found on all module rules of Webpack. Rules that deal with global stylesheets must use it, including but not limited to CSS/SCSS/LESS/, and so on.

What is a module and why is it important

Now we begin to enter the secret realm. On the face of it, compiling the correct module type may seem like a simple step, but as explained in the following sections, this is an area that can lead to many complex problems. This is the part that took me a long time to understand.

First of all, we need to understand the module. Over the years, JavaScript has developed the ability to effectively import / export code in the form of "modules" between files. There are many different JavaScript module standards that have existed for many years, but for the purposes of this article, we will focus on two standards. One is commonjs, and the other is es2015. Here is their code form:

/ / Commonjsconst stuff = require ('. / stuff'); module.exports = stuff;// es2015 import stuff from'. / stuff';export default stuff

By default, Babel assumes that we write code using the es2015 module and convert the JavaScript code to use the commonjs module. This is done for broad compatibility with server-side JavaScript libraries, which are usually built on top of NodeJS (NodeJS only supports commonjs modules). However, Webpack does not support the use of commonjs modules to complete tree-shaking.

Now, there are plug-ins (such as common-shake-plugin) that claim to give Webpack the ability to tree-shaking commonjs modules, but in my experience, these plug-ins either don't work or have little impact on tree-shaking when running on es2015 modules. I do not recommend these plug-ins.

Therefore, in order to tree-shaking, we need to compile the code into the es2015 module.

Es2015 module Babel configuration

As far as I know, Babel does not support compiling other module systems into es2015 modules. However, if you are a front-end developer, you may already be using the es2015 module to write code, because this is the fully recommended approach.

So, in order for our compiled code to use the es2015 module, all we need to do is tell babel to leave them alone. To do this, we just need to add the following to our babel.config.js (in this article, you'll see that I prefer JavaScript configuration to JSON configuration):

/ / basic Babel configuration of es2015 module const config = {presets: [['[@ babel/preset-env] (http://twitter.com/babel/preset-env)', {modules: false}]]}

Setting modules to false tells babel not to compile the module code. This will allow Babel to keep our existing es2015 import/export statements.

* * highlight: * * all code that requires tree-shaking must be compiled in this way. Therefore, if you have libraries to import, you must compile them into es2015 modules for tree-shaking. If they are compiled to commonjs, they will not be able to do tree-shaking and will be packaged into your application. Many libraries support partial import, and lodash is a good example, which itself is a commonjs module, but it has a lodash-es version that uses the es2015 module.

In addition, if you use internal libraries in your application, you must also compile using the es2015 module. In order to reduce the size of the application package, all of these internal libraries must be modified to compile in this way.

Sorry, Jest is on strike.

Other testing frameworks are similar, using Jest.

In any case, if you get to this point, you will find that the Jest test is starting to fail. You will, as I did at that time, see all kinds of strange errors in the log, a batch of panic. Don't panic. I'll take you one step at a time.

The reason for this result is simple: NodeJS. Jest is developed based on NodeJS, while NodeJS does not support es2015 modules. There are some ways to configure Node for this, but it doesn't work on jest. So, we're stuck here: Webpack needs es2015 for tree shaking, but Jest cannot perform tests on these modules.

That's why I said I entered the "secret realm" of the module system. This is the part of the whole process that takes me the most time to figure out. It is recommended that you read this section and the following sections carefully, because I will give you a solution.

The solution has two main parts. The first part focuses on the code of the project itself, that is, the code that runs the test. This part is easier. The second part focuses on the library code, that is, code from other projects that is compiled into es2015 modules and introduced into the current project. This part is complicated.

Resolve the project local Jest code

Babel has a very useful feature for our problem: the environment option. It can be run in different environments through configuration. Here, we need the es2015 module for the development and production environment, and the commonjs module for the test environment. Fortunately, Babel is very easy to configure:

/ / Sub-environment configuration Babel const config = {env: {development: {presets: [['@ babel/preset-env] (http://twitter.com/babel/preset-env)', {modules: false}]]}, production: {presets: [['@ babel/preset-env] (http://twitter.com/babel/preset-env)', {modules: false}]} Test: {presets: [['[@ babel/preset-env] (http://twitter.com/babel/preset-env)', {modules: 'commonjs'}]], plugins: [' transform-es2015-modules-commonjs' / / Not sure this is required, but I had added it anyway]}

Once set up, all the project native code can be compiled normally, and the Jest test can be run. However, third-party library code that uses the es2015 module still doesn't work.

Resolve the library code in Jest

The reason for the error in running the library code is obvious, just take a look at the node_modules directory. The library code here uses the es2015 module syntax for tree-shaking. These libraries have been compiled in this way, so when Jest tries to read the code in a unit test, it explodes. Have you noticed that we have asked Babel to enable the commonjs module in the test environment? why doesn't it work for these libraries? This is because Jest (especially babel-jest) ignores any code from node_modules by default when compiling code before running the test.

This is actually a good thing. If Jest needs to recompile all libraries, it will greatly increase the test processing time. However, while we don't want it to recompile all the code, we want it to recompile libraries that use es2015 modules so that they can be used in unit tests.

Fortunately, Jest provides us with a solution in its configuration. I want to say that this part really made me think about it for a long time, and I didn't feel the need to make it so complicated, but it was the only solution I could think of.

Configure Jest to recompile library code

/ / Jest configuration of recompiled library code const path = require ('path'); const librariesToRecompile = [' Library1', 'Library2'] .join (' |'); const config = {transformIgnorePatterns: [`[\\ /] node_ modules [\\ /] (?! (${librariesToRecompile})). * $`], transform: {'^. +\ .jsx? $': path.resolve (_ _ dirname, 'transformer.js')}}

The above configuration is required for Jest to recompile your library. There are two main parts, which I will explain one by one.

TransformIgnorePatterns is a feature of the Jest configuration, which is an array of regular strings. Any code that matches these regular expressions will not be recompiled by babel-jest. The default is a string "node_modules". This is why Jest does not recompile any library code.

When we provide a custom configuration, we tell Jest how to ignore the code when recompiling. That's why the abnormal regular expression you just saw has a negative antecedent assertion in it to match all code except the library. In other words, we tell Jest to ignore all code in node_modules except for the specified library.

This once again proves that the JavaScript configuration is better than the JSON configuration, because I can easily insert an array of library names into the regular expression through string manipulation.

The second is the transform configuration, which points to a custom babel-jest converter. I'm not 100% sure this is necessary, but I added it anyway. Set it to load our Babel configuration when all code is recompiled.

/ / Babel-Jest converter const babelJest = require ('babel-jest'); const path = require (' path'); const cwd = process.cwd (); const babelConfig = require (path.resolve (cwd, 'babel.config')); module.exports = babelJest.createTransformer (babelConfig)

After all this is configured, you should be able to run again when you are testing the code. Keep in mind that any es2015 module that uses the library needs to be configured like this, otherwise the test code won't work.

Npm/Yarn Link is the devil.

Next comes another pain point: the link library. The process of using npm/yarn links is to create a symbolic link to the local project directory. It turns out that Babel throws a lot of errors when recompiling libraries linked in this way. One of the reasons it took me so long to figure out about Jest is that I've been linking to my library in this way and made a bunch of mistakes.

The solution is: don't use npm/yarn link. With a tool like "yalc", it can connect to local projects while simulating the normal npm installation process. It not only does not have the problem of Babel recompilation, but also can better handle transitive dependencies.

Optimization for specific libraries.

If you complete all the above steps, your application will basically achieve a relatively robust tree shaking. However, to further reduce the package size, there are a few things you can do. I will list some optimization methods for specific libraries, but this is definitely not all. In particular, it can inspire us to do something cooler.

MomentJS is a famous large volume library. Fortunately, it can eliminate multilingual packs to reduce the size. In the following code example, I exclude all the multilingual packs of momentjs, leaving only the basic parts, which is significantly smaller.

/ / remove multilingual pack const {IgnorePlugin} from 'webpack';const config with IgnorePlugin = {plugins: [new IgnorePlugin (/ ^\.\ / locale$/, / moment/)]}

Moment-Timezone is MomentJS's old cousin and a big guy. Its volume is basically caused by a very large JSON file with time zone information. I have found that as long as I keep the year data of this century, I can reduce the size by 90%. This situation requires the use of a special Webpack plug-in.

/ / MomentTimezone Webpack Pluginconst MomentTimezoneDataPlugin = require ('moment-timezone-data-webpack-plugin'); const config = {plugins: [new MomentTimezoneDataPlugin ({startYear: 2018, endYear: 2100})]}

Lodash is another big guy that causes file packages to swell. Fortunately, there is an alternative package, Lodash-es, which is compiled into an es2015 module with the sideEffects flag. Replacing Lodash with it can further reduce the size of the package.

In addition, Lodash-es,react-bootstrap and other libraries can be slimmed down with the help of Babel transform imports plug-ins. The plug-in reads the import statement from the library's index.js file and points it to a specific file in the library. This makes it easier for webpack to tree shaking the library when parsing the module tree. The following example shows how it works.

/ / Babel Transform Imports// Babel configconst config = {plugins: [['transform-imports', {' lodash-es': {transform: 'lodash/$ {member}', preventFullImport: true}, 'react-bootstrap': {transform:' react-bootstrap/es/$ {member}', / / The es folder contains es2015 module versions of the files preventFullImport: true}}]]} / / these libraries no longer support full imports, otherwise there will be an error import _ from 'lodash-es';// named imports still support import {debounce} from' loash-es';//, but these named imports will be compiled like this by babel / / import debounce from 'lodash-es/debounce' This is the end of this article on "sample Analysis of Webpack4 Tree Shaking Optimization". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, please share it for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report