A quick review of webpack principles
Webpack can be thought of as a module packer, turning all parsed modules into an object, loading our stuff through the entry module, and then implementing recursive dependencies in turn, running all files through the entry module. Since WebPack only knows JS, it needs to be converted into a suitable format for the browser to run through a series of loaders and plugins.
-
Loader mainly preprocesses the loading and translation of resources. In essence, loader is a function that converts the received content and returns the converted result. Multiple Loaders can be used for a certain type of resource, executed from right to left and from bottom to top.
-
Plugins extend the functionality of WebPack and essentially listen for the entire packaging life cycle. Webpack is based on the event flow framework Tapable, which broadcasts many events during its life cycle. The Plugin can listen for these events and change the output when appropriate through the API provided by WebPack.
This article mainly introduces webpack5 configuration, according to the steps of the configuration side package comparison will be more impressive ~ later will attach the complete source code.
Webpack installation
Create a new directory, go to the directory to initialize package.json, and install the WebPack dependencies
NPM init -y installation dependency NPM I webpack webpack-cli -dCopy the code
Basic configuration
The default configuration file name for webpack is webpack.config.js, so create a new file named webpack.config.js in the root directory of the project and write the simplest single-page configuration in the configuration file:
let path = require("path");
module.exports = {
mode: "development",
entry: "./src/js/index.js",
output: {
filename: "js/bundle.js",
path: path.resolve("dist"),
publicPath: "http://cdn.xxxxx"
}
}
Copy the code
The configuration,
- Mode – Packaging mode
development
For development mode, the code is not compressed after packagingproduction
For production mode, the packaged code is compressed code
- Entry – Entry file
- Output – Package file configuration
filename
: After the file is packed, the value of filename can be set to bandhash
Stamped file:js/bundle.[hash].js
/js/bundle.[hash:8].js
(Only 8-bit hash stamp is displayed)path
: Specifies the path of the package file. The value must be an absolute pathpublicPath
: Indicates the online CDN address
TIP: Path is a built-in module in the above code. You don’t need to install it.
After creating the file, you need to create the index.js file in the SRC /js directory under the root directory of the project, and then enter a line of JS code.
After the configuration, you can use the webpack command to try to pack. If an error occurs and the command cannot be found, you can pack NPM I webpack -g after the global installation. After the package is successfully packaged, it will be output to the dist directory under the root directory of the project.
The project directory structure is roughly as follows
├ ─ package. Json ├ ─ webpack. Config. Js ├ ─ SRC | ├ ─ js | | └ index. The js ├ ─ distCopy the code
HTML file packaging
Since Webpack only knows JS, HTML files need to be packaged through the HTMl-webpack-plugin
npm i html-webpack-plugin -D
Copy the code
After installation in the webpack.config.js configuration file
let HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
})
]
}
Copy the code
Production mode enables compression of HTML files:
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: { removeAttributeQuotes: true, collapseWhitespace: true },
hash: true
})
]
Copy the code
The configuration,
- Plugins-webpack plug-in configuration
- HTML – wepack – plugin configuration
- Template-html Relative/absolute path to the template file
- Minify – Compression configuration
removeAttributeQuotes
: Deletes the attribute double quotation markscollapseWhitespace
: Code compressed into a single line
- Hash – Imports files with hash stamps
- HTML – wepack – plugin configuration
TIP: If you do not specify the template configuration, it will be the default HTML file of the plug-in, not the HTML file in the project
Open the service
Webpack starts the service by installing webpack-dev-server
npm i webpack-dev-server -D
Copy the code
Configuration webpack. Config. Js
devServer: {
port: 5000,
compress: true,
open: true,
client: { progress: true }
}
Copy the code
The configuration,
- devServer –
webpack-dev-server
configuration- Port – Indicates the port number
- Open compress –
gzip
The compression - Open – Automatically opens the page after startup
- client
progress
: Displays compilation progress as a percentage in the browser
Run the webpack-dev-server command to view the configuration. If the command cannot be found, run the NPM I webpack-dev-server -g command to install the server globally
Cross domain
You can configure devServer.proxy to solve cross-domain interface problems
Assumes that the interface address for http://localhost:3000/api/users, right/API/users can request the following configuration
devServer: {
proxy: {
'/api': 'http://localhost:3000',
},
},
Copy the code
But the address of the interface in the actual project there are many possible, generally will not have a directory/API, namely the general interface address for http://localhost:3000/users, thus enumeration configuration will be very trouble, can be solved through agent request
The request http://localhost:3000/api/users interface address first, then by devServer agent to http://localhost:3000/users
In this article, through express open interface service, interface address for http://localhost:3000/user, interface code are outside, the late upload the complete source code, The interface service can be started by using node “project path \ webPack5 \ SRC \js\server.js” and then configuring webpack.config.js
devServer: {
proxy: {
"/api": {
target: "http://localhost:3000/",
pathRewrite: {
"/api": ""
},
},
}
}
Copy the code
devServer
The configuration,
- Proxy-proxy configuration
- Target – Indicates the interface domain name
- PathRewrite – interface pathRewrite to proxy requests to the interface server
Mock interface data
When the back-end interface is not written properly and you do not want to block progress, you can use the interface data format agreed with the back-end to simulate the debug page. Can use a custom function and application. The ability to customize middleware configuration devServer setupMiddlewares, in middlewares. Unshift callback function in the use of res. Send the need to mock data passed in:
devServer: { setupMiddlewares: (middlewares, devServer) => { if (! devServer) { throw new Error("webpack-dev-server is not defined"); } middlewares. Unshift ({name: "user-info", // path is optional, path: "/user", middleware: (req, res) => {// mock data res.send({name: "moon mock"}); }}); return middlewares; }},Copy the code
Style to deal with
Loaders for style processing and their functions:
less-loader
Load and translate LESS filespostcss-loader
Use:PostCSSLoad and translate CSS/SSS files as can be handledautoprefixer
CSS package to add browser prefixes to CSScss-loader
: parsing@import
andurl()
Syntax, use import to load parsed CSS files and return CSS codemini-css-extract-plugin
的loader
: Extracts the CSS file and imports the HTML file with the link tag
If sASS is used, change the less less-loader to Node-sass sass-loader
npm i mini-css-extract-plugin css-loader postcss-loader autoprefixer less-loader less -D
Copy the code
Configuration webpack. Config. Js
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); Module. exports = {plugins: [new MiniCssExtractPlugin({filename: "CSS /main.css", // remove CSS filename})], module: {rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"], }, { test: /\.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, ] } }Copy the code
You also need to create and configure postcss.config.js
module.exports = {
plugins: [require("autoprefixer")]
};
Copy the code
After the above files are configured, you will find that the CSS3 style is still not prefixed after packaging, and you need to configure browserList of package.json to take effect
"browserslist": [
"last 1 version",
"> 1%",
"IE 10"
],
Copy the code
Js processing and grammar check
Es6 or higher syntaxes need to be converted to ES5 with esLint specification code:
babel-loader
: Load the ES2015+ code and use itBabelTranslated into ES5@babel/preset-env
: Basic ES syntax analysis package, unified setting of various translation rules, the purpose is to tell loader what rules to convert into the corresponding JS version@babel/plugin-transform-runtime
: Parses advanced syntax, such as generator, but excludes include syntax, which must be installed@babel/polyfill
. The official document says you need to bring it online@babel/runtime
In this patch, the package has also been optimized for method extraction, such as class syntax extraction (extraction of the classCallCheck method).@babel/polyfill
: Parses more advanced syntax, such aspromise
.include
Etc., in the JS filerequire
Introduction toeslint-loader
: check whether JS conforms to the specificationEslint websiteConfiguration download
Install dependencies
npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -@babel/polyfill -D
npm i @babel/runtime eslint-loader eslint -S
Copy the code
webpack.config.js
{ test: /\.js$/, use: { loader: "eslint-loader", options: { enforce: },},}, {test: /\.js$/, // Enforce default normal normal loader use: {loader: "Babel-loader ", options: {presets: ["@babel/preset-env"], // Set es6 to ES5 plugins: ["@babel/plugin-transform-runtime"], // }, }, include: path.resolve(__dirname, "src"), exclude: /node_modules/, },Copy the code
To configure the source – the map
Source map configuration source-map value:
- Source-map source code will be generated separately source-map file error will indicate the current error row and column large and complete
- Eval-source-map does not produce a separate file; rows and columns can be displayed
- Rowy-module-source-map does not identify columns and generates a separate mapping file
- Cheap -module-eval-source-map does not generate files Integration does not generate columns in packaged files
webpack.config.js
devtool: "eval-source-map",
Copy the code
Introduce JS global variables
There are three ways to introduce global variables
expose-loader
You can expose variables to global window objects, using jquery as an example
npm i jquery expose-loader -D
Copy the code
Then configure loader in webpack.config.js to expose $to the window global object
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: '$'
}]
}]
}
Copy the code
In addition to the above methods can also be exposed in the entry JS file
require("expose-loader? $! jquery");Copy the code
providePlugin
You can use the webapck built-in providePlugin to inject variables into each module, again using jquery as an example
Configure it in webapck.config.js
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'
});
]
}
Copy the code
You can then use the $call directly in any JS module without importing the jquery package
// in a module
$('#item'); // <= works
// $ is automatically set to the exports of module "jquery"
Copy the code
Import through CDN
You can also import global variables via CDN links, but if you add import $from ‘jquery’ to the js file, jquery will be included. External can be used to prevent some imported packages from being packaged into bundles
index.html
< script SRC = "https://code.jquery.com/jquery-3.1.0.js" integrity = "sha256 - slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk =" crossorigin="anonymous" ></script>Copy the code
webpack.config.js
module.exports = {
//...
externals: {
jquery: 'jQuery',
},
};
Copy the code
In other words, the code shown below works fine:
import $ from 'jquery'; $('.my-element').animate(/* ... * /);Copy the code
The example above. The attribute name is jquery, indicating that the jquery module in import $from ‘jquery’ should be excluded. To replace this module, the jQuery value will be used to retrieve a global jQuery variable. In other words, when set to a string, it is treated as global (defined above and below).
Style compression and JS compression
In production mode, csS-minimizer-webpack-plugin can be used to compress CSS. However, if CSS is compressed using this plugin, JS compression will not be compressed, so terser-webpack-plugin needs to be installed
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, minimizer: [new CssMinimizerPlugin (), / / compression js new TerserPlugin ({test: / \. Js (\? *)? $/ I}),],}},Copy the code
The image processing
Need loader to parse image resources:
file-loader
: parse the import/require () of the file into a URL, send the file to the output folder (dist folder), and return the (relative) URLurl-loader
: likefile-loader
Works the same, but can be returned if the file is smaller than the limitdata URLThat is, change the image to Base64html-loader
: Can parse images introduced by HTML tags, and specify which tag-attribute combination should be processed by querying attrs. Default value: attrs=img: SRC
Install dependencies
npm i file-loader url-loader html-loader -D
Copy the code
Configuration webpack. Config. Js
module: { rules: [ { test: /\.jpg|png|jpeg$/, use: { loader: "file-loader", options: { outputPath: "images/", name: "[name]. [ext]", / / if you don't write the file name, will generate random name / / publicPath: "http://cdn.xxx.com/images", / / can be configured in a production environment of CDN address prefix},,}}, {test: /\.(html)$/, use: { loader: "html-loader", options: { esModule: false, }, }, }, ] }Copy the code
TIP: Url-loader can be limited with options.limit. If the size is smaller than k, use base64 conversion. If the size is larger than k, use file-loader packaging
The html-loader configuration is incorrect
The HTml-loader must disable es6 modularization and use CommonJS for parsing. Otherwise, an error will be reported. The main reason is that the two Loaders parse images in different ways.
The project directory structure is roughly as follows
├ ─ eslintrc. Json ├ ─ package - lock. Json ├ ─ package. The json ├ ─ postcss. Config. Js ├ ─ webpack. Config. Js ├ ─ SRC | ├ ─ index. The HTML | ├ ─ js | | ├ ─ index. Js | | ├ ─ for server js | | └ test. The js | ├ ─ image | | └ logo. The PNG | ├ ─ CSS | | ├ ─ a.c ss | | └ index. The CSS ├ ─ distCopy the code
Resolve configuration
Resolve Common attribute configuration:
modules
: tells WebPack which directory to search for when parsing modules. Both absolute and relative paths can be used, but be aware that there is a slight difference between them.- Using an absolute path, searches are performed only in a given directory. Use relative paths by viewing the current directory as well as the ancestor path.
- If you want to search ahead of a target directory, you need to place that directory in front of the target directory
alias
: Set the alias for easy use. The following example applies tosrc
DirectorymainFields
: When importing a module from an NPM package (for example, import * as D3 from ‘D3’), this option determines which field to use in package.json to import the module. The default value varies depending on the target specified in the WebPack configuration. Here the Browser property is preferred because it is the first entry of mainFieldsextensions
: Try to resolve these suffixes in order. When an imported file has no suffix, and there are multiple files with the same name but different extensions, WebPack parses the file with the suffix listed at the top of the array and skips the rest.
let path = require("path");
module.exports = {
resolve: {
modules: [path.resolve("node_modules")],
alias: {
"@": path.resolve(__dirname, "src"),
},
mainFields: ["browser", "module", "main"],
extensions: [".js", ".json", ".vue"],
},
}
Copy the code
Multi-page configuration
Multiple pages, as the name implies, are multiple HTML pages, so there are usually multiple JS entry files.
In the following configuration, the key value of entry corresponds to the [name] value of the output property, and the property chunks in HtmlWebpackPlugin represent the js code file corresponding to [name]. Not specifying chunks will import all packaged JS files.
In this example, [name] is home and other respectively, that is, the package is home.js and other.js. The final package effect is that home.html imports home.js and other.html imports other.js
Configuration webpack. Config. Js
let path = require("path");
let HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: {
home: "./src/js/index.js",
other: "./src/js/other.js",
},
output: {
filename: "js/[name].js",
path: path.resolve("dist")
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "home.html",
chunks: ['home']
}),
new HtmlWebpackPlugin({
template: "./src/other.html",
filename: "other.html",
chunks: ['other']
}),
],
}
Copy the code
Webpack applet
clean-webpack-plugin
Cleanup plug-in, which can be used to clean up the last package file, and clear the value of the directory output.path
Install dependencies
npm i clean-webpack-plugin -D
Copy the code
Configuration webpack. Config. Js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
plugins: [
new CleanWebpackPlugin(),
]
}
Copy the code
copy-webpack-plugin
Copy the plug-in and export a folder to a package folder, such as a document folder (such as a doc folder)
Install dependencies
npm i copy-webpack-plugin -D
Copy the code
Configuration webpack. Config. Js
const CopyWebpackPlugin = require("copy-webpack-plugin"); Module.exports = {plugins: [new CopyWebpackPlugin({patterns: [{from: "./doc", to: "./doc",},],}),]}Copy the code
Plug-in configuration Properties
- patterns
- From: indicates the source file, relative to the current directory path
- To: The target file, relative to the output.path file path, will be generated in the dist/doc directory
webpack.BannerPlugin
Copyright notice plug-in, webpack built-in plug-in, no installation required
Configuration webpack. Config. Js
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.BannerPlugin("copyright by Moon in 2022"),
]
}
Copy the code
The packaged file begins with a copyright notice that goes something like this:
watch
You can listen for file changes and recompile as they change, which can be used in real-time packaging scenarios
Configuration webpack. Config. Js
Watch: true, watchOptions: {poll: 1000, // Check changes every second aggregateTimeout: 600, // Ignore: /node_modules/,},Copy the code
Configuration properties
watchOptions
Monitoring parameterspoll
: Checks for changes every n millisecondsaggregateTimeout
: buffeting, when the first file changes, adds delay before rebuilding. This option allows WebPack to aggregate any other changes made during that time into a rebuild. In millisecondsignored
: On some systems, listening for a large number of files can result in a large CPU or memory footprint. You can use the re to exclude imagesnode_modules
Such a huge folder
After configuration, enter NPM run build in the command window to monitor and package in real time, as shown in the picture
The environment variable
Define DEV environment variables through the webPack built-in plug-in DefinePlugin.
You can also separate webpack configurations that differ between development and production modes by splitting the webpack.config.js file into three
- Common configuration is placed in
webpack.config.base.js
file - The development mode configuration is placed
webpack.config.dev.js
File, approvedwebpack-merge
mergewebpack.config.base.js
Files andwebpack.config.dev.js
File configuration - The production mode configuration is placed
webpack.config.prod.js
File (logically consistent with development mode configuration file)
The complete code of the webpack.config.dev.js file is as follows:
let { merge } = require("webpack-merge"); let base = require("./webpack.config.base.js"); let HtmlWebpackPlugin = require("html-webpack-plugin"); const webpack = require("webpack"); module.exports = merge(base, { mode: "development", devtool: "eval-source-map", plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", }), new webpack.DefinePlugin({ ENV: JSON.stringify("dev"), }), ], devServer: { compress: true, client: { progress: true }, port: Mock setupMiddlewares: (Middlewares, devServer) => {if (! devServer) { throw new Error("webpack-dev-server is not defined"); } middlewares. Unshift ({name: "fist-in-array", // 'path' is optional path: "/user", middleware: (req, res) => { res.send({ name: "moon mock" }); }}); return middlewares; ,}}});Copy the code
Using environment variables, the directory structure is roughly as follows
├─.eslintrc.json ├─ Package.lock. json ├─ Package.config.js ├─ WebPack.config.base.js ├─ WebPack.config.dev ├ ─ webpack. Config. Prod. Js ├ ─ SRC | ├ ─ index. The HTML | ├ ─ js | | ├ ─ index. The js | | ├ ─ for server js | | └ test. The js | ├ ─ image | | └ logo. PNG | ├ ─ CSS | | ├ ─ a.c ss | | └ index. The CSS ├ ─ doc | └ notes. The md ├ ─ distCopy the code
After the configuration file is changed, the packaging command should also be adjusted appropriately. You need to specify the configuration file when packaging:
// Webpack --config webpack.config.dev.js // webpack --config webpack.config.prod.jsCopy the code
Production mode configuration file and public configuration file source uploaded later
Hot update
Hot updates to Webpack are also known as Hot Module Replacement, or HMR. This mechanism allows you to replace the old module with the new one without refreshing the browser. Enabled by default hot update, no configuration, it will be automatically applied webpack. HotModuleReplacementPlugin, it is necessary to enable the HMR.
To optimize the
The following configuration code is in the WebPack configuration file and will not be described here
module.noParse
Since WebPack parses import files, requires referenced packages, and analyzes package dependencies, but some packages don’t have dependencies, noParse can improve build performance by not resolving dependencies in a referenced package. Suitable for packages without dependencies, such as jquery
module: {
noParse: /jquery/,
}
Copy the code
webpack.IgnorePlugin
The built-in WebPack plug-in IgnorePlugin can prevent the generation of modules for import or require the invocation of modules that match regular expressions or filter functions. For example, the moment package contains many language packages, which are stored in the locale folder. However, most scenarios only reference one language package, so you can ignore the locale language package in the moment directory
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
Copy the code
It works if you omit it and reintroduce a language package into the JS file
import "moment/locale/zh-cn";
moment.locale("zh-cn");
Copy the code
Pull out the common code
It is usually used in multi-page application scenarios or when a single JS file is too large and requires a long time to request. It is necessary to split several JS files and optimize the request speed by using Optimization’s splitChunks attribute.
Splitchunks. cacheGroups The cache group can inherit and/or override any option from splitchunks. *. But test, Priority, and reuseExistingChunk can only be configured at the cache group level. Set them to false to disable any default cache groups.
Understand several properties of splitChunks before looking at the following configuration:
priority
: Indicates the priority of the pull-out code. The higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value isvendor
Module iscommon
The module is pulled out and not pulled outname
: The file name of each chunk. It is not defined as a random nametest
: Matching directorychunks
: Selects which chunks to optimizeinitial
: Extracts the code from the entry, and considers the latter two values if there are asynchronous modulesasync
: Asynchronous moduleall
: Both asynchronous and non-asynchronous modules can exist
minSize
: Indicates the minimum size of chunk to be generated. Set this parameter to 0 for testingminChunks
: The minimum number of chunks that must be shared before splitting, and the number of times that the current code block must be referenced before being removed. Set this parameter to 1 for convenience
In this example, chunk Common and Chunk Vendor are divided
Optimization: {// splitChunks: {// cacheGroups: {// common module Commons: {name: "common", chunks: "initial", minSize: 0, minChunks: 1, }, vendor: { name: "vendor", priority: 1, test: /[\\/]node_modules[\\/]/, chunks: "All ", // includes both asynchronous and non-asynchronous code blocks},},},},Copy the code
To facilitate your understanding, present the packaged directory tree structure
├ ─ index. The HTML ├ ─ js | ├ ─ common. Js | ├ ─ common. Js. LICENSE. TXT | ├ ─ main. Js | ├ ─ main. Js. LICENSE. TXT | ├ ─ vendor. Js | └ vendor. Js. LICENSE. TXT ├ ─ images | └ logo. The PNG ├ ─ doc | └ notes. The md ├ ─ CSS | └ main. The CSSCopy the code
This piece is more difficult to understand, it is suggested to try packaging several times to compare the difference to understand
Lazy loading
Lazy loading is implemented through ES6’s import() syntax, dynamic loading of files is implemented through JSONp, and import functions return promise objects. Vue lazy loading, react lazy loading is implemented in this way. For a simple example, some JS files are loaded after a button is clicked.
The code that gets the button DOM element object is omitted here. Button.addeventlistener ('click', function(){ import('./test.js').then(data => { console.log(data); })})Copy the code
Webpack comes with optimizations
tree-shaking
Import automatically removes unnecessary code in production, that is, tree shaking. The require syntax does not support tree-shaking
scope hosting
Scope hosting, for example:
let a = 1
let b = 2
let c = 3
let d = a+b+c
console.log(d)
Copy the code
The code is packaged with only the last sentence, and webpack automatically omits any code that can be simplified.
Hand-written simple less-loader
less-loader.js
Import the less plug-in in the /loaders/less-loader.js directory file
const less = require("less");
function loader(source) {
let css = "";
less.render(source, function (err, res) {
css = res.css;
});
}
module.exports = loader;
Copy the code
webpack.config.js
Write the following configuration
resolveLoader: {
alias: {
"lessLoader": path.resolve(__dirname, "loaders", "less-loader"))
}
},
module: {
rules: [
{
test: /\.less/,
use: ["style-loader", "lessLoader"]
}
]
}
Copy the code
The last
Complete source code post upload, to be continued…