The project share 1000 words, it takes 5 minutes to read, the example part 3000 words, it takes 15 minutes to read, it takes 1 hour to try the example, if there is any mistake, please correct.
Optimize the first play in Taobao
scenario
This project is a large medium platform system of Taoshi user growth team. It is a single-page application, covering many business functions and using many lazy loading page components to improve performance. The first screen time is about 1s, and the experience is good. However, there are many large project files, resulting in a long build and release time and a large memory footprint. My task is to optimize the problems related to this as much as possible.
Train of thought
- First of all, it’s not hard to see that the problem is not the user experience, but the development experience. Too long packaging time reduces the development efficiency.
- Looking at the project, the project uses many lazy components to improve single-page performance, but these components reference many duplicate modules at the same time, resulting in volume bloat.
- Next, it is necessary to determine the specific types of repeated modules and whether the impact is significant. Therefore, it is necessary to observe which modules are contained in the packaged chunk and the degree of repetition. We can set the output of the Webpack compiler to show specific information about the chunk:
const compiler = webpack(webpackConfig); // Get the compiler from the Webpack configuration information, and then configure its output
compiler.hooks.done.tap('done'.(stats) = > {
console.log(
stats.toString({
colors: true.chunks: true.// Set this to true
assets: true.children: false.modules: false,})); }Copy the code
Of course, simple projects can have the same effect by adding webpack –display-chunks to the end of the package command.
- Find the modules that are repeated in multiple chunks in the chunk information and record their paths, for example:
[. / SRC/components/XXX. JSX] 4.52 KiB {55} {60} {66} {73} {87} {96} {113} {119} {127} {129} {133} [./node_modules/base/yyy.js] 205 bytes {50} {54} {64} {70} {73} {74} {75} {80} {82} {83} {87} {92} {97} {104} {109} {111} {112} {113} {115} {117} {120} {127} {128} {129} {130} {132} {138} {150} {151} [./node_modules/base/zzz.js] 205 bytes {50} {54} {64} {70} {73} {74} {75} {80} {82} {83} {87} {92} {97} {104} {109} {111} {112} {113} {115} {117} {120} {127} {128} {129} {130} {132} {138} {150} {151}...Copy the code
Inside each curly bracket is the ID of a chunk, and these three modules are repeatedly packaged into many chunks.
-
Code division strategy, focus on configuration optimization. SplitChunks, extract the repeat module, to the first screen performance, front page need package cannot too big, if too big need to break up. (Project code split policy is not easy to post, can only use the following example to replace)
-
Finally, verify the packaging effect and adjust the strategy until it is optimal.
results
From my Taobao front end team weekly:
The package compilation optimization of the project has achieved effective results. The total volume of the project has been reduced by 6.4m compared with the original (the original volume was 42.2m, but now it is 35.8m), the compilation time has been shortened by 60%, the release time has been shortened by 20%, the number of files has been reduced by 30, and there is no memory overflow problem when packaging.
There is only one technique used: SplitChunksPlugin for Webpack. SplitChunksPlugin has been around for two years, and the community has accumulated a lot of data, but I still feel the need to add the following example tutorial for two reasons:
- Some properties of SplitChunksPlugin are not well explained by the Chinese community, and the translation of the tutorial on the official website is not easy to understand.
- The demos I could find were pretty basic, usually just demonstrating the use of a property, and I needed a progressive example that would allow the various configurations to be considered together so that they could be mapped to a real project.
The following code graphic warning, Webpack knowledge system complete old drivers can directly skip, small white suggestions carefully read.
The central idea of packaging optimization
Before you concentrate on something, you should first find the big picture, so that you won’t get lost in complex projects. Front-end optimization does two things:
- Optimize the user experience
- Reduces the first screen load time
- Improve the fluency of various interactions, such as form validation and page switching
- Optimize the development experience
- Reduce build time
- Automation to complete some repetitive work, the liberation of productivity, scaffolding is a representative product
Webpack provides the primary optimization tool for modular projects:
- Extract common code
- Load on demand (lazy load)
Therefore, we are to use Webpack two major optimization means to complete the above front-end optimization of the two things. When we are faced with a huge project, we can jump out and have a look.
SplitChunksPlugin private kitchens
SplitChunksPlugin Imports cacheGroups to group modules. Each cache group allocates matched modules to chunks based on rules. The result of each cache group can be a single chunk or multiple chunks.
Webpack has made some general optimization. Before manually configuring SplitChunksPlugin for optimization, we need to understand what optimization webPack does by default and how to do it, and then adjust it according to our own needs. SplitChunksPlugin = SplitChunksPlugin = SplitChunksPlugin = SplitChunksPlugin = SplitChunksPlugin = SplitChunksPlugin
module.exports = {
/ /...
optimization: {
splitChunks: {
// properties set outside of cacheGroups apply to all cacheGroups, although they can be reset within each cache group
chunks: "async"./ / what kind of a code block will be used to break up, choose three: "initial" : the entrance code block | "all" : all | "async" : on-demand loaded block of code
minSize: 30000.// Only modules larger than 30KB will be extracted
maxSize: 0.If the value is set to 0, the chunk will be divided if the chunk can be divided. If the chunk cannot be divided, it will not be forced
minChunks: 1.// How many code blocks must be referenced before a module is extracted into a new chunk
maxAsyncRequests: 5.// after splitting, the maximum number of parallel requests that can be loaded on demand is set to 6 by default in webpack5
maxInitialRequests: 3.// the maximum number of parallel requests allowed in the entry code block after splitting. The default value in webpack5 is 4
automaticNameDelimiter: "~".// Block name separator
name: true.// The name of the code block packaged for each cache group
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/.// Matches the module in node_modules
priority: -10.// Priority. When the module matches multiple cache groups at the same time, it is assigned to the cache group with the highest priority
},
default: {
minChunks: 2.// Overrides the outer global properties
priority: -20.reuseExistingChunk: true.// Whether to reuse modules that have been separated from the original code block},},},},};Copy the code
Five of these attributes are key to controlling code splitting rules, and I’ll mention a few more:
- MinSize (default 30000) : such that modules larger than this value are extracted.
- MinChunks (default 1) : Used to define at least how many repetitions a module must have before it is extracted.
- MaxInitialRequests (default 3) : A block of code ends up being a number of requests, so this property determines the maximum number of blocks an entry can split into. A value too small will not make the entry smaller no matter how much you slice it up.
- MaxAsyncRequests (default 5) : Same as above, determining the maximum number of code blocks that can be loaded on-demand.
- Test: The regular expression is used to accurately match the modules to be extracted, and various rules can be formulated according to the project structure, which is the key to manual optimization.
Once these rules are formulated, only the modules that meet all the requirements will be extracted, so reasonable configuration is required according to the project situation to achieve satisfactory optimization results.
Treasure attribute Name
Name (default true), used to determine the name of the chunk to be packaged by the cache group, is easily overlooked but powerful. The odd thing is that it has two types of values, Boolean and string:
- When the value is true, WebPack automatically selects a name based on the key of the code block and the cache group, so that a cache group is packaged into multiple chunks.
- When the value is false, it is suitable for production mode. Webpack avoids unnecessary naming of chunk to reduce the packaging volume. Except for the entry chunk, the names of other chunks are determined by their IDS, so the final packaging result is a row of numerically named JS. This is why when we look at resources requested from online web pages, there are always some 0.js, 1.js files mixed with them (of course, there are more ways to name resources with numeric ids than this, see below).
- When the value is string, the cache group is eventually packaged into a chunk named string. In addition, when two cache groups have the same name, they end up packaged together in one chunk. You can even set it to the name of an entry to remove it.
An example to play with various scenarios
It’s time to learn the code. Webpack’s default optimization strategy is good enough for average-sized projects, but big factory projects that change hands are often complex and require manual optimization. SplitChunksPlugin (webPack 4.43.0) SplitChunksPlugin (WebPack 4.43.0)
|--node_modules/
| |--vendor1.js
| |--vendor2.js
|--pageA.js
|--pageB.js
|--pageC.js
|--utility1.js
|--utility2.js
|--utility3.js
|--webpack.config.js
Copy the code
vendor1.js:
export default() = > {console.log("vendor1");
};
Copy the code
vendor2.js:
export default() = > {console.log("vendor2");
};
Copy the code
pageA.js:
import vendor1 from "vendor1";
import utility1 from "./utility1";
import utility2 from "./utility2";
export default() = > {console.log("pageA");
};
Copy the code
pageB.js:
import vendor2 from "vendor2";
import utility2 from "./utility2";
import utility3 from "./utility3";
export default() = > {console.log("pageB");
};
Copy the code
pageC.js:
import utility2 from "./utility2";
import utility3 from "./utility3";
export default() = > {console.log("pageC");
};
Copy the code
utility1.js:
import utility2 from "./utility2";
export default() = > {console.log("utility1");
};
Copy the code
utility2.js:
export default() = > {console.log("utility2");
};
Copy the code
utility3.js:
export default() = > {console.log("utility3");
};
Copy the code
Each file doesn’t have much content, but the key is their reference relationship. Webpack.config.js is configured as follows:
var path = require("path");
module.exports = {
mode: "development".// mode: "production",
entry: {
pageA: "./pageA".pageB: "./pageB".pageC: "./pageC",},optimization: {
chunkIds: "named".// Specify the chunkId in the packaging process. Setting it to named will generate a readable chunkId for debugging
splitChunks: {
minSize: 0.// The default size is 30000 (30KB), but the files in demo are very small. MinSize is set to 0, so that each file meets the size requirement
cacheGroups: {
commons: {
chunks: "initial".minChunks: 2.maxInitialRequests: 3.// The default is 3
},
vendor: {
test: /node_modules/,
chunks: "initial".name: "vendor",},},},},output: {
path: path.join(__dirname, "dist"),
filename: "[name].js",}};Copy the code
Console run: Webpack, pack result:As you can see, splitChunks sets minSize=0 globally and all modules meet this condition. The cache group vendor matches the contents of node_modules with the test re, packaged into a code block vendor.js. Utility2.js is referenced in pageA, pageB, pageC and complies with the Commons cache group minChunks=2, so it is packaged separately to CommonspageApageBPageC. Js. However, utility3.js is also referenced in pageB. Js and pageC. Js, which meet the Commons conditions, but are still scattered in pageB and pageC chunk. We look at the output and see that the pageB entry needs to load CommonspageApageBThe three packages of pagec.js vvendor. Js pageb.js, while maxInitialRequests in our Commons rule is 3, the number of entry subcontracting has reached the upper limit, it is likely that the upper limit is too small to continue subcontracting. So we changed the Commons rule to increase maxInitialRequests to 5:
commons: {
chunks: "initial".minChunks: 2.maxInitialRequests: 5.// The default value is 3, which cannot meet our subcontracting quantity
},
Copy the code
Again packaged, the result is:This time Utility3.js is packaged separately as CommonspageBPageC, and the pageB entry becomes four packages: CommonspageApageBPageC js, vendor. Js CommonsPageB ~ pageC js, pageB. Js. So, if we find that some modules cannot retrieve rules, we can see if the number of packages has reached its limit. Check maxInitialRequests and maxAsyncRequests. MaxAsyncRequests are the same as maxInitialRequests, but determine the upper limit of subquests to load on demand.
As we continue our research, we can’t help but wonder: why does the cache group Commons produce Commons pageA pageB pagec.js and Commons pageB~ pagec.js, while the cache group vendor produces vendor.js? Package name formats vary greatly. This is where the name attribute comes in. We comment out vendor’s name:
vendor: {
test: /node_modules/,
chunks: "initial".// name: "vendor",
}
Copy the code
Packing results are as follows:Found that the original vendor.js is split into vendorPageA. Js and vendorPageb.js, recall the name feature from the previous section. Because we didn’t specify the name, which defaults to true, WebPack automatically selects a name based on the key of the code block and cache group, so that a cache group is packaged into multiple chunks. Then let’s experience the feeling of name being false:
splitChunks: {
minSize: 0.// The default size is 30000 (30KB), but the files in demo are very small. MinSize is set to 0, so that each file meets the size requirement
name:false.cacheGroups: {
commons: {
chunks: "initial".minChunks: 2.maxInitialRequests: 5.// The default value is 3, which cannot meet our subcontracting quantity
},
vendor: {
test: /node_modules/,
chunks: "initial".name: "vendor",}}}Copy the code
Packing results are as follows:The two chunks produced by the Commons cache group are now 0.js and 1.js, reducing their size by almost 20 bytes, which is also an optimization.
Add ingredients and load as needed
The examples above are all optimizations for entry files, but let’s mix in the load on demand code to see what new experiences we can bring to our optimizations. Add two lazy loading files to the project async1.js and async2.js:
|--node_modules/
| |--vendor1.js
| |--vendor2.js
|--pageA.js
|--pageB.js
|--pageC.js
|--utility1.js
|--utility2.js
|--utility3.js
|--async1.js
|--async2.js
|--webpack.config.js
Copy the code
Async1. Js code:
import utility1 from "./utility1";
export default() = > {console.log("async1");
};
Copy the code
Async2. Js code:
import utility1 from "./utility1";
export default() = > {console.log("async1");
};
Copy the code
Pagea.js updated to:
import vendor1 from "vendor1";
import utility1 from "./utility1";
import utility2 from "./utility2";
export default() = > {/ / lazy loading
import("./async1");
import("./async2");
console.log("pageA");
};
Copy the code
Webpack.config.js is configured to:
var path = require("path");
module.exports = {
mode: "development".// mode: "production",
entry: {
pageA: "./pageA".pageB: "./pageB".pageC: "./pageC",},optimization: {
chunkIds: "named".// Specify the chunkId in the packaging process. Setting it to named will generate a readable chunkId for debugging
splitChunks: {
minSize: 0.// The default size is 30000 (30KB), but the files in demo are very small. MinSize is set to 0, so that each file meets the size requirement
// name:false,
cacheGroups: {
commons: {
chunks: "all".// After adding on-demand loading, set it to all to include all modules in the optimization scope
// name: "commons",
minChunks: 2.maxInitialRequests: 5.// The default value is 3, which cannot meet our subcontract quantity
},
vendor: {
test: /node_modules/,
chunks: "initial".name: "vendor",},},},},output: {
path: path.join(__dirname, "dist"),
filename: "[name].js",}};Copy the code
The rest of the code is unchanged. Now that the project file has been added, the output of the direct Webpack may not be enough, so we execute: Webpack –display-chunks is used to learn which chunks are contained in each chunk, which modules are contained in each chunk, and which cache group they belong to. In this way, based on the specific information of chunk, we can determine whether there are duplicate modules that are not completely extracted, and whether there are some modules that clearly do not match the rules we want. If so, It means there is room for further optimization. Packing results are as follows:The chunks that hit the cache group are labeled split Chunk information, the entry chunk is labeled [Entry], and the two on-demand files are packaged as 0.js and 1.js and do not belong to any cache group or entry.
It is found that utility1.js is referenced by pagea. js, Async1. js and async2.js modules at the same time, which should match the rules of Commons cache group and be extracted separately into a chunk. The result, however, is that it is still packaged in Pagea.js. This is because async1.js and Async2. js are lazy loading modules of Pagea. js, while Pagea. js references utility1.js synchronously, so when loading async1.js and async2.js, utility1.js already exist. Just use it, so there’s no need to add a new chunk to the list.
When should Utility1.js be mentioned separately? We adjusted the code to move the on-demand code from pagea.js to pageb.js:
pageA.js:
import vendor1 from "vendor1";
import utility1 from "./utility1";
import utility2 from "./utility2";
export default() = > {/ / lazy loading
// import('./async1');
// import('./async2');
console.log("pageA");
};
Copy the code
pageB.js:
import vendor2 from "vendor2";
import utility2 from "./utility2";
import utility3 from "./utility3";
export default() = > {/ / lazy loading
import("./async1");
import("./async2");
console.log("pageB");
};
Copy the code
Executing webpack –display-chunks results in the following:Utility1.js was extracted into 0.js separately and belongs to the Commons cache group. After the loading on demand code is moved from Pagea.js to pageb.js, since pageB and pageA are in parallel without dependencies, async1.js and Async2.js need to load utility1.js modules separately. Since Commons cache groups are chunks=all, public modules of Async1.js, Async2.js and Pagea.js are extracted separately.
Finally, we want to change the numeric ID name to a meaningful name. We can use webpack’s Magic Comments to change pageb.js to:
import vendor2 from "vendor2";
import utility2 from "./utility2";
import utility3 from "./utility3";
export default() = > {/ / lazy loading
import(/* webpackChunkName: "async1" */ "./async1");
import(/* webpackChunkName: "async2" */ "./async2");
console.log("pageB");
};
Copy the code
Ordinary packaging is enough, and the result is:This gives all chunks loaded on demand names, and utility1.js extracted separately matches the default naming format and has its own names.
To highlight
This example uses webPack’s two main optimizations, focusing on how to keep the project packaging under control, avoid confusing packaging situations, and ultimately get the desired packaging results. I hope that after reading this article, we can have optimization points in the face of complex projects.
Of course, optimization itself is a matter of piece-building. For example, extracting a public chunk will result in one more file being packaged, which will inevitably result in another network request. When the project is very large, extracting each common module separately into a chunk will lead to surprisingly slow packaging speed, which will affect the development experience. Therefore, a compromise solution is usually adopted, that is, extracting the large and repeated modules separately, and packing some small and repeated modules into a chunk, so as to reduce the number of packages and keep the package from being too large. Otherwise, the page load time will be affected.
In taobao research for a period of time to pack, to share my thoughts with you: optimization is under the limited space and time to calculate force, removing inefficient repetitive (proposed public module), reasonable redundancy (small files allow duplicate), and by using some users have no perception of interval (preload), achieve comprehensive considerations on the optimal time and space.
Next, take a closer look at SplitChunksPlugin source code and the code splitting principle of WebPack. SplitChunksPlugin source code will be ripped out in a week with more than 50 likes.
— Dividing line —
Mom don’t have to worry about my optimization | Webpack series (2) : SplitChunksPlugin source code explanation
The source code
My code split example
The resources
Webpack website
The official demo
Other dry goods
Summary and Reflection:
- Sacrifice weight, embrace techniques, dry goods and chicken soup I have | 2020 mid-year summary
Answer:
- Koban small front of the big factory surface
CSS details:
- The interviewer wants to know how much you know about absolute position
For those who can’t find their way
- Back end to front end of the little brother suddenly reap big factory offer, the truth is actually