webpack Packaging Optimization
I won't explain more about what is webpack here. If you don't know, this article is not suitable for you to read now.
webpack5 basic configuration
Let's first look at a basic configuration, webpack based on webpack 5 confing. JS file.
/* * webpack.confing.js */ const { resolve } = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { // pattern mode: "development", // entrance entry: "./src/index.js", // Export output: { filename: "built.js", path: resolve(__dirname, "build"), // Custom output static resource file name (picture) assetModuleFilename: "assets/[hash][ext]", }, // modular module: { rules: [ // Configuration of loader { test: /\.css$/, // Use loader to process files /** * use The execution order in the array is from right to left and from bottom to top * style-loader Create a style tag and import the style file into the header * css-loader Change the css module into a commonjs module and load it into js. The content of the file is the style string */ use: ["style-loader", "css-loader"], }, { test: /\.less$/, use: [ "style-loader", "css-loader", // Compile less file into css file // You need to download less loader and less "less-loader", ], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, // webpack 5 has built-in resource types and has abandoned the previous URL loader and file loader type: "asset/resource", }, { test: /\.html$/, // Process image files in html, introduce img files, and then let URL loader process them loader: "html-loader", }, { // Dealing with other resources exclude: /\.(html|js|css|less|png|svg|jpg|jpeg|gif)$/i, type: "asset/resource", }, ], }, // plug-in unit plugins: [ // HtmlWebpackPlugin // By default, an empty html file will be created, and all packaged resources will be automatically introduced. // A structured html file is required new HtmlWebpackPlugin({ template: "./src/index.html", }), ], /** * Download yarn add webpack dev server -- dev * Run npx webpack serve */ devServer: { // Path after project construction contentBase: resolve(__dirname, "build"), // Automatically open browser open: true, // Port number port: 5555, // Turn on gzip compression compress: true, }, }; Copy code
The above code is the most basic code of our article, and the packaging optimization later is configured based on this file.
HMR hot update
The HMR - hot module replacement function will replace, add or delete modules during the operation of the application without reloading the whole page.
advantage:
- Retain the application state lost during a full reload of the page.
- Only update changes to save valuable development time.
- When CSS/JS is modified in the source code, it will be updated in the browser immediately, which is almost equivalent to changing the style directly in the browser devtools.
Problem picture
We can see from the above figure that when we change the style, our js file is executed again, which is a problem for our project. Our optimization direction is to change the file and only the file is reloaded.
code
According to the above problem, we only need to change the code at devServer.
/* * webpack.confing.js */ ... module.exports = { ... devServer: { // Path after project construction contentBase: resolve(__dirname, "build"), // Automatically open browser open: true, // Port number port: 5555, // Turn on gzip compression compress: true, // New -- > enable hot update // The module hot replacement function will replace, add or delete modules during program operation without reloading the whole page. hot: true, }, }; Copy code
Effect picture
devtool source code debugging mode
Choose a source map format to enhance the debugging process. Different values can significantly affect the speed of build and rebuild.
-
For the source code debugging of the development environment
Eval source map - initializing the source map is slow, but provides a faster speed when rebuilding and generates the actual file. The number of lines can be mapped correctly because it will be mapped to the original code. It generates the best quality source map for the development environment.
-
Source code debugging for production environment
(none) (omit the devtool option) - no source map is generated. This is a good choice.
Rule.oneOf matching rule
Matching rules for loader.
/* * webpack.confing.js */ ... module.exports = { ... // modular module: { rules: [ // Configuration of loader { oneOf: [ // The following loader will execute the matching file only once. { test: /\.css$/, // Use loader to process files /** * use The execution order in the array is from right to left and from bottom to top * style-loader Create a style tag and import the style file into the header * css-loader Change the css module into a commonjs module and load it into js. The content of the file is the style string */ use: ["style-loader", "css-loader"], }, { test: /\.less$/, use: [ "style-loader", "css-loader", // Compile less file into css file // You need to download less loader and less "less-loader", ], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, // webpack 5 has built-in resource types and has abandoned the previous URL loader and file loader type: "asset/resource", }, { test: /\.html$/, // Process image files in html, introduce img files, and then let URL loader process them loader: "html-loader", }, { // Dealing with other resources exclude: /\.(html|js|css|less|png|svg|jpg|jpeg|gif)$/i, type: "asset/resource", }, ], }, ], }, ... } Copy code
In this way, the matching of loader s during packaging will improve the speed.
File cache
In the production environment, we can cache our packaged css, js and other resources into the browser to improve the speed of the second page we enter. So we have to be right about webpack confing. js to configure and cache js files, css files and picture files.
First, we have to download the following loader:
yarn add babel-loader @babel/core @babel/preset-env mini-css-extract-plugin --dev
Optimization steps
- Extract css files from packaged files as separate files.
- Use babel cache for js files.
Extract css and cache file resources
/* * webpack.confing.js */ ... // Add plug-in const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { ... // pattern mode: "production", output: { filename: "built.[contenthash:10].js", ... }, ... // modular module: { rules: [ // Configuration of loader { oneOf: [ // The following loader will execute the matching file only once. { test: /\.css$/, // Replace style loader with use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.less$/, use: [ // Replace style loader with MiniCssExtractPlugin.loader, "css-loader", "less-loader", ], }, ... // Add js loader { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], // Enable babel cache // On the second build, the previous cache is read // Used to cache the execution results of the loader. After the webpack is built, it will try to read the cache, // To avoid the possible Babel consumption of high performance during each execution // Recompilation process. cacheDirectory: true, }, }, }, ], }, ], }, // plug-in unit plugins: [ ... //Add plug-in new MiniCssExtractPlugin({ filename: "css/built.[contenthash:10].css", }), ], }; Copy code
-
Hash: a unique hash value will be generated each time wepack is built.
- Because of the problem of using both has and css values.
- Repackaging will invalidate all caches. (maybe I only changed one file)
-
Chunk hash: hash value generated according to chunk. If the package comes from the same chunk, the hash value is the same.
- Question: the hash values of js and css are still the same
- Because css is introduced in js, it belongs to the same chunk
-
contenthash: generates a hash value according to the contents of the file.
- The hash value of different files must be different, so that the code can run online and the cache can be used better.
Add jsloader and set cache
/* * webpack.confing.js */ ... module.exports = { ... // modular module: { rules: [ // Configuration of loader { oneOf: [ ... // Add js loader { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], // Enable babel cache // The second read before the cache is built // Used to cache the execution results of the loader. After the webpack is built, it will try to read the cache, // To avoid the possible Babel consumption of high performance during each execution // Recompilation process. cacheDirectory: true, }, }, }, ], }, ], }, ... }; Copy code
Next, we write the test code. Create a new server JS file and create a new server for testing. Download the express framework.
yarn add express --dev
/* * server.js */ const express = require("express"); const server = express(); // Cache for one hour server.use(express.static("build", { maxAge: 1000 * 3600 })); server.listen(5555, () => { console.log("Server started successfully!", "http://localhost:5555/"); }); Copy code
Check cache steps
- webpack packaging
- Start the node server js
- http://localhost:5555/
tree shaking to remove useless code
Use premise
- ES6 modularization must be used
- Open production environment
Advantages: when packaging the production environment, we can ignore the unused code and delete the unused code from the packaging file to reduce the volume of the packaging file.
As long as we meet the above two preconditions, I will automatically delete the invalid code during the packaging of webpack.
Add test file
/* * testTreeShaking.js */ export const test1 = () => { console.log("test1"); }; export const test2 = () => { console.log("test2"); }; Copy code
Modify file
/* * index.js */ ... import { test1 } from "./testTreeShaking"; test1(); // test2 function is not introduced. Ignored when packing. Copy code
/* * webpack.confing.js */ ... module.exports = { ... // Modify packaging mode mode: 'production', ... }; Copy code
package. Configuration in JSON
/* * ackage.json */ "sideEffects": false // No side effects (tree shaking can be performed) // css / @babel/polyfill files may be killed "sideEffects": ["*.css", "*.less"] Copy code
For webpack packaging, view the packaging file as shown in the figure below:
You can see that the test2 function is not packaged into the package file.
code Split
There are three types of code segmentation:
- Multi entry files are automatically code split
- optimization.splitChunks control code splitting
- optimization.splitChunks + import() for code splitting
Advantages: Code segmentation can effectively reject js files that are too large.
Multi entry files are automatically code split
/* * webpack.confing.js */ ... module.exports = { ... entry: { // Multiple entries: there is one entry, and the final output has a bundle index: "./src/index.js", test: "./src/testTreeShaking.js", }, ... }; Copy code
The effect of webpack packaging is shown in the following figure:
optimization.splitChunks control code splitting
/* * webpack.confing.js */ ... module.exports = { ... // New code optimization: { /* 1. Node can be_ The code in modules packs a chunk separately for final output 2. Automatically analyze whether there are public files in the multi entry chunk. If so, it will be packaged into a single chunk */ splitChunks: { //This indicates which chunk s will be selected for optimization. When a string is provided, the valid values are all, async, and initial. //Setting to all can be particularly powerful because it means that chunks can be shared between asynchronous and non asynchronous chunks. chunks: "all", }, }, ... }; Copy code
The effect of webpack packaging is shown in the following figure:
optimization.splitChunks + import() for code splitting
/* * webpack.confing.js */ ... module.exports = { ... // New code optimization: { /* 1. Node can be_ The code in modules packs a chunk separately for final output 2. Automatically analyze whether there are public files in the multi entry chunk. If so, it will be packaged into a single chunk */ splitChunks: { //This indicates which chunk s will be selected for optimization. When a string is provided, the valid values are all, async, and initial. //This means that non chunk can be set between asynchronous and all. chunks: "all", }, }, ... }; Copy code
/* * index.js */ /* Through js code, a file is packaged into a chunk separately import Dynamic import syntax: a file can be packaged separately */ import("./testTreeShaking").then( (res) => { console.log("res", res); res.test1(); } ); Copy code
The effect of webpack packaging is shown in the following figure:
File preload
difference
- Reload using file
- Load the browser first when it is idle
Modify file
/* *index.html */ ... <button id="btn">load testTreeShaking file</button> ... Copy code
/* *index.js */ console.log("load index file"); Copy code
/* *testTreeShaking.js */ export const test1 = () => { console.log("test1"); }; export const test2 = () => { console.log("test2"); }; console.log("load index file"); Copy code
File lazy loading
/* *index.js */ console.log("load index file"); document.getElementById("btn").onclick = function () { // Lazy load: load only when the file needs to be used import("./testTreeShaking").then(({ test1 }) => { test1(); }); }; Copy code
Lazy loading effect drawing
Preload
/* *index.js */ console.log("load index file"); document.getElementById("btn").onclick = function () { // Preload prefetch: js files will be loaded in advance before use // Normal loading can be considered as parallel loading (loading multiple files at the same time) // Preload prefetch: after other resources such as webpackPrefetch are loaded, the browser is idle, and then load resources secretly import(/* webpackPrefetch: true */ "./testTreeShaking").then(({ test1 }) => { test1(); }); }; Copy code
Preload rendering
PWA progressive network application
PWA can be used to do many things. Offline is the most important function when the application is running. This is achieved by using a web technology called Service Workers.
PWA renderings of Taobao
Add the workbox-webpack-plugin plug-in and adjust the webpack.. config. JS file:
yarn add workbox-webpack-plugin --dev
Modify webpack confing. js
/* * webpack.confing.js */ ... // Add plug-in const WorkboxWebpackPlugin = require("workbox-webpack-plugin"); module.exports = { ... // plug-in unit plugins: [ ... //Add plug-in new WorkboxWebpackPlugin.GenerateSW({ /* 1. Help serviceworker start quickly 2. Delete old serviceworker Generate a serviceworker configuration file~ */ clientsClaim: true, skipWaiting: true, }), ], }; Copy code
Register Service Worker
/* * index.js */ /* sw The code must run on the server --> nodejs --> npm server Start the server and expose all resources in the build directory as static resources */ // Register serviceWorker // Handling compatibility issues if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/service-worker.js") .then(() => { console.log("sw Registration succeeded~"); }) .catch(() => { console.log("sw Registration failed~"); }); }); } Copy code
Next, we will the previous server JS files are copied and used directly:
- Pack first
- node server.js start server
- http://localhost:5555/
Multi process packaging
yarn add thread-loader --dev
Modify webpack confing. JS file:
/* * webpack.confing.js */ ... module.exports = { ... // modular module: { rules: [ // Configuration of loader { oneOf: [ ... // Add js loader { test: /\.js$/, exclude: /node_modules/, use: [ /* New code Turn on multi process packaging. The process start-up is about 600ms, and the process communication also has overhead. Only when the work takes a long time, multi process packaging is required */ { loader: "thread-loader", options: { // The number of worker s generated. The default is (number of cpu cores - 1), or, // In require ('os') Fallback to 1 when cpus() is undefined workers: 2, // 2 processes }, }, ... ], }, ], }, ], }, ... }; Copy code
You can check the effect by running webpack. Because it takes 600ms to start the multi process packaging, it is more suitable for projects with larger code volume.
Multi process packaging rendering is not enabled
Open multi process packaging rendering
externals
Prevent some import ed packages from being packaged into bundle s, but obtain these external dependencies from the outside at runtime.
Modify webpack confing. JS file, add externals attribute
/* * webpack.confing.js */ ... module.exports = { ... externals: { jquery: "jQuery", }, }; Copy code
Select the CDN link of JQ and add it to the index html
/* * index.html */ <!DOCTYPE html> <html lang="zn"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>webpack Packaging Optimization</title> <!-- New code --> <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script> </head> <body> <h1>webpack Packaging Optimization</h1> <div class="color_1"></div> <div class="color_2"></div> <img src="./img/zp.jpg" /> </body> </html> Copy code
/* * index.js */ import $ from "jquery"; console.log("$", $); Copy code
webpack is packaged for testing.