preface
Then the basic and advanced chapters will start with the webpack.config.js file and expand the configuration to show you how WebPack works step by step. Webpack will be shown in five ways:
The entrance (entry)
Exports (output)
Preprocessor (loader)
The plug-in (plugins)
Mode (mode)
Before you start, ask yourself a question: Where do the WebPack source files come from, and who does the output go to once you’ve assembled it?
Entrance & Exit
Resources to deal with
The webapck story begins by specifying one or more entries, which is the origin of the story. If Webpack is a tree, then the entry is equivalent to the root of a tree. Indeed, Webapck retrieves from the entry file and generates a dependency tree for the modules that have dependencies, eventually generating a chunk(literally a chunk of code, which in Webpack can be understood as abstracted and wrapped modules). Bundles obtained by Chunk are generally called bundles, and their relationship is as follows:
entry -> module -> bundle
The entrance
The context and Entry configuration items in Webpack together determine the location of the entry file. When you configure an entry, you actually do two things:
- Determine entrance position
- Definition: the chunk name
context
Base directory, an absolute path used to resolve entry points and loaders from the configuration
Context can be understood as a path prefix for a resource entry, and must be configured in the form of an absolute path, which must be a string.
const path = require('path');
module.exports = {
context: path.resolve(__dirname, './src')
enrty: './compontens/index.js
};
module.exports = {
context: path.resolve(__dirname, './src/compontens')
enrty: './index.js
};
Copy the code
The main reason for this equivalence is to make entry more concise. Context can be omitted
entry
Instead of context being a string, entry can be a string, an array, an object, or a function.
string [string] object = { <key> string | [string] | object = { import string | [string], dependOn string | [string], filename string }} (function() => string | [string] | object = { <key> string | [string] } | object = { import string | [string], dependOn string | [string], filename string })
Copy the code
- string
module.exports = {
entry: "./src/index.js",
output: {
filename: "./bundle.js"
},
mode: "development",
};
Copy the code
- An array type
Note: The function of pass array is to pre-merge multiple resource files. When packaging, Webpack will use the last element in the array as the actual entry path.
module.exports = { entry: ['react', './src/index.js'], output: { filename: "./bundle.js" }, mode: "development", }; Module.exports = {entry: './ SRC /index.js', output: {filename: "./bundle.js"}, mode: "development",}; index.js import 'react',Copy the code
If you want to define multiple entries, you must use object types
module.exports = { entry: { index: './src/index.js', lib: './src/lib.js', }, }; Object value can also be an array module. Exports = {entry: {index: [' react ', '. / SRC/index. Js'], and lib: '. / SRC/lib. Js',},};Copy the code
- Function types
Function types support asynchronous operation and dynamic loading
module.exports = { entry: () => ({ index: ['react', './src/index.js'],, lib: './src/lib.js', }), }; Async module.exports = {entry: () => new Promise((resolve) => {setTimeout(() => {resolve('./ SRC /index.js')})}),}; Dynamic loading module. Exports = {entry () {/ / from an external source (remote server, the file system or database) to obtain actual item return fetchPathsFromSomeExternalSource (); }};Copy the code
vendor
As the project gets bigger and bigger, once the code is updated, even with minor changes. Users have to download the entire resource file, which is very performance unfriendly. To solve this problem, we can use the extract Vendor method. Vendor means a pair of suppliers. In Webpack, it usually means a bundle of third-party modules. Extract the vendor
module.exports = {
entry: {
index: './src/index.js',
vendor: ['react','react-dom','react-redux'],
},
};
Copy the code
We did vendor extraction, so the problem is, we didn’t set the entry path for vendor, how do we pack webPack? This time we can use optimization splitChunks, public modules are extracted from the index and the vendor. With this configuration, the bundle generated by index will contain only business modules, and the third-party modules it depends on will be extracted to form a new Bundel, thus achieving our purpose of extracting vendor. Since this part doesn’t change very often, the client cache can be effectively utilized to speed up the overall rendering speed during subsequent page requests.
exit
There are more than 10 configuration items to be exported. Only a few common configuration items are detailed here. Let’s start with a simple example:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist/',
}
};
Copy the code
filename
Filename displays the filename of the resource in the form of a string.
output: {
filename: 'bundle.js',
}
};
Copy the code
It can also be a file path:
output: {
filename: './dist/bundle.js',
}
};
Copy the code
We can also specify a different name for each bundle. Webpack uses a template like language to dynamically generate file names:
output: {
filename: '[name].js',
}
};
Copy the code
If you want to control the client cache, it is better to add [chunkhash], because the chunkhash generated by each chunk is only related to its own content, and the change of a single content will not affect other resources, so the client cache can be updated precisely.
output: {
filename: '[name]@[chunkhash].js',
}
};
Copy the code
path
Path specifies the output location of the resource. Its value must be an absolute path. See the examples:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
}
};
Copy the code
In the example, the output path is set to the dist directory, and after Webpack4, the packaging path is set to dist by default
publicPath
PublicPath is easily confused with PATH
Path specifies the file output location and publicPath specifies the resource request location
The instance
Single entry
const path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', path: Path.resolve (__dirname, 'dist'), // webpack4 default configuration can be omitted}};Copy the code
Multiple entry
const path = require('path'); module.exports = { entry: { pageA: './src/pageA.js', pageB: './src/pageB.js' }, output: { filename: '[name].js', path: Path.resolve (__dirname, 'dist'), // webpack4 default configuration can be omitted}};Copy the code
Preprocessor (loader)
Looking back, we talked about javascript packaging, but a project can’t just have javascript files, HTML, CSS, less, SCSS, images, fonts, templates, etc. What does Webpack do? How does WebPack manage precompilation in the same way? Loader, which endows Webpack with the ability to deal with different resource types, greatly enriching the scalability of Webpack
For Webpack, everything is a module, we can load CSS as we load JS, we can maintain JS and CSS files in the same JS file. Modules are highly cohesive and reusable structures that can be applied to every static resource through webapck’s idea of everything as a module. Let’s unveil loader
What is the loader
Before we start, what is loader? Each loader is essentially a function. Before Webpackage 4, the input and output of functions had to be strings; After Webpack4, Loaders also support abstract syntax tree (AST) passing as a way to reduce the parsing of duplicate code.
output = loader(input)
The input may be a string from a project file or the result of a previous loader conversion, which is chained
Output = loaderA (loaderB (loaderC (input)));
Let’s take a look at the loader source code structure
module.exports = function loader (content, map, meta) { var callback = this.async(); var result = handle(content, map, meta); Callback (null, result.content, // converted content result.map, // converted source-map result.meta, // converted AST)}Copy the code
You can see that the Loader itself is a function that transforms the received content.
Start by packaging a CSS file
Add the main. CSS file to the SRC directory with the following structure:
└ ─ webpackDemo · · · · · · · · · · · · · · · · · · · · · · · · sample root dir ├ ─ ─ the SRC · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · source dir + │ └ ─ ─ . The main CSS · · · · · · · · · · · · · · · · · · · · · · · · · main styles │ └ ─ ─ index. The js · · · · · · · · · · · · · · · · · · · · · · · · · index js ├ ─ ─ package. The json · · · · · · · · · · · · · · · · · · · · · · · · · package file └ ─ ─ webpack. Config. Js · · · · · · · · · · · · · · · · · · · · webpack config fileCopy the code
main.css
/* ./src/main.css */
body {
margin: 0 auto;
padding: 100px;
background: yellowgreen;
}
Copy the code
Then add CSS to index.js
import HelloWorld from "./hello-world";
import "./main.css";
document.write("My first webpack app <br />");
HelloWorld();
Copy the code
After the configuration is complete, go back to the command line terminal and run the Webpack command again. At this point, you will find that the command line reported a module resolution error, as follows:Webapck can’t handle CSS syntax, so it throws an error and tells you that a proper loader is needed to handle this file: a loader that can load CSS modules is required, the most commonly used is CSS-Loader. We need to install the Loader through NPM, and then add the corresponding configuration in the configuration file. The specific operation and configuration are as follows:
$ npm install css-loader --save-dev # or yarn add css-loader --dev module.exports = { entry: "./src/index.js", output: {filename: "./bundle.js"}, module: {rules: [{test: /\.css$/, // "Css-loader" // specify loader}]}, mode: "development", devServer: {publicPath: "./dist" // publicPath: ".Copy the code
Add a Rules array to the Module property of the configuration object. This array is our load rule configuration for the resource module, where each rule object requires two properties:
-
The first is the test property, which is a regular expression that matches the path of the file encountered during packaging. Here we end with.css;
-
Then there is the use attribute, which specifies the loader to use for the matched file, in this case CSS-loader.
After the configuration is complete, there will be no error when executing the package command. The process is as follows:
css -> css-loader -> webpack -> bundle
However, you will find that CSS styles do not take effect even though the packaging is successful, because csS-loader only handles the loading syntax of CSS. To make CSS work, we also need style-loader to insert styles into the page. To combine csS-loader and style-loader, we need to install style-loader first
$ npm install style-loader --save-dev # or yarn add style-loader --dev module.exports = { entry: "./src/index.js", output: { filename: "./bundle.js" }, module: { rules: [ { test: Use: ["style-loader", "css-loader"]}]}, mode: "Development ", devServer: {publicPath: "./dist"}};Copy the code
After the configuration is complete, go back to the command line to repackage, and the bundle.js file will have two additional modules. All style modules loaded in THE CSS -loader are added to the page by creating style tags.
Don’t forget to re-npm Run Build!
So the style will take effect, and you can see the style tag, which contains the CSS style, and we’re done with the configuration to load the CSS file from the JS file
exclude & include
The loader configuration excludes all regular-matched modules from the rule change, for example
Rules: [{test: /\.css$/, // Whether to use this loader according to the file path match encountered during the packaging process // Use multiple loaders for the same module, pay attention to the order: ["style-loader", "css-loader"], exclude: /node_modules/, } ]Copy the code
Node_modules is excluded, and exclude is mandatory in configuration, otherwise it will slow down the overall packaging speed
Include means that this rule applies only to regular matches to modules. For example, we set include to the source SRC directory, which naturally excludes node_modules. If both exist, exclude takes a higher priority.
Commonly used loader
babel-loader https://webpack.js.org/loaders/babel-loader html-loader https://webpack.js.org/loaders/html-loader file-loader https://webpack.js.org/loaders/file-loader url-loader https://webpack.js.org/loaders/url-loader style-loader https://webpack.js.org/loaders/style-loader css-loader https://webpack.js.org/loaders/css-loader sass-loader https://webpack.js.org/loaders/sass-loader postcss-loader https://webpack.js.org/loaders/postcss-loader eslint-loader https://github.com/webpack-contrib/eslint-loaderCopy the code
Custom loader
Sometimes the existing loader cannot meet our requirements, so we need to modify it. Now let’s implement a Loader from scratch. My requirement here is to develop a loader that can load markdown files so that MD files can be imported directly into the code. We should all know that markdown usually needs to be converted to HTML before rendering to the page, so I want to import the MD file and get the markdown converted HTML string directly,
Each Webpack Loader needs to export a function, which is the processing process of our Loader for resources. Its input is the content of the loaded resource file, and the output is the result after processing. We receive input through the source parameter and output through the return value.
The first step
There are two ways to import loader: NPM installation and direct import.
Create a markdown-loader.js file directly in the project root directory. When you’re done, you can publish this module to NPM as a standalone module. Now that we have an overview of how Loader works, let’s go back to markdown-loader.js and finish my requirements. You need to install a module that parses Markdown into HTML, called marked. We then marked our markdown syntax and the result was an HTML string.
// ./markdown-loader.js const marked = require("marked"); module.exports = source => { // 1. Const HTML = marked(source); // html => '<h1>About</h1><p>this is a markdown file.</p>' // 2. Exports = 'module.exports = ${json.stringify (HTML)}'; return code; // code => 'export default "<h1>About</h1><p>this is a markdown file.</p>"' };Copy the code
The second step
Creating an MD file
<! -- ./src/markdown.md --> # markdown this is a markdown file.Copy the code
Import the MD file in the index.js file
import markdown from "./markdown.md";
document.write(markdown);
Copy the code
Next, modify the configuration and import the Loader
module.exports = { entry: "./src/index.js", output: { filename: "./bundle.js" }, module: { rules: [ { test: Use: ["style-loader", "css-loader"]}, {test: ["style-loader", "css-loader"]} /\.md$/, // Use: "./markdown-loader.js"}]}, mode: "development", devServer: {publicPath: "./dist" // what is publicPath?}};Copy the code
The third step
Execute the package command and open the browser preview
npm run build
npm run dev
Copy the code
You can see that the Markdowm syntax displays correctly:
Multiple loaders
We can also try the second approach, which is to return the HTML string directly in our markdown-loader and pass it to the next loader. This involves multiple Loaders working together.
// ./markdown-loader.js const marked = require('marked') module.exports = source => { // 1. Const HTML = marked(source) return HTML}Copy the code
Install an HTMl-loader, which handles HTML, as shown below:
npm install html-loader --save-dev module.exports = { entry: "./src/index.js", output: { filename: "./bundle.js"}, module: {rules: [{test: /\.css$/, // Whether to use this loader according to the file path match encountered during the packaging process // Use multiple loaders for the same module, pay attention to the order: [" style - loader ", "CSS - loader"]}, {test: / \. Md $/, / / use relative path directly use: [" HTML - loader ", ". / markdown - loader "]}]}, mode: "Development ", devServer: {publicPath: "./dist"}};Copy the code
Once installed, package, compile, and you’ll find that your code still works.
Loader is one of the core functions of Webpack. With loader, Webpack can load the resources you want to load.
The plug-in (plugins)
Loader is responsible for completing the loading of various resource modules in the project, so as to realize the modularization of the overall project, while Plugin is used to solve other automation work in the project except the packaging of resource modules. Therefore, Plugin has a wider range of capabilities and more natural uses. So what can we do with plug-ins?
Implement automatic cleanup of the dist directory before packaging (last packaging result) Automatic generation of application required HTML files; Inject potentially variable parts of the code, such as API addresses, depending on the environment; Copy resource files that you do not need to participate in packaging to the output directory. Compress the output of Webpack after it is packaged;Copy the code
Now let’s step into common plug-ins
clean-webpack-plugin
As you may have noticed, Webpack is overwritten directly into the dist directory every time it is packaged. And before packaging, some may have been deposited in the dist directory in the last left over from the packaging file, when we packed again, only can overwrite files with the same, and those who have been removed resource files will have been accumulated in the inside, there was a redundant files, eventually led to the deployment of online this is clearly unreasonable.
It makes more sense to automatically clean up the Dist directory before each complete packaging so that only the necessary files are in the dist directory after each packaging.
The installation
npm install clean-webpack-plugin --save-dev
Copy the code
Import the file in webpack.config.js after installation
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); module.exports = { entry: "./src/index.js", output: { filename: "./bundle.js" }, module: { rules: [ { test: Use: ["style-loader", "css-loader"]}, {test: ["style-loader", "css-loader"]} Use: ["html-loader", "./markdown-loader"]}]}, plugins: [new CleanWebpackPlugin()], mode: "Development ", devServer: {publicPath: "./dist"}};Copy the code
OK, now let’s test, repackage to see if we can delete the Dist file, and you can add something to the bundle to see if it’s empty after you’ve packaged it.
html-webpack-plugin
When the project was published, we needed to publish the HTML files in the root directory and all the packaged results in the dist directory at the same time, which was very cumbersome, and once we were live, we had to make sure that the resource file paths in the HTML code were correct. If the directory or file name of the packaged output changes, the corresponding script tag in the HTML code also needs to be manually changed.
The best way to solve these two problems is to have Webpack automatically generate the corresponding HTML files at the same time as packaging, and let the HTML files also participate in the whole project construction process. This way, Webpack can automatically introduce the packaged bundles into the page during the build process.
The installation
npm install html-webpack-plugin --save-dev
Copy the code
Import the file in webpack.config.js after installation
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin()
]
Copy the code
Finally, we go back to the command line terminal and run the package command again. During the package process, an index. HTML file is automatically generated in the dist directory. When we find this file, we can see that the contents of the file are blank HTML using bundle.js. The result is as follows:
<! DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Webpack App</title> <meta name="viewport" content="width=device-width, initial-scale=1"></head> <body> <script src="./bundle.js"></script></body> </html>Copy the code
We can also configure the HtmlWebpackPlugin:
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "Webpack Plugin Sample",
meta: {
viewport: "width=device-width"
}
})
],
Copy the code
Custom plugins
Before we simply implement a loader, know the basic principle of loader:
- Loader exports a function
- Returns a string after internal processing of the argument source file
So how do we write a plugins?
Hook mechanism
The plugin mechanism for Webpack is the most common hook mechanism we use in software development and the hook mechanism is very easy to understand, it is somewhat similar to events in the Web. Or the life cycle in React vue. In order to facilitate the extension of plug-ins, Webpack has a hook buried in almost every link. This allows us to easily extend the capabilities of Webpack by mounting different tasks on these different nodes as we develop the plug-in.
For details about the predefined hooks, we can refer to the API documentation:
Compiler Hooks
Compilation Hooks
JavascriptParser Hooks
For those interested, let’s implement a plug-in that automatically clears comments in Webpack results.
The first step
Create a remove-comments-plugin.js file in the root directory
Webpack requires that our plug-in be either a function or an object that contains apply methods, and we typically define a type within which apply methods are defined. This type is then used to create an instance object to use the plug-in. We define a RemoveCommentsPlugin type, and then define an apply method in this type. This method is called when Webpack starts, and it takes a Compiler object parameter, This object is the most core object in the working process of Webpack, which contains all the configuration information we build this time. It is through this object that we register the hook function, the specific code is as follows:
//./remove-comments-plugin.js class RemoveCommentsPlugin {apply (compiler) {console.log('RemoveCommentsPlugin starts ') // Compiler => contains all configuration information for our build}}Copy the code
The second step
How do I remove comments from packaged files? Should I wait until the package is complete before executing the hook function? So we use a hook called emit, which is executed when Webpack is about to export files to the output directory. Access the EMIT hook via the hooks attribute of the Compiler object, and register a hook function via the tap method, which takes two arguments:
- The first is the name of the plug-in, which in our case is RemoveCommentsPlugin.
- The second is the function to be mounted to the hook;
Let’s print for in and look at the name of each file
//./remove-comments-plugin.js class RemoveCommentsPlugin {apply(compiler) {console.log("MyPlugin starts "); compiler.hooks.emit.tap("RemoveCommentsPlugin", Compilation => {// compilation => for (const name in compilation.assets) {console.log(name); // Output file name}}); } } module.exports = RemoveCommentsPlugin;Copy the code
Introduce plug-ins in weback.config.js
const RemoveCommentsPlugin = require("./remove-comments-plugin");
plugins: [
new RemoveCommentsPlugin(),
],
Copy the code
After running, you can see that the contents of our plug-in file are correctly executed
The third step
Once we have the file name and content, we go back to the code. We need to check if the file name ends in.js, because Webpack may also output other files, and we only need to process js files.
We take the contents of the file, remove the comments in the code by means of re substitution, and finally overwrite the corresponding object in compilation.assets. Within the overwritten object, we also expose a source method to return the new content. We also need to expose a size method to return the size of the content, which is the format required within Webpack, as follows:
// ./remove-comments-plugin.js class RemoveCommentsPlugin { apply(compiler) { compiler.hooks.emit.tap("RemoveCommentsPlugin", For (const name in compilation.assets) {if (name.endswith (".js")) {// compilation => {// compilation => const contents = compilation.assets[name].source(); const noComments = contents.replace(/\/\*{2,}\/\s? /g, ""); compilation.assets[name] = { source: () => noComments, size: () => noComments.length }; }}}); } } module.exports = RemoveCommentsPlugin;Copy the code
Run the command to view the bundle
The key point we found by implementing the plug-in ourselves was to use the lifecycle hook to mount the task function to complete a plug-in point implementation.
Mode (mode)
By selecting one of development, Production, or None to set the mode parameter, you can enable the optimizations built into WebPack for that environment. The default value is production. The first way to pass mode is to set the mode value in webpack.config.js
module.exports = {
mode: 'production'
};
Copy the code
Or from the CLI parameter:
webpack --mode=development
Copy the code
If not, Webpack sets mode to production by default. We can use mode to configure the development environment and set the configuration of different environments.
conclusion
The advanced chapter introduces the basic WebPack configuration from the five core configuration properties. Webpack attributes can not be all introduced, it is more important to understand them, know the basic principle of its implementation, to do know ~
The preparation of this article referred to
Webpack principles and practices
Webapck of actual combat
Related series
Webpack base paper