This is a basic tutorial designed to give you a quick overview of the main features of WepBack, as well as a personal way to organize your notes while learning webPack.

Stubborn Bronze — First introduction to Webpack

Initialize the project

Create the project folder, execute the following command to generate package.json file and initialize the project.

npm init
Copy the code

The package.json file is shown below, which contains some basic information about the project.

{" name ", "aaa", "version" : "1.0.0", "description" : ""," main ":" index. Js ", "scripts" : {" test ", "echo \" Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" }Copy the code

Install webpack

// In Webpack 4, webpack kernel and webpack-CLI are separated, so both Webpack and webpack-CLI are installed.Copy the code

-d is short for NPM install –save-dev, which installs the module and saves it to the devDependencies property in pageage.json, which indicates the module needed for project development.

-s is short for NPM install –save, which installs the module and saves it to the Dependencies property in pageage.json. The dependencies field specifies the module on which the project runs.

The devDependencies attribute in package.json is as follows:

"DevDependencies" : {" webpack ":" ^ 5.4.0 ", "webpack - cli" : "^ 4.2.0"}Copy the code

The root directory creates the SRC folder and main.js file.

Node_modules \.bin\ webpack.src \main.js, and the compilation succeeded. At this time, dist folder and its sub-file main.js are added in the root directory, which is the packaged file.

Note that:

Change the “/” to “\” when packaging the command line. Otherwise, the message “Not an internal or external command, runnable program or batch file” is displayed. node_modules\.bin\webpack src\main.js

On the command line, / follows the argument used by the command, and \ is the path to the file.

In webpack2, we will not have this warning, but in Webpack4, we will not have this warning. Mode becomes an important concept (WebPack will turn on some configuration for us by default based on the mode we set). This can be resolved by following the command with –mode development specifying development environment or –mode production specifying production environment.

node_modules\.bin\webpack src\main.js --mode development
Copy the code

If you really don’t want to type more words, you can use NPX webpack instead of node_modules\.bin\webpack. What is NPX?

Why don’t we specify a path and file name to package the output when WebPack can package the output? Zero configuration packaging is supported starting with Webpack4 (fixed entry SRC /index.js and fixed exit dist/main.js). Let’s see what it looks like:

Const path = require('path') module.exports = {entry: path.resolve(__dirname, 'SRC /index.js'), { filename: main.js, path: path.resolve(__dirname, 'dist') } }Copy the code

Node_modules \.bin\webpack can be packed directly without adding files or paths. If it is not SRC /index.js, you need to specify an entry file, such as our.node_modules.bin webpack.src main.js; An error will be reported if there is no SRC /index.js file and no entry file is specified when packaging.

Context Configures the root directory

Const path = require('path') module.exports = {context: __dirname, // defaults to the current working directory entry where webpack is started: Path.resolve (__dirname, 'SRC /index.js'), // package entry}Copy the code

If context is set to path.resolve(__dirname, ‘SRC ‘), finding relative path files is always described as relative to the context. For example, entry above could be written as entry: ‘./index.js’.

Entry configuration

Entry is an entry to packing. It can be written in several ways:

Resolve (__dirname, 'SRC /main.js') 2.1 Object format, generally used in multi-page applications: Entry: {main: Path.resolve (__dirname, 'SRC /main.js')} 2.2 entry: {main: path.resolve(__dirname,' SRC /main.js'), search: Path.resolve (__dirname, 'SRC /search.js')} 3.2 Entry: { main: ['/src/main.js', './src/search.js'] search: ['/src/main2.js', './src/search2.js'] } 4. Function form: The function returns any of the above threeCopy the code

The value is a string

entry: path.resolve(__dirname, 'src/main.js')
Copy the code

Is equivalent to

entry: {    
    main: path.resolve(__dirname, 'src/main.js')
}
Copy the code

The default is an object with a key of main.

Array format The final file is produced in array order. For example, if main: [‘/ SRC /a.js’, ‘./ SRC /b.js’], a.js prints a and b.js prints B, then the final output file main.js prints a and B. Typically used for single-page applications. Sometimes used for JS dynamic compatibility.

Entry represents the entry for packaging. When webPack is packaging, it will start from the files specified in the entry. These entry files may import other files through the form of import, and other files may also import other files… This forms a dependency graph, which is like a tree, which regards the roots as the entrance, so for a tree, it has many branches, although there are many, but starts from the entrance, you can find all the branches. Find this process, by webpack to help us do.

The output configuration

Output represents the exit for packaging, and you can see that packaging when we don’t have any configuration creates the dist folder in the root directory and outputs the main.js file.

Output has two basic attributes, filename indicates the name of the (path) file to output and path indicates the public directory for the (all) output of the resource.

We can specify the packaged output path with the -o command.

.\node_modules\.bin\webpack .\src\main.js  -o  .\disttemp
Copy the code

Webpack5 added –output-filename to specify the output filename.

.\node_modules\.bin\webpack .\src\main.js --output-filename main2.js
Copy the code

In previous versions, you could specify both the output file path and the file name with -o.

.\node_modules\.bin\webpack .\src\main.js  -o .\disttemp\main2.js
Copy the code

NPM script

Is there a short way to input a string of code (.\node_modules\.bin\ webpack.src \main.js) for each package?

NPM run, if run without any arguments, lists all script commands that can be executed in package.json.

NPM has two built-in command abbreviations, NPM test and NPM start, which are short forms of NPM run XXX.

NPM run creates a shell, executes the specified command, and ** temporarily adds node_modules/.bin to the environment variable PATH. ** This is how we can access the module directly in NPM script.

"scripts": {    
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack ./src/main.js"
}
Copy the code

/node_modules/.bin is installed to create soft links, and NPM script can access commands directly. NPM run dev will execute the package command.

Order Silver – WebPack configuration file

We create the webpack configuration file in the root directory. The default file is webpack.config.js:

Write the default configuration we just guessed:

const path = require('path')
module.exports = {
    entry: path.resolve(__dirname, 'src/main.js'),
    output: {
        filename: main.js,
        path: path.resolve(__dirname, 'dist')
    }
}
Copy the code

You could also write:

 entry: {
     main: path.resolve(__dirname, 'src/main.js')
 }
Copy the code

When we do not have a configuration file, we execute the webpack command to pack and specify an explicit entry. When we configure webpack.config.js, we only need to keep the webpack in the package.json command webpack. / SRC /main.js. Webpack automatically reads the configuration file for packaging.

What if I change the name of the configuration file?

Huh? Mice fed juice

It’s not impossible.

We’ll see in Vue-CLI or other frameworks that make a distinction between production configuration and development environment. For example, the common configuration webpack.base.conf.js, the development configuration webpack.dev.conf.js, and the production configuration webpack.prod.conf.js.

If we use a custom configuration file name, we need to use the –config display to specify our configuration file and its path when packaging:

package.json

webpack --config ./build/webpack.dev.conf.js
Copy the code

When entry is a string or array and output is a file name, it’s easy to understand, but what if we need to package multiple pages?

Add a new search.js file to the SRC folder, and then modify the webpack.config.js file as follows:

const path = require('path')
module.exports = {
    entry: {
        main: path.resolve(__dirname, 'src/main.js'),
        search: path.resolve(__dirname, 'src/search.js')
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    }
}
Copy the code

Output [name] represents a placeholder, which corresponds to the key name of the entry object. Js and search.js files are generated in the dist directory.

What if multiple entrances were packaged into one exit?

module.exports = {
    entry: {
        main: path.resolve(__dirname, 'src/main.js'),
        search: path.resolve(__dirname, 'src/search.js')
    },
    output: {
        filename: 'test.js',
        path: path.resolve(__dirname, 'dist')
    }
}
Copy the code

It is not allowed to pack multiple entries into one exit.

After all this time, it’s time to see what you can do with it.

We add some “tags” (print a few sentences) to both main.js and search.js, and then wrap it up.

Once the package is complete, create the index.html file in the dist directory and import the packed JS file. After opening the page, F12 (CTRL + Shift + I) goes to the developer tools and you can see that our console prints the result.

The loader webpack

Webpack itself can only parse JS files, but there are many resources in the front-end application, such as less files or image resources, Webpack cannot parse them, how to let Webpack identify and parse them, depends on loader to help us solve. The loader is executed from right to left and from bottom to top (you can change the execution order by using the Enforce attribute).

Starting with Webpack2, importing JSON files is supported by default, so you’ll see in some tutorials that WebPack can parse JS and JSON files. However, when you use custom file extensions, you still need to add jSON-loader.

file-loader

File-loader is used to parse files. The official website makes it pretty clear. Let’s install it first:

npm install -D file-loader
Copy the code

We introduced an image in main.js, which I placed in the SRC folder. The project structure is as follows:

(Yes, my oldest son)

(Yes, and a little boy.)

Loader is configured using rules under module. Rules is an array. Each item is an object, representing the resolution method of a resource. Each object has four basic properties:

  • Test: indicates the matched file.

  • Use: indicates the list of loaders to be applied, which can be a string. For example, use: ‘file-loader’ indicates that one loader is applied to matched resources, and an array indicates that multiple Loaders are used. Such as the use: [‘ file – loader, {

    loader: ‘xxxx-loader’,options: { … }}]. Loader indicates a single loader to be used, and options indicates the configuration of the loader. Each loader has its own configuration properties.

  • Include: Matches only the files included in the include.

  • Exclude: Does not match the files contained in exclude.

  • Enforce: ‘post’ | ‘pre’, on behalf of the execution order, no configuration for ordinary loader, execution order of pre – > ordinary loader – > post.

We modify main.js

console.log('This is main.js');
import imgSrc from './2.jpg'
var imgTag = new Image();
imgTag.src = imgSrc;
document.body.appendChild(imgTag);
Copy the code

webpack.config.js

module.exports = { entry: { main: path.resolve(__dirname, 'src/main.js'), search: path.resolve(__dirname, 'src/search.js') }, output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.(png|jpg|gif)$/, use: { loader: 'file-loader', //outputPath: 'img' // image output directory, default package outputPath, here is the splices}}]}}Copy the code

Execute the package command NPM run dev, open the index.html folder dist, and see that the image has been appended to the page.

Url-loader — parses file resources

Url-loader is similar to file-loader. The difference is that url-loader has an attribute limit of a specified size. The attribute greater than limit is processed by file-loader, and the attribute smaller than loader is converted to dataUrl and collected in HTML files. This reduces the number of requests, although it does increase the size of the file.

Import file:///C:/Users/86133/Desktop/webpackTest/dist/2.jpg will be in the output directory file generated file. The PNG and returns the public URL, file:///C:/Users/86133/Desktop/webpackTest/dist/2.jpg namely.

You can use url-loader with file-loader, but you need to install it. Otherwise, you will be prompted to install file-loader when resources exceed limit. Come on, show!

Install the url – loader

npm install -D url-loader
Copy the code

Url-loader does not need to configure file-loader.

rules: [ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { limit: //outputPath: 'img' //outputPath: 'img' // the outputPath of the image, the default is to package the outputPath, here is the splice}}}]Copy the code

The unit of limit is byte. My image is 160K, I set it to 170, and it should eventually become the dataUrl.

Take a look. NPM Run Dev

Css-loader – parses CSS files

Css-loader is used to parse CSS files.

CSS is parsed into strings:

\"* {\\r\\n background-color: red; \\r\\n}\"Copy the code

Install first:

npm install -D css-loader
Copy the code

Webpack. Config. Js:

rules: [
    {
        test: /\.css$/,
        use: ['css-loader']
    }
]
Copy the code

Then create style. CSS in the SRC folder, give the body a background color aqua, and introduce it in main.js.

import './style.css'
Copy the code

Run NPM run dev to package.

Open index.html and find that the style is not in effect.

Why is that?

Developer tools review elements to see the structure of the page, no style tag, no link tag, open main.js to see. We can find a piece of code that looks like this.

___CSS_LOADER_EXPORT___.push([module.i, "body {\n    background-color: rgb(41, 85, 85);\n    display: -webkit-box;\n    display: -webkit-flex;\n    display: -ms-flexbox;\n    display: flex;\n    -webkit-box-pack: center;\n    -webkit-justify-content: center;\n        -ms-flex-pack: center;\n            justify-content: center;\n    -webkit-box-align: center;\n    -webkit-align-items: center;\n        -ms-flex-align: center;\n            align-items: center;\n}\", \"\"])"
Copy the code

The result of CSS file parsing is converted into a JS module by CSS-Loader and pushed into an array provided by a module within CSS-Loader that does not use the array.

** Loader is only responsible for parsing files. ** In order for CSS to work, we need to parse the result of CSS-loader by style-loader, that is, by inserting the style tag into the head tag. By default, each CSS file generates a style tag (configurable by parameter).

Style-loader – mounts parsed CSS to the style tag

npm install -D style-loader
Copy the code

Change the webpack. Config. Js

{
    test: /\.css$/,
    use: ['style-loader', 'css-loader']            
}
Copy the code

Loader works from right to left. We need to parse the CSS file and then mount it to the page using the style tag.

When you open index.html, you can see that the background color of the page has changed.

Sass-loader — Parses SASS/SCSS files

Sas-loader parses SCSS files.

npm install -D sass-loader
Copy the code

Webpack. Config. Js:

{
     test: /\.scss$/,
     use: ['style-loader', 'css-loader', 'sass-loader']
}
Copy the code

Then create style2. SCSS in SRC, give img a border, and import it in main.js.

import './style2.scss'
Copy the code

After packaging, open the web page to see the effect.

Note that sASS – Loader depends on SASS, so sASS must also be installed.

Postcss-loader – Automatically adds the browser prefix

Some of the CSS properties on the front end are compatible depending on the vendor, while PostCSS in combination with AutopreFixer helps you automatically add browser prefixes (note that AutopreFixer needs to specify the range of prefixes).

npm install -D postcss-loader autoprefixer
Copy the code

Configuration Method 1: Add the configuration file postcss.config.js in the root directory.

module.exports = { plugins: [ require('autoprefixer')({ overrideBrowserslist: [" Android 4.1 ", "iOS 7.1", "Chrome > 31", "ff > 31", "ie > = 8"]]}});Copy the code

Webpack. Config. Js:

 {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader']
}
Copy the code

Configuration mode 2: webpack.config.js configuration file configuration:

{ test: /\.css$/, use: ['style-loader', 'css-loader', { loader: 'postcss-loader', options: { plugins: [ require('autoprefixer')({ overrideBrowserslist: [" Android 4.1 ", "iOS 7.1", "Chrome > 31", "ff > 31", "ie > = 8"]]]}}}})Copy the code

PostcssOptions ->postcssOptions->plugins:[])

Set browserslist in package.json:

"browserslist": [
    "last 1 version",
    "> 1%",
    "IE 10"
]
Copy the code

webpack.base.config.js

{
    test: /\.css$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader', {
        loader: "postcss-loader",
        options: {
            postcssOptions: {
                plugins: [
                    [
                        "autoprefixer"
                    ]
                ]
            }
        }
    }]
}
Copy the code

Configuration mode 4: configuration mode 3 webpack.base.config.js+ root directory created. Browserslistrc file:

last 2 version
> 1%
Copy the code

Babel-loader – handles JS syntax

npm install -D babel-loader @babel/core @babel/preset-env
Copy the code

Why install @babel/ core@babel /preset-env

The Node API for Babel has been moved to babel-core. If @babel/core is not installed, WebPack will prompt you to install it first.

rules: [
    {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env']  //这是babel-loader的写法
            }
        }
    }
]
Copy the code

In fact, instead of using presets in the options option of babel-loader, you can also create a. Babelrc file in the project root directory, which uses JSON format.

Babelrc:

{  "presets": [["@babel/preset-env"]]  }  
Copy the code

Webpack of plugins

Plugins are plugins. For things that loader can’t do, you can find plugins.

Html-webpack-plugin – Automatically creates index.html and imports files

When we look at the packaging effect, we create index.html in the dist directory and manually import the packaged JS and CSS files, The HTML-webpack-plugin helps us automatically generate an HTML file and import packaged files (if you have multiple webpack entry points, they will all be in the script tag of the generated HTML file). If there are any CSS assets in the webpack output, for example, extracted CSS using the ExtractTextPlugin, these will be included in the tag in the HTML head).

npm install -D html-webpack-plugin
Copy the code

Webpack. Config. Js:

var HtmlWebpackPlugin = require('html-webpack-plugin');
modules: {
    ...
},
plugins: [
    new HtmlWebpackPlugin({
        template: path.resolve(__dirname, 'src/index.html')
    })
]
Copy the code

Template specifies an HTML file in the project as the template. If you don’t specify it, it will automatically generate an HTML file in the dist folder. So what’s the use of template? So you create an HTML file in your project, configure what you want to configure, and use it as a template.

Delete the index. HTML file in dist directory and pack it.

Clean-webpack-plugin — Clean up the build output directory

Clean-webpack-plugin is generally used in production environments (since development environments use devServer, there is no build directory because it is not packaged).

If we change the key name of entry, the output file name will also change after packaging, and there will be more and more useless files in the dist directory. Therefore, we introduced the clean-webpack-plugin to help us automatically clear the packaging directory before each packaging.

npm install -D clean-webpack-plugin
Copy the code

webpack.base.conf.js

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
plugins: [
    new CleanWebpackPlugin()
]
Copy the code

This way we don’t have to delete useless files after each package.

Mini-css-extract-plugin — Extract style into a separate CSS file

The Mini-CSS-extract-plugin is commonly used in production environments.

npm i -D mini-css-extract-plugin
Copy the code

Webpack. Config. Js:

const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module: { rules: [ ... { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] }, { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] } ] }, plugins: [... new MiniCssExtractPlugin({filename: '[name].css' // default is chunk name})]Copy the code

Need to be ‘style – loader’ replace MiniCssExtractPlugin. Loader, or not to take effect.

When you look at index.html after packaging, you can see that CSS is introduced with the link tag.

Mini-css-extract-plugin is generally used in production environment, while style-loader is generally used in development environment, which is faster in packaging (less plug-in does less extra processing, and the development environment does not need to extract CSS files). However, if you really want to use contenthash in development, it is recommended that you use it in this way (come back to this section when you learn about file fingerprint) :

new MiniCssExtractPlugin({    filename: devMode? '[name].css' : '[name]_[contenthash].css'})
Copy the code

The development environment uses fixed CSS file names. If contenthash is used as the file name suffix, you need to manually refresh the CSS to get the modified result. This is because the CSS is rebuilt after modification due to hot update, and the contenthash of the generated CSS file will change. The HTML file automatically generated by the HTML-webpack-plugin has the same name as the link reference to CSS.

File name before modification:

Modified file name:

Refreshed file name:

Terser-webpack-plugin — Compress JS, multi-process parallel compression (speed up packing and building)

Generally used in the production environment

npm install -D terser-webpack-plugin 
Copy the code

webpack.prod.conf.js

const TerserPlugin = require('terser-webpack-plugin'); module.exports = merge(webpackBaseConfig, { mode: 'production', optimization: { minimize: true, minimizer: [new TerserPlugin({parallel: true})]} plugins: [..] })Copy the code

parallel:Type: Boolean|Number Default: true

You can set a Boolean or number value to it, which defaults to CPU core number -1 when true, or you can set a specific value.

Optimize the CSS – assets – webpack – plugin CSS – compression

Generally used in the production environment.

npm install -D optimize-css-assets-webpack-plugin
Copy the code

webpack.prod.conf.js

var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); module.exports = merge(webpackBaseConfig, { mode: 'production', plugins: [... new OptimizeCssAssetsPlugin({assetNameRegExp: /\. CSS $/g, // matching file, default is CSS file cssProcessor: Require ('cssnano') // default CSS processor, other configurable})]})Copy the code

The compressed CSS file might look like this:

Copy-webpack-plugin — Copies resources

npm install -D copy-webpack-plugin
Copy the code

Create an image folder under the SRC directory, place some images, and configure the path.

const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { ... Plugins: [... new CopyWebpackPlugin({patterns: [{from: path.resolve(__dirname, './ SRC /image')), // Copy the resource path to: Path. resolve(__dirname, 'dist/static') // copy to {]})]}Copy the code

Note that the current CopyWebpackPlugin needs to pass in the Patterns property of the configuration object to place the array of copy rules, whereas the previous CopyWebpackPlugin passed in the array of copy rules directly.

Glory Gold – devServer for Webpack

Before learning about devServer, let’s interrupt with a file listener:

File to monitor

File listening automatically rebuilds the output file when changes are detected in the source code (the only drawback is that the user needs to manually refresh the browser).

Principle of file monitoring:

Module. exports = {watch: true, watchOptions: {// watch: true enabled listening mode // default empty, do not listen to files or folders, support regular match ignored: /node_modules/, // Listen to changes and wait for 300ms. Default is 300ms. AggregateTimeout: 300, poll: 1000 poll: 1000}};Copy the code

Note that the CleanWebpackPlugin needs to be turned off first, otherwise the index.html file will be automatically deleted when the file changes.

devServer

DevServer can start a simple Web server. Hereinafter referred to as WDS. The devServer base also uses — Watch to listen on files.

npm install -D webpack-dev-server
Copy the code

Webpack. Config. Js:

module.exports = { ... DevServer: {contentBase: path.join(__dirname, "dist"), // Make files in dist directory accessible. Compress: true, // Whether to enable gzip compression port: 9000, // Open the service on this port open: true, // Whether to automatically open the browser //hot: true // Set this parameter to true for hot updates to take effect}, plugins: []}Copy the code

Compress indicates that gzip compression is enabled. Port indicates the port for enabling the service. Open indicates that the browser is automatically opened when the service is started.

Next we will use the WDS specific command:

package.json

"dev": "webpack-dev-server"
Copy the code

For a custom file name, use –config file path name.

DevServer’s open argument can be replaced with –open in NPM script.

"dev": "webpack-dev-server --config ./webpack.config.js --open"
Copy the code

The open argument is actually a String in addition to a Boolean, which specifies which browser to open. If set to true, the default browser is opened, which can be specified as “Chrome”, etc.

"dev": "webpack-dev-server --config ./webpack.config.js --open Chrome"
Copy the code

Note: Packing with webpack-dev-server does not generate the dist directory in the project file, it packs the file into memory.

Hot replacement plugin HotModuleReplacementPlugin module

When we were modifying the project file previously, if we modified the file, we needed to repackage it to see the latest effect. Module hot replacement is when the program runs, the modification of the part of the file operation, when you change a place, the page does not re-render.

HotModuleReplacementPlugin collocation derServer usually used together, it is the webpack.

Webpack. Config. Js:

var webpack = require('webpack') module.exports = { devServer: { ... Hot: true / / devServer default is to refresh the web page to update, is set to true to make hot update}, plugins: [new webpack. HotModuleReplacementPlugin ()]}Copy the code

In addition to configuring hot:true in webpack.config.js, you can also use the –hot parameter in NPM script to turn on hot updates.

Configure devServer in WebPack5

“Webpack” : “^ 5.6.0”,

“Webpack – cli” : “^ 4.2.0”,

“Webpack dev – server” : “^ 3.11.0”

In Webpack-cli4, the CLI file structure has changed and will appear when migrated from Webpack-cli3

Error: Cannot find module ‘webpack-cli/bin/config-yargs’Because in Webpack-cli4, devServer’sThe startup command changes from webpack-dev-server to Webpack serve

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack serve"
}
Copy the code

And then you can be happy again.

What if I don’t use the HMR plugin and just turn on devServer{hot:true}?

I knew someone was as curious as I was.

After installing Webpack-dev-server, we can see this code in the dependency package:

node_modules/webpack-dev-server/utils/addEntries.js

if (options.hot || options.hotOnly) { config.plugins = config.plugins || []; if ( ! config.plugins.find( // Check for the name rather than the constructor reference in case // there are multiple copies of  webpack installed (plugin) => plugin.constructor.name === 'HotModuleReplacementPlugin' ) ) { config.plugins.push(new webpack.HotModuleReplacementPlugin()); }}Copy the code

If you set hot or hotOnly to true, then webpack-dev-server will check if you have added HMR, and if not, it will automatically add it for you. Well, you can’t get away with it.

Premium Platinum – Webpack-Merge for Webpack

There is a distinction between the WebPack configuration that we use in our daily development and the webPack configuration that goes live.

In webpack.config.js, there is a mode attribute that represents the environment to which the current configuration is applied, the default being the Production environment.

Webpack. Config. Js:

Module. exports = {mode: 'production', // do not write the equivalent of production entry: {... }}Copy the code

They have the following differences (from webpack’s official website) :

The production environment will enable ModuleConcatenationPlugin plug-in. Each module is wrapped by Webpack in separate function closures that slow down JavaScript execution in the browser. While ModuleConcatenationPlugin will all modules in reference to order in a function closure. Can new webpack. Optimize. ModuleConcatenationPlugin manually add ().

NamedModulesPlugins are automatically enabled in the development environment, and WebPack is used to reduce file size (so don’t use it in production mode!). , will rename the module, when the program error, it is not clear which file the current error came from.

Error before opening:

Error after opening:

Information Updated before opening:

Updated information after enabling:

Better not?

Opens FlagDependencyUsagePlugin production mode, will remove useless code, such as import after a module is not in use, so this module will not be packaged.

So the development environment and production environment have some common configuration, if only relying on CV method, it is really ugly, and difficult to maintain. What to do?

webpack-merge

npm install -D webpack-merge
Copy the code

We rename the configuration file to webpack.base.conf.js as our common configuration file, and create a configuration file webpack.prod.conf.js for production and a configuration file webpack.dev.conf.js for development.

Also make some configuration adjustments:

DevServer official is not recommended on production environments, so pull away devServer and HotModuleReplacementPlugin the two brothers to webpack. Dev. Conf. Js

Webpack. Base. Conf. Js:

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {CleanWebpackPlugin} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    entry: {
        main: path.resolve(__dirname, 'src/main.js'),
        search: path.resolve(__dirname, 'src/search.js')
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
            },
            {
                test: /\.scss$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 170 * 1024
                    }
                }
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'src/index.html')
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename:'[name].css'
        })
    ]
}
Copy the code

Webpack. Dev. Conf. Js:

const path = require('path');
const {merge} = require('webpack-merge')
var webpack = require('webpack')
const webpackBaseConfig = require('./webpack.base.conf.js')
module.exports = merge(webpackBaseConfig, {
    mode: 'development',
    devServer: {
        contentBase: path.join(__dirname, "dist"),
        compress: true,
        port: 9000,
        open: true,
        hot: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
})
Copy the code

Webpack. Dev. Conf. Js:

const {merge} = require('webpack-merge')
const webpackBaseConfig = require('./webpack.base.conf.js')
module.exports = merge(webpackBaseConfig, {
    mode: 'production'
})
Copy the code

The project directory structure is as follows:

Since our configuration file name has changed, we need to modify the commands in package.json and add the production mode packaging command prod.

"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "Webpack-dev-server -- config. /webpack.dev.conf.js", "dev2": "webpack -- config. /webpack.dev.conf.js", // dev environment package "prod": "webpack --config ./webpack.prod.conf.js" }Copy the code

Tested separately, the function is normal.

A special case

Sometimes we might want to “take different Loaders/plugins for different environments” in a common configuration file. From what we’ve just seen, webpack sets process.env.node_env to development (production) when setting mode to development (production). But when we print process.env.node_env in the webpack configuration file, we will find that the value is undefined instead of development or production, oh! It set an air!

We try to print process.env.node_env in SRC /main.js, execute NPM run dev2 for development and NPM run prod for production respectively, open the console and find the value we want.

So how do you determine the current environment in the configuration file?

In the search engine even roll with climb after a while, looked for a “folk prescription”.

webpack.base.conf.js

const devMode = process.argv.indexOf('--mode=production') === -1;
Copy the code

Need to work with NPM script:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --config ./webpack.dev.conf.js",
    "dev2": "webpack --mode=development --config ./webpack.dev.conf.js",
    "prod": "webpack --mode=production --config ./webpack.prod.conf.js"
}
Copy the code

–mode specifies the current environment.

If mode exists in the configuration file at the same time as –mode in NPM script, –mode takes precedence.

Then you can use it like this:

{
      test: /\.css$/,
      use: [ devMode? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
}
Copy the code

File fingerprint Policy

Before we study document fingerprinting, let me try to jog your memory with a picture:

What? No recollection?

Let me help you remember:

The file fingerprint is the filename suffix of the packaged output (note the difference between the suffix and extension)

What’s the use?

For example: your company has a project that has been packaged for a while, and one day you change a file such as index.js and republish it. You update is no sense for the user, the user client local, had previously to index. Js resources to the cache, if the client cache is also available, so will be read directly from the cache index. The js file, and your update for this part of the user is unaware of, so as a result of the existence of the cache, When you need to fetch new code, unexpected behavior occurs. (In short, if you do not change the file name of a resource when deploying a new version, the browser may assume that it has not been updated and use the cached version.)

This is clearly not what we expect: for updated content, we expect the client to initiate a resource request, and for unupdated content, we expect the client to read directly from the browser cache.

Yes, that’s it!

Let’s take a look at some common document fingerprints:

  • Hash: Relates to the construction of the entire project file. Whenever the project file is modified, the Hash value for the entire project will change.
  • Chunkhash: Related to chunks packed by Webpack, different entries will generate different Chunkhash values.
  • Contenthash: Hash generated based on the file content. If the file content does not change, the Contenthash does not change.

How do you use file fingerprints?

Webpack. Base. Conf. Js:

module.exports = { entry: { main: path.resolve(__dirname, 'src/main.js'), search: path.resolve(__dirname, 'src/search.js') }, output: { filename: devMode? '[name]. Js' :' [name] _ [hash] js', / / a production environment using the file fingerprint path: the path. The resolve (__dirname, 'dist')}}Copy the code

search.js

console.log('This is search page')
Copy the code

main.js

import './style.css'console.log('This is main.js');
Copy the code

NPM run prod generates the following:

We found that the file names were all hashed for the build. After the file is updated, the user’s browser still reads the cached file. Because the hash value changes every time the file is repackaged, the file name changes accordingly.

What if I just modify the main.js file?

import './style.css'console.log('This is main.js2');
Copy the code

Execute package NPM run prod:

The file name (hash) of the output JS file is changed. In other words, after republishing, users need to re-request all resources.

Or…

Running is not possible (mainly can’t run), so have to change!

Isn’t there Still Chunkhash

module.exports = {
    entry: {
        ...
    },
    output: {
        filename: devMode? '[name].js' : '[name]_[chunkhash].js',
        path: path.resolve(__dirname, 'dist')
    }
}
Copy the code

Webpack, start!

As you can see, each bundle generates its own chunkhash, which does not affect each other. At this point continue to modify main.js (main.js: don’t help me, I can change!) .

Webpack, start!

As you can see, the chunkhash of search remains unchanged after main.js is repackaged. For the user browser, it can read the latest main.js and search.

.

.

.

Wait a minute! And what about Contenthash?

Click “like” in the top left corner of your computer and bottom right corner of your phone to unlock new content — Contenthash

Let’s take a look at the pictures above. Of course, they are not memes. Pull out the similarities. What do you find?

That’s right, it’s main.css

If I modify the CSS file and then package it, will the browser read the cached file directly?

So the fingerprint that CSS files need to use is hash? Chunkhash?

Considering that hash is definitely out of the question, how about chunkhash?

module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
            filename: devMode? '[name].css' : '[name]_[chunkhash].css'
        })
        ...
    ]
}
Copy the code

If this is the case, then modifying main.js will change the filename of main.css. This is clearly not appropriate. Let’s try contenthash:

module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
            filename: devMode? '[name].css' : '[name]_[contenthash].css'
        })
        ...
    ]
}
Copy the code

Webpack, start!

Modify main.js and then package to view:

You can see that the CSS file is still the original file name. Js and main. CSS file names have been changed. This is because the chunkhash of main bundle has changed due to the modification of the CSS file.

Hash is usually used on files:

{ test: /\.(png|jpe? g|gif|svg)(\? . *)? $/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }Copy the code

To sum up:

Js file fingerprint setting: Set filename for output, using chunkhash.

File fingerprint setting for the CSS: Set filename of MiniCssExtractPlugin using contenthash.

File fingerprint setting for resources such as images: Set the name of file-loader and use hash.

Do not use [chunkhash]/[hash]/[contenthash] in the development environment, as there is no need for persistent caching in the development environment and this increases compile time. The development environment will use [name] instead.

Uninstall the game — set up the VUE development environment

Install vue-loader and vue-template-Compiler

npm install -D vue-loader vue-template-compiler
Copy the code

Vue-template-compiler is used to compile the template template.

Configure the vue – loader

Webpack. Base. Conf. Js:

const vueLoaderPlugin = require('vue-loader/lib/plugin')
module: {
    rules: [
        {
              test: /\.vue$/,
              use: [
                  {
                       loader: 'vue-loader'
                  }
              ] 
        }
    ]
},
plugins: [
    ...
    new vueLoaderPlugin()
]
Copy the code

main.js

import Vue from 'vue';
import App from './app.vue';
new Vue({
    render: h=>h(App)
}).$mount('#app')
Copy the code

Create the app.vue file in the SRC folder

<template>
  <div>
      {{title}}
  </div>
</template>
<script>
export default {
    data() {
        return {
            title: "Hello Vue!"
        }
    }}
</script>
Copy the code

The index.html file

<! DOCTYPE html><html lang="en"> <head> ... <title>my first webpack</title> </head> <body> <div id="app"></div> </body> </html>Copy the code

Webpack, start ~! (Don’t forget to install vUE before this.)

Very happy.

It smells good — an extra meal

The webPack optimization strategy is well known