preface
Front-end engineering has experienced many excellent tools, such as Grunt, Gulp, Webpack, rollup, etc., each tool has its own application scenario, and the most widely used tool today is Webpack. Therefore, learning WebPack well has become an essential skill for a good front-end.
Due to the complexity of webpack stack, the author intends to explain it in two articles:
- Basic application: explain all kinds of basic configuration;
- Advanced applications: Explanation
webpack
Optimization and principles.
[Note] This article is based on webPack 4.x
What is the webpack
Webpack is a module packaging tool.
Webpack can package the following code without any configuration:
When nothing is configured, WebPack uses the default configuration.
// moduleA.js
function ModuleA(){
this.a = "a";
this.b = "b";
}
export default ModuleA
// index.js
import ModuleA from "./moduleA.js";
const module = new ModuleA();
Copy the code
We know that the browser does not know the import syntax, and running such code directly in the browser will result in an error. We can use Webpack to package such code, enabling JavaScript to be modular.
The original version of WebPack could only pack JavaScript code, but as it has evolved CSS files, image files, and font files can all be packaged by WebPack.
This article will focus on how WebPack packages these resources. The basic article is intended to pave the way for performance optimization and principles later. If you are already familiar with WebPack, you can skip this article.
Initialize the webPack installation
mkdir webpackDemo // Create a folder
cd webpackDemo // Go to the folder
npm init -y // Initialize package.json
npm install webpack webpack-cli -D // The development environment installs Webpack and webPack-CLI
Copy the code
After this installation, we can use the Webpack command in our project.
Package the first file
webpack.config.js
const path = require('path');
module.exports = {
mode: 'development'./ / {1}
entry: { / / {2}
main:'./src/index.js'
},
output: { / / {3}
publicPath:"".// Add a uniform prefix address to all dist files. For example, add the domain name published to the CDN
filename: 'bundle.js'.path: path.resolve(__dirname,'dist')}}Copy the code
Code analysis:
- {1}
mode
Is packaging a development environment or a build environment,development | production
- {2}
entry
The entry file isindex.js
- {3}
output
Output to thepath
Configuration of thedist
Folder, the output file namefilename
Configuration of thebundle.js
Create files for simple packaging:
src/moduleA.js
const moduleA = function () {
return "moduleA"
}
export default moduleA;
--------------------------------
src/index.js
import moduleA from "./moduleA";
console.log(moduleA());
Copy the code
Modify package.json scripts to add a command:
"scripts": {
"build": "webpack --config webpack.config.js"
}
Copy the code
Run the NPM run build command
Source code analysis of bundled bundle.js
The source code has been simplified, only the core part of the display, easy to understand:
(function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
// Cache files
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Initialize the moudle and also store a copy in the cache
var module = installedModules[moduleId] = {
i: moduleId,
l: false.exports: {}};// execute the function body corresponding to "./ SRC /index.js"
modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);
// mark the module "./ SRC /index.js" as well as loading it
module.l = true;
// Returns the module that has been loaded successfully
return module.exports;
}
// Where the anonymous function starts execution, and the default path is the entry file
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
// Pass the module object of the anonymous execution function body, containing "./ SRC /index.js", "./ SRC/modulea.js"
// And their corresponding function bodies to be executed
({
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ \"./src/moduleA.js\"); \n\n\nconsole.log(Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()); \n\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/moduleA.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \nconst moduleA = function () {\n return \"moduleA\"\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (moduleA); \n\n\n//# sourceURL=webpack:///./src/moduleA.js?"); })});Copy the code
/ SRC /index.js”./ SRC /index.js
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ \"./src/moduleA.js\"); \n\n\nconsole.log(Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()); \n\n\n//# sourceURL=webpack:///./src/index.js?");
})
Copy the code
You’ll notice that it’s an eval method.
Let’s take a closer look at eval and simplify the code as follows:
var moduleA = __webpack_require__("./src/moduleA.js");
console.log(Object(moduleA["default") ());Copy the code
__webpack_require__(__webpack_require__. S = “./ SRC /index.js”); “, then “./ SRC /index.js” recursively calls the output object to get “./ SRC/modulea.js “.
Let’s see what the “./ SRC/modulea.js “code will output:
const moduleA = function () {
return "moduleA"
}
__webpack_exports__["default"] = (moduleA);
Copy the code
Looking back at the code above, this is equivalent to:
console.log(Object(function () {
return "moduleA"}) ());Copy the code
Finally, execute and print “moduleA”
Through the analysis of this source code can be seen:
- After packaging the module, all are passed
eval
Function to execute; - By calling the entry function
./src/index.js
And then we recursively find all the modules, because recursion does double counting, so__webpack_require__
There is a cache object in the functioninstalledModules
.
loader
We know that WebPack packs JavaScript modules, and we’ve long heard that WebPack packs images, fonts, and CSS, so we need loader to help us identify these files.
[Note] If the file is not recognized, please find loader.
Pack image files
Modify the configuration file: webpack.config.js
const path = require('path');
module.exports = {
mode: 'development'.entry: {
main:'./src/index.js'
},
output: {
filename: 'bundle.js'.path: path.resolve(__dirname,'dist')},module: {rules:[
{
test:/\.(png|svg|jpg|gif)$/,
use:{
loader: 'url-loader'.options: {
name: '[name]_[hash].[ext]'.outputPath:"images".// Package the resource into the images folder
limit: 2048 // if the size of the image is less than 2048KB, output base64, otherwise output the image}}}]}}Copy the code
Modification: SRC/index. Js
import moduleA from "./moduleA";
import header from "./header.jpg";
function insertImg(){
const imageElement = new Image();
imageElement.src = `dist/${header}`;
document.body.appendChild(imageElement);
}
insertImg();
Copy the code
After the packing, it was found that the packing was normal, and there was an additional image file in the dist directory.
Let’s make a simple analysis:
Webpack itself actually only know JavaScript modules, when touching the image file will go to find, the module configuration rules found test: / \. (PNG | SVG | JPG | GIF) $/, When the re matches the image file suffix, it is processed using url-loader. If the image is less than 2048KB (this can be set to any value, depending on the project), base64 is output.
Package style file
{
test:/\.scss$/.// The re matches the.scss style file
use:[
'style-loader'.// Insert the resulting CSS content into the HTML
{
loader: 'css-loader'.options: {
importLoaders: 2.// import SCSS file again in SCSS, also execute sass-loader and postCSs-loader
modules: true // Enable the CSS module}},'sass-loader'.// Parse SCSS files into CSS files
'postcss-loader'// Automatically adds the vendor prefix -webket-moz, which also requires the creation of the postcss.config.js configuration file]}Copy the code
postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')]};Copy the code
Package analysis:
- when
webpack
encounterxx.scss
The style file is; - In turn, calls
postcss-loader
Automatically adds vendor prefixes-webket -moz
; - call
sass-loader
把scss
File conversion tocss
File; - call
css-loader
To deal withcss
File, whereimportLoaders:2
, it isscss
Other things are introduced in the filescss
File, need to be called repeatedlysass-loader
postcss-loader
Configuration items of; - The last call
style-loader
Let me compile itcss
The content of the document is<style>... </style>
Form is inserted into the page.
[Note] The loader is executed from the end of the array to the end of the array.
Package font files
{
test: /\.(woff|woff2|eot|ttf|otf)$/.// Package the font files
use: ['file-loader'] // Move the font file to dist
}
Copy the code
plugins
Plugins can do things for you at a certain point in a WebPack’s life cycle, just like calling plug-ins to do auxiliary things.
html-webpack-plugin
Function:
After the package is complete, an HTML file (or template) is automatically generated, and the JS file generated by the package is automatically imported into the HTML file.
Use:
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html' // Use a template file})]Copy the code
clean-webpack-plugin
Function:
The folder configured for output is automatically deleted each time the package result is output
Use:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
...
new CleanWebpackPlugin() Use this plug-in to delete the dist directory each time it is generated
]
Copy the code
source map
There is a very important function in the development process, that is error debugging, we have errors in the process of writing code, if the compiled package is not friendly, it will seriously affect our development efficiency. Configuring the Source Map helps solve this problem.
Example: Modified: SRC /index.js to add an incorrect line of code
console.log(a);
Copy the code
Since mode: ‘development’ turns the Source map feature on by default, let’s turn it off first.
devtool: 'none' // Close the source map configuration
Copy the code
Execute the package to see the console error message:
The error stack information is actually given in the packaged bundle file, but the structure of our file during development is not like this, so we need it to indicate how many lines in index.js we have failed so that we can quickly locate the problem.
We remove the devtool:’ None ‘configuration and do the packing:
At this point, it prints out our specific development errors on the stack, which is what the Source Map does.
To summarize: The source map is a mapping that knows the specific lines and columns in the actual development file that corresponds to the bundle.js file in the dist directory.
webpackDevServer
It’s not scientific to manually compile the code every time you modify it. We want webPack to automatically compile the code every time we write it, and webpackDevServer can help us.
Add configuration:
devServer: {
contentBase: './dist'.// Server boot root set to dist
open: true.// Automatically open the browser
port: 8081.// Configure the service startup port. The default port is 8080
proxy: {'/api': 'http://www.baidu.com' // When the development environment sends/API requests, they are proxyed to http://www.baidu.com host}},Copy the code
It basically helps us start a Web service, listens for files under SRC, and automatically helps us re-execute the WebPack compilation when the files change.
Let’s add a command to package.json:
"scripts": {
"start": "webpack-dev-server"
},
Copy the code
Now that we have executed the NPM start command, we can see that the console is in listening mode, and any changes we make to the business code will trigger a WebPack recompilation.
Webpack-dev-server implements the request broker
In the front-end development of the project with the front and back end separated, every student must encounter a thorny problem is cross-domain request. Webpack-dev-server can also be used as a proxy in a production environment. It can also be used as a proxy in a development environment
devServer: { ... Proxy :{'/ API ': 'http://www.baidu.com' // when the development environment sends/API requests will be proxy to http://www.baidu.com host}}Copy the code
The configuration items of proxy are very rich and you can refer to the documentation for details. We just need to remember that it can provide the function of proxy server to us.
Manually implement a simple version of Webpack-dev-server
Add: server.js to the project root directory
NPM install Express webpack-dev-middleware -d
const express = require('express');
const app = express();
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js'); // Import the WebPack configuration file
const compiler = webpack(config); // Webpack compiles the runtime
// Tell Express to use webpack-dev-middleware,
// And use the webpack.config.js configuration file as the base configuration
app.use(webpackDevMiddleware(compiler, {}));
// Listen on the port
app.listen(3000.() = >{
console.log('Program started on port 3000');
});
Copy the code
Webpack dev – middleware functions:
- through
watch mode
Listening for changes to the resource and then automatically packaging, essentially callingcompiler
On the objectwatch
Methods; - Using an in-memory file system compiles quickly
compiler.outputFileSystem = new MemoryFileSystem()
;
Package. json adds a command:
"scripts": {
"server": "node server.js"
},
Copy the code
Run the NPM run server command to start the user-defined service. Enter http://localhost:3000/ in the browser to view the effect.
Hot Moudule Replacement (HMR)
Module hot update replaces, adds, or removes modules while the application is running without reloading the entire page.
HMR configuration
const webpack = require('webpack');
module.exports = {
devServer: {
contentBase: './dist'.open: true.port: 8081.hot: true // Hot update configuration
},
plugins: [new webpack.HotModuleReplacementPlugin() // Add hot update plugin]}Copy the code
Write HMR code manually
When writing code, it is often found that hot updates fail because the corresponding loader does not implement hot updates. Let’s see how to implement a hot update simply.
import moduleA from "./moduleA";
if (module.hot) {
module.hot.accept('./moduleA.js'.function() {
console.log("ModuleA supports hot update pull");
console.log(moduleA()); })}Copy the code
Code explanation:
A common ES6 syntax module that we wrote ourselves, if we want to implement hot updates, must manually listen for the relevant files, and then actively invoke the update callback when it is received.
Webpack creates a Module object for each module, which is added when initializing the module object when you enable hot update:
function hotCreateModule(moduleId) {
var hot = {
active: true.accept: function(dep, callback){
if (dep === undefined) hot._selfAccepted = true;
else if (typeof dep === "function") hot._selfAccepted = dep;
else if (typeof dep === "object")
for (var i = 0; i < dep.length; i++)
hot._acceptedDependencies[dep[i]] = callback || function() {};
else hot._acceptedDependencies[dep] = callback || function() {}; }}}Copy the code
The dependency table of the listening file path and callback function is saved in the module object. When the listening module changes, it will actively call the relevant callback function to achieve manual hot update.
[Note] All written business modules will eventually be managed by WebPack as module objects. If hot update is enabled, module will add hot attributes. These properties make up the WebPack compile runtime object.
Compile ES6
You obviously know that you have to use Babel to support this, so let’s see how to configure it
configuration
1. Install related packages
npm install babel-loader @babel/core @babel/preset-env @babel/polyfill -D
Copy the code
2. Modify webpack.config.json configuration
If you don’t know the type of file you want to compile, please use loader
module: {rules:[
{
test: /\.js$/.// Rematches the js file
exclude: /node_modules/.// Exclude the node_modules folder
loader: "babel-loader"./ / use the Babel - loader
options: {presets:[
[
"@babel/preset-env"./ / {1}
{ useBuiltIns: "usage" } / / {2}[]}}]}Copy the code
Babel configuration parsing:
- {1}
babel presets
Is a collection of plug-ins that transformES6+
New syntax, but some newAPI
It won’t handle itPromise
Generator
Is the new syntaxArray.prototype.map
Method is the newAPI
,babel
Does not convert this syntax, so you need topolyfill
To deal with
- {2}
useBuiltIns
The configuration is processed@babel/polyfill
How is it loaded? It has three valuesfalse
entry
usage
false
Wrong:polyfills
Do any operation;entry
: according to thetarget
The browser version will be supportedpolyfills
Split import, import only those not supported by the browserpolyfill
usage
: Checks the codeES6/7/8
Load only what is used in the codepolyfills
demo
Create a file SRC/modulees6.js
const arr = [
new Promise(() = >{}),
new Promise(() = >{})];function handleArr(){
arr.map((item) = >{
console.log(item);
});
}
export default handleArr;
Copy the code
Modify the file SRC /index.js
import moduleES6 from "./moduleES6";
moduleES6();
Copy the code
Execute the packaged source file (simplified) :
"./node_modules/core-js/modules/es6.array.map.js":
(function(module.exports, __webpack_require__) {
"use strict";
var $export = __webpack_require__("./node_modules/core-js/modules/_export.js");
var $map = __webpack_require__("./node_modules/core-js/modules/_array-methods.js") (1); $export($export.P + $export.F * ! __webpack_require__(/ *! ./_strict-method */ "./node_modules/core-js/modules/_strict-method.js")([].map, true), 'Array', {
map: function map(callbackfn) {
return $map(this, callbackfn, arguments[1]); }});Copy the code
Looking at the code, it should be clear that Polyfill is a reimplementation of the Map method using ES5 syntax to make it compatible with older browsers.
Polyfill implements all of the ES6+ syntax, which is too large for us to import, hence the configuration useBuiltIns: “usage” only loads the syntax used.
Compile the React file
configuration
Install dependency packages
npm install @babel/preset-react -D
npm install react react-dom
Copy the code
webpack.config.js
module: {rules:[
{
test: /\.js$/.// Rematches the js file
exclude: /node_modules/.// Exclude the node_modules folder
loader: "babel-loader"./ / use the Babel - loader
options: {presets:[
[
"@babel/preset-env",
{ useBuiltIns: "usage"}], ["@babel/preset-react"[]}}]}Copy the code
Add a [“@babel/preset-react”] configuration to the presets configuration, and preset will help us convert JSX syntax in React to react. createElement.
demo
Modify the file: SRC /index.js
import React,{Component} from 'react';
import ReactDom from 'react-dom';
class App extends Component{
render(){
const arr = [1.2.3.4];
return (
arr.map((item) = ><p>num: {item}</p>)
)
}
}
ReactDom.render(<App />.document.getElementById('root'));
Copy the code
Run the yarn build command to package the yarn properly and the yarn build interface is displayed.
As the complexity of the project increases, the configuration of Babel also becomes more complex, so we need to extract the configuration of Babel into a separate file for easy configuration and management, that is, the.babelrc file in our project directory.
.babelrc
{
"presets":[
["@babel/preset-env", {"useBuiltIns": "usage" }],
["@babel/preset-react"]]}Copy the code
Babel-laoder executes presets from the back of the array in the same order as when multiple Loaders are used simultaneously.
Extract the options object from babel-loader in webpack.config.js into a separate file.
One problem we found was that the bundle.js file was about 1M large, because react and react-dom were packed in.
Tree Shaking
The essence of Tree Shaking is to eliminate useless JavaScript code.
import { forEach } from "lodash"
forEach([1.2.3].(item) = >{
console.log(item);
})
Copy the code
The LoDash library was introduced in the project and only the forEach method was used. Back in the days of jquery we could only import the entire LoDash file. Tree Shaking is supported through import, so let’s configure it.
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production'.devServer: {
contentBase: './dist'.// Server boot root set to dist
open: true.// Automatically open the browser
port: 8081.// Configure the service startup port. The default port is 8080
},
entry: { // Import file
main:'./src/index.js'
},
output: { // Export file
publicPath:"".filename: 'bundle.js'.path: path.resolve(__dirname,'dist')},module: {/ / a loader configuration
rules:[
{
test: /\.js$/.// Rematches the js file
exclude: /node_modules/.// Exclude the node_modules folder
loader: "babel-loader"./ / use the Babel - loader}},plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html' // Use a template file
}),
new CleanWebpackPlugin()
]
}
Copy the code
Just configure mode: Tree Shaking is the default in the ‘production’ generation environment.
It is still 72KB when packaged, so Tree Shaking fails. Why?
For Tree Shaing to be Shaking, you must have libraries that use the import Export ESModule syntax, and LoDash provides libraries for using Lodash-es.
Modify the business code: SRC /index.js
import { forEach } from "lodash-es";
forEach([1.2.3].(item) = >{
console.log(item);
})
Copy the code
Perform packaging again:
The packaged size is only 5.55Kb, indicating that Tree Shaking is working.
Why ESModule
Tree Shaking is something you need to use ES6’s modular syntax. Is it possible to use CommonJs syntax for Tree Shaking? The answer is definitely no.
The CommonJS module is run time loaded, and the ES6 module is compile time output interface.
An ES6 module is not an object, and its external interface is a static definition that is generated during the code static parsing phase. Static analysis means analyzing code literally without executing it.
Take the above code to analyze: We introduced forEach in Lodash-es. As you can see in the static analysis phase, we only use forEach, so there is no need to introduce all the functions in lodash-es. Just keep forEach.
sideEffects
SideEffects must be configured when configuring Tree Shaking
package.json
{
"sideEffects": false
}
Copy the code
"sideEffects": false
saidwebpack
It can safely delete unused onesexport
;"sideEffects": ["*.css"]
said*.css
The introduction of do not doTree Shaking
To deal with.
Why do YOU need to do anything with CSS? Because we often introduce global CSS like this:
import "xxx.css"
Copy the code
If not configured, Tree Shaking will think that XXx. CSS is only imported, but not used, so the associated CSS code is removed. When configured as [“*.css”], all CSS files are excluded from Tree Shaking.
Code Splitting
Code segmentation, as the name implies, is to divide the packaged code.
Look at a scene:
import { forEach } from "lodash-es";
forEach([1.2.3].(item) = >{
console.log(item);
})
Copy the code
Execute the package command:
We found that LoDash was also packaged into bundle.js.
In real development, we would probably work with multiple libraries, and if they were all packaged into bundle.js, the file would be huge!
Another problem is that the static files we pack will all add hash values as follows:
output: {
filename: '[hash]_bundle.js'.// The packaged file is similar to 07b62441b18e3aaa6c93_bundle.js
path: path.resolve(__dirname,'dist')}Copy the code
Do we all know that the purpose of, that is, the browser will cache on the same name of the static resources if we don’t add the hash value, but online and found a bug, I once again after the static resource updates to the server in the package, the user’s browser with cache will not immediately according to the latest results, and need to clear the cache manually.
Generally, imported class library files do not change, while our business code is constantly changing. It’s not reasonable to pack both mutable and immutable code together, or at least cause the user to have to load the entire code when we repackage.
If we split the code and work with the browser’s caching mechanism, the user site only needs to load the updated business code, and the class library code does not need to be reloaded.
So that’s why we need to do code splitting. Let’s take a look at how code splitting can be configured in WebPack.
SplitChunksPlugin
Its configuration is one of the more complex and important configurations of the WebPack plug-in, so this article explains the meaning of its core configuration in detail.
There are two ways to introduce a third-party module:
Synchronization mode:
import { forEach } from "lodash";
import $ from "jquery";
$(function () {
forEach([1.2.3].(item) = >{
console.log(item); })});Copy the code
Asynchronous mode:
import("lodash").then(({default: _}) = >{
console.log(_.join(["a"."b"]."-"));
})
Copy the code
The SplitChunksPlugin plug-in already provides a default configuration out of the box that allows you to quickly split and package code for both of these modular introductions. Here’s what it means by default:
optimization: {
splitChunks: {
chunks: 'async'.minSize: 30000.minRemainingSize: 0.maxSize: 0.minChunks: 1.maxAsyncRequests: 6.maxInitialRequests: 4.automaticNameDelimiter: '~'.cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2.priority: -20.reuseExistingChunk: true}}}}Copy the code
chunks
async
Asynchronous module takes Effectinitial
Synchronization Module Takes Effectall
Asynchronous synchronization takes effect
minSize
For example, the default size is 30000 KB. Assuming that the size of the library to be packaged is smaller than 30000 KB, it will not be packaged by modules.
maxSize
Maximum package size, let’s say LoDash is 1MB, let’s say it’s 500KB, webPack will try to split LoDash into 2 files, but loDash libraries are hard to split, so the end result will be the same, just one package.
minChunks
How many times a module is used before it is code split.
maxAsyncRequests
The maximum number of chunks to load
maxInitialRequests
Maximum number of entry files to do code segmentation
automaticNameDelimiter
The concatenation of the file name
name
Filename in cacheGroups takes effect when it is set to true
cacheGroups
The cache group, in which defaultVendors and Default act as two module cache arrays. It is usually a synchronized imported module, which is pushed into the array when hit by the cache policy, and then merged into a chunk.
The cache policy is configured like this:
cacheGroups: {
vendors: {
chunks: 'initial'.// Only for synchronous modules
test: /[\\/]node_modules[\\/]/.// The synchronization code is packaged to determine whether it is under node_modules
priority: -10.It is possible to package a module according to both vendors' and default rules, and then the larger the vendors' value, the higher the precedence, is determined by the vendors' and vendors' rules
filename: '[name].chunk.js' // The output file name
},
default: {
minChunks: 2.// When the module is used twice
priority: -20.// Indicates the permission value
reuseExistingChunk: true // Check for circular references to avoid packing useless modules in}}Copy the code
Synchronization module packaging:
import { forEach } from "lodash";
import $ from "jquery";
$(function () {
forEach([1.2.3].(item) = >{
console.log(item); })});Copy the code
Analysis:
lodash
Module hitvendors
Strategy, push invendors
Policy cache group;jquery
Module also hitvendors
Strategy, push invendors
Policy cache group;- There are no other modules, so merge and output a file name
vendors~main.chunk.js
, includingverdors
Is the name of the strategy,~
Wavy lines isautomaticNameDelimiter
The configuration of themain
是entry
The name of the entry file,chunk.js
是filename
Suffix set in.
Asynchronous module packaging:
import(/* webpackChunkName: "lodash" */"lodash").then(({default: _}) = >{
console.log(_.join(["a"."b"]."-"));
})
import(/* webpackChunkName: "jquery" */"jquery").then(({default: $}) = >{$(function () {
console.log("Jquery has been loaded"); })})Copy the code
Analysis:
- First, modules are annotated according to magic in order to satisfy lazy loading requirements
webpackChunkName
Packaged into separate modules such asjquery.bundle.js
和lodash.bundle.js
- Again it’s going to go
cacheGroups
To check whether the corresponding policy is matchedvendors
It doesn’t match,default
Policies can match, butdefault
There is a configuration inreuseExistingChunk: true
Indicates that it will look for the module that has been packaged, and if it has been packaged it will be printed. Changed it tofalse
After, will putjquery.bundle.js
Rename as per policydefault~jquery.bundle.js
Since it is loaded asynchronously, the two modules on the home page will not be merged and output separately.
Policies in cacheGroups can be added on a project-by-project basis, so WebPack provides a variety of callback methods to make configuration more flexible.
CSS file code segmentation
With the increase of the project CSS files are very many, if all packaged into JS, it is bound to be js files too bloated, affecting the loading speed, so we have to separate the CSS package.
MiniCssExtractPlugin
It will help us create a new onecss
fileOptimizeCSSAssetsPlugin
It will help us merge compressioncss
file
Let’s take a look at the specific configuration:
.const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
mode: 'production'.// The configuration of minimizer is performed only in Production mode
optimization: {...minimizer: [
new OptimizeCSSAssetsPlugin({})
]
},
module: {rules:[
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader, // Use the loader provided by the MiniCssExtractPlugin
'css-loader']]}},plugins: [...new MiniCssExtractPlugin({ // Initialize the MiniCssExtractPlugin in the plugin and configure the naming rules for the separate CSS files
filename: "[name].css".chunkFilename: "[id].chunk.css"}})]Copy the code
Another important thing to remember is that in production we have Tree Shaking enabled by default, so you need to configure sideEffects in package.json, otherwise the CSS file will be Shaking off by Tree.
"sideEffects": ["*.css"] // All.css files are not tree shaking
Copy the code
After the configuration is complete, execute the package command to find that the CSS file can be isolated and the CSS file is compressed.
To configure the CSS cacheGroups
splitChunks: {
...
cacheGroups: {...styles: {
name: 'styles'.test: /\.css$/,
chunks: 'all'.enforce: true,},... }}Copy the code
Add a styles policy so that the output name is styles. CSS which is the name of this policy. Enforce: true means that the styles policy ignores other parameters of splitChunks.
Lazy loading of modules
Lazy loading of modules does not require webpack configuration, but is supported using the import() syntax provided by ES6
Let’s compare the following two pieces of business code:
document.addEventListener("click".() = >{
import(/* webpackChunkName: "lodash" */"lodash").then(({default: _}) = >{
console.log(_.join(["a"."b"]."-"));
});
},false);
Copy the code
import { join } from "lodash";
document.addEventListener("click".() = >{
console.log(_.join(["a"."b"]."-"));
},false);
Copy the code
The first piece of code says: click document to load the LoDash module asynchronously, and output a string when the module loads successfully.
The second code says: enter the interface first load loDash module, when click the page output string.
Obviously, the first code is an asynchronous loading mode. If the user does not click the page, it is unnecessary to load the corresponding module, saving resources. This is asynchronous loading, and all it takes is for WebPack to configure Babel to support ES6 syntax.
Preload and Prefetch
Let’s look at a business scenario like this:
The user clicks login, and the login dialog box is displayed. In fact, it is not necessary to complete the loading of the login pop-up module at the beginning, because it is an interactive content that must be carried out after the user sees the home page, which means that this module can be loaded after the home page is loaded.
How to achieve this effect? Browsers give us a way to load resources:
<link rel="preload" href="loginModule.js" as="script">
<link rel="prefetch" href="loginModule.js" as="script">
Copy the code
preload
It starts loading in parallel;prefetch
The home page module will be loaded after completion, and then to load.
To achieve this effect we don’t need to make any changes to the WebPack configuration and use the import() syntax provided by ES6 along with magic comments.
document.addEventListener("click".() = >{
import(/* webpackPrefetch: true */ /* webpackChunkName: "lodash" */ "lodash").then(({default: _}) = >{
console.log(_.join(["a"."b"]."-"));
});
},false);
Copy the code
View the browser console after executing the package command:
File caching policy
We packed static resource file is to be published on the server, such as static resource name for the main js, if online have a bug at this time, we must be fixed immediately and then immediately pack and static resource updates to the server, if you don’t change the file name, because the browser’s cache problem, the user is no way to see the effect immediately, So we can add a hash configuration to the file name
output: { // Export file
publicPath:"".filename: '[name].[hash].js'.chunkFilename:'[name].[hash].js'.path: path.resolve(__dirname,'dist')},Copy the code
Business Code:
import { forEach } from "lodash";
import $ from "jquery";
$(function () {
forEach([1.2.3].(item) = >{
console.log(item); })});Copy the code
After packaging, two JA files are output
main.[hash].js
Entry file ofvendors~main.[hash].js
的chunk
file
They share the same hash value, so when I modify the business code:
$(function () {
forEach([1.2.3.4.5.6].(item) = >{
console.log(item); })});Copy the code
Business code changed, but we introduce third-party libraries is no change, when executed again packing, two types of file hash values are changed, we deployed to the server, the user’s browser can indeed to reload and immediately see the effect, but the user should not reload the third library code, these but did not change. At this point we should use the [contenthash] configuration provided by WebPack, which means that only the module file hash value changes if the content changes, and the file hash value stays unchanged if the content does not change
output: {
publicPath:"".filename: '[name].[contenthash].js'.chunkFilename:'[name].[contenthash].js'.path: path.resolve(__dirname,'dist')}Copy the code
Development environment vs. build environment
In the WebPack configuration, the mode attribute is provided to configure the development environment and the production environment. Let’s summarize the differences between the two environments in the engineering configuration:
Function \ Environment | Develoment | Production |
---|---|---|
Code compression | Uncompressed (easy to debug) | Compression (reducing code size) |
Tree Shaking | Disabled by default | The default open |
Source Map | cheap-module-eval-source-map | cheap-module-source-map |
WebpackDevServer (local service) | Need to open | Don’t need |
HMR (Hot Update) | Need to configure the | Don’t need |
Normally when we write webPack configuration, we will do the configuration in separate files, because the production environment and development environment is very different.
Configuration file separation procedure
- Extract a common configuration, for example
js
To deal with,css
Processing, processing of images and other resources is the same in both development and production environments; - Configure a development environment and production environment configuration separately, and then pass
webpack-merge
Merge common configuration:
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common,{
mode: 'development'. });Copy the code
Configuring global variables
plugins: [
...
new webpack.ProvidePlugin({
$:"jquery"._:"loadsh"})]Copy the code
After the $and _ global variables are configured, we can use them directly without importing them in the subsequent module writing:
export function ui (){$('body').css('background'.'green');
}
Copy the code
Using environment variables
package.json
"scripts": {
"dev-build": "webpack --env.development --config webpack.common.js"."dev": "webpack-dev-server --env.development --config webpack.common.js"."build": "webpack --env.production --config webpack.common.js"
},
Copy the code
Added: –env.development, –env.production
webpack.common.js
module.exports = (env) = >{
console.log(env); // {development:true} || {production:true}
if(env && env.production){
return merge(commonConfig,prodConfig);
}else{
returnmerge(commonConfig,devConfig); }}Copy the code
View the specific configuration code
summary
Through the study of this article and hands-on practice, I believe that we will have a more comprehensive understanding of the basic configuration of Webpack, and lay a solid foundation for learning how to optimize and the principle of Webpack.
All code in this article is hosted
Please give me a thumbs up if you like this article