preface

Front-end engineering has experienced many excellent tools, such as Grunt, Gulp, Webpack, rollup, etc., each tool has its own application scenario, and the most widely used tool today is Webpack. Therefore, learning WebPack well has become an essential skill for a good front-end.

Due to the complexity of webpack stack, the author intends to explain it in two articles:

  1. Basic application: explain all kinds of basic configuration;
  2. Advanced applications: ExplanationwebpackOptimization and principles.

[Note] This article is based on webPack 4.x

What is the webpack

Webpack is a module packaging tool.

Webpack can package the following code without any configuration:

When nothing is configured, WebPack uses the default configuration.

// moduleA.js
function ModuleA(){
  this.a = "a";
  this.b = "b";
}

export default ModuleA


// index.js
import ModuleA from "./moduleA.js";

const module = new ModuleA();
Copy the code

We know that the browser does not know the import syntax, and running such code directly in the browser will result in an error. We can use Webpack to package such code, enabling JavaScript to be modular.

The original version of WebPack could only pack JavaScript code, but as it has evolved CSS files, image files, and font files can all be packaged by WebPack.

This article will focus on how WebPack packages these resources. The basic article is intended to pave the way for performance optimization and principles later. If you are already familiar with WebPack, you can skip this article.

Initialize the webPack installation

mkdir webpackDemo // Create a folder
cd webpackDemo // Go to the folder
npm init -y // Initialize package.json

npm install webpack webpack-cli -D // The development environment installs Webpack and webPack-CLI
Copy the code

After this installation, we can use the Webpack command in our project.

Package the first file

webpack.config.js

const path = require('path');

module.exports = {
  mode: 'development'./ / {1}
  entry: { / / {2}
  	main:'./src/index.js'
  }, 
  output: { / / {3}
    publicPath:"".// Add a uniform prefix address to all dist files. For example, add the domain name published to the CDN
    filename: 'bundle.js'.path: path.resolve(__dirname,'dist')}}Copy the code

Code analysis:

  • {1} modeIs packaging a development environment or a build environment,development | production
  • {2} entryThe entry file isindex.js
  • {3} outputOutput to thepathConfiguration of thedistFolder, the output file namefilenameConfiguration of thebundle.js

Create files for simple packaging:

src/moduleA.js

const moduleA = function () {
  return "moduleA"
}

export default moduleA;

--------------------------------

src/index.js

import moduleA from "./moduleA";

console.log(moduleA());
Copy the code

Modify package.json scripts to add a command:

"scripts": {
  "build": "webpack --config webpack.config.js"
}
Copy the code

Run the NPM run build command

Source code analysis of bundled bundle.js

The source code has been simplified, only the core part of the display, easy to understand:

 (function(modules) {
 	var installedModules = {};

 	function __webpack_require__(moduleId) {
		// Cache files
 		if(installedModules[moduleId]) {
 			return installedModules[moduleId].exports;
 		}
		// Initialize the moudle and also store a copy in the cache
 		var module = installedModules[moduleId] = {
 			i: moduleId,
 			l: false.exports: {}};// execute the function body corresponding to "./ SRC /index.js"
 		modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);

		// mark the module "./ SRC /index.js" as well as loading it
 		module.l = true;
    
 		// Returns the module that has been loaded successfully
 		return module.exports;
 	}
	// Where the anonymous function starts execution, and the default path is the entry file
 	return __webpack_require__(__webpack_require__.s = "./src/index.js");
 })
	// Pass the module object of the anonymous execution function body, containing "./ SRC /index.js", "./ SRC/modulea.js"
	// And their corresponding function bodies to be executed
 ({
   "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ \"./src/moduleA.js\"); \n\n\nconsole.log(Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()); \n\n\n//# sourceURL=webpack:///./src/index.js?");
   
  }),

   "./src/moduleA.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \nconst moduleA = function () {\n return \"moduleA\"\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"]  = (moduleA); \n\n\n//# sourceURL=webpack:///./src/moduleA.js?"); })});Copy the code

/ SRC /index.js”./ SRC /index.js

(function(module, __webpack_exports__, __webpack_require__) {
	"use strict";
	eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ \"./src/moduleA.js\"); \n\n\nconsole.log(Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()); \n\n\n//# sourceURL=webpack:///./src/index.js?");
})
Copy the code

You’ll notice that it’s an eval method.

Let’s take a closer look at eval and simplify the code as follows:

var moduleA = __webpack_require__("./src/moduleA.js");
console.log(Object(moduleA["default") ());Copy the code

__webpack_require__(__webpack_require__. S = “./ SRC /index.js”); “, then “./ SRC /index.js” recursively calls the output object to get “./ SRC/modulea.js “.

Let’s see what the “./ SRC/modulea.js “code will output:

const moduleA = function () {
  return "moduleA"
}
__webpack_exports__["default"] = (moduleA);
Copy the code

Looking back at the code above, this is equivalent to:

console.log(Object(function () {
  return "moduleA"}) ());Copy the code

Finally, execute and print “moduleA”

Through the analysis of this source code can be seen:

  1. After packaging the module, all are passedevalFunction to execute;
  2. By calling the entry function./src/index.jsAnd then we recursively find all the modules, because recursion does double counting, so__webpack_require__There is a cache object in the functioninstalledModules.

loader

We know that WebPack packs JavaScript modules, and we’ve long heard that WebPack packs images, fonts, and CSS, so we need loader to help us identify these files.

[Note] If the file is not recognized, please find loader.

Pack image files

Modify the configuration file: webpack.config.js

const path = require('path');

module.exports = {
  mode: 'development'.entry: { 
    main:'./src/index.js'
  },
  output: {
    filename: 'bundle.js'.path: path.resolve(__dirname,'dist')},module: {rules:[
      {
        test:/\.(png|svg|jpg|gif)$/,
        use:{
          loader: 'url-loader'.options: {
            name: '[name]_[hash].[ext]'.outputPath:"images".// Package the resource into the images folder
            limit: 2048 // if the size of the image is less than 2048KB, output base64, otherwise output the image}}}]}}Copy the code

Modification: SRC/index. Js

import moduleA from "./moduleA";
import header from "./header.jpg";

function insertImg(){
  const imageElement = new Image();
  imageElement.src = `dist/${header}`;
  document.body.appendChild(imageElement);
}

insertImg();
Copy the code

After the packing, it was found that the packing was normal, and there was an additional image file in the dist directory.

Let’s make a simple analysis:

Webpack itself actually only know JavaScript modules, when touching the image file will go to find, the module configuration rules found test: / \. (PNG | SVG | JPG | GIF) $/, When the re matches the image file suffix, it is processed using url-loader. If the image is less than 2048KB (this can be set to any value, depending on the project), base64 is output.

Package style file

{
  test:/\.scss$/.// The re matches the.scss style file
    use:[
      'style-loader'.// Insert the resulting CSS content into the HTML
      {
        loader: 'css-loader'.options: {
          importLoaders: 2.// import SCSS file again in SCSS, also execute sass-loader and postCSs-loader
          modules: true // Enable the CSS module}},'sass-loader'.// Parse SCSS files into CSS files
      'postcss-loader'// Automatically adds the vendor prefix -webket-moz, which also requires the creation of the postcss.config.js configuration file]}Copy the code

postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')]};Copy the code

Package analysis:

  1. whenwebpackencounterxx.scssThe style file is;
  2. In turn, callspostcss-loaderAutomatically adds vendor prefixes-webket -moz
  3. callsass-loaderscssFile conversion tocssFile;
  4. callcss-loaderTo deal withcssFile, whereimportLoaders:2, it isscssOther things are introduced in the filescssFile, need to be called repeatedlysass-loader postcss-loaderConfiguration items of;
  5. The last callstyle-loaderLet me compile itcssThe content of the document is<style>... </style>Form is inserted into the page.

[Note] The loader is executed from the end of the array to the end of the array.

Package font files

{
  test: /\.(woff|woff2|eot|ttf|otf)$/.// Package the font files
  use: ['file-loader'] // Move the font file to dist
}
Copy the code

plugins

Plugins can do things for you at a certain point in a WebPack’s life cycle, just like calling plug-ins to do auxiliary things.

html-webpack-plugin

Function:

After the package is complete, an HTML file (or template) is automatically generated, and the JS file generated by the package is automatically imported into the HTML file.

Use:

const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
  new HtmlWebpackPlugin({
    template: 'src/index.html' // Use a template file})]Copy the code

clean-webpack-plugin

Function:

The folder configured for output is automatically deleted each time the package result is output

Use:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

plugins: [
  ...
  new CleanWebpackPlugin() Use this plug-in to delete the dist directory each time it is generated
]
Copy the code

source map

There is a very important function in the development process, that is error debugging, we have errors in the process of writing code, if the compiled package is not friendly, it will seriously affect our development efficiency. Configuring the Source Map helps solve this problem.

Example: Modified: SRC /index.js to add an incorrect line of code

console.log(a);
Copy the code

Since mode: ‘development’ turns the Source map feature on by default, let’s turn it off first.

devtool: 'none' // Close the source map configuration
Copy the code

Execute the package to see the console error message:

The error stack information is actually given in the packaged bundle file, but the structure of our file during development is not like this, so we need it to indicate how many lines in index.js we have failed so that we can quickly locate the problem.

We remove the devtool:’ None ‘configuration and do the packing:

At this point, it prints out our specific development errors on the stack, which is what the Source Map does.

To summarize: The source map is a mapping that knows the specific lines and columns in the actual development file that corresponds to the bundle.js file in the dist directory.

webpackDevServer

It’s not scientific to manually compile the code every time you modify it. We want webPack to automatically compile the code every time we write it, and webpackDevServer can help us.

Add configuration:

devServer: {
  contentBase: './dist'.// Server boot root set to dist
  open: true.// Automatically open the browser
  port: 8081.// Configure the service startup port. The default port is 8080
  proxy: {'/api': 'http://www.baidu.com' // When the development environment sends/API requests, they are proxyed to http://www.baidu.com host}},Copy the code

It basically helps us start a Web service, listens for files under SRC, and automatically helps us re-execute the WebPack compilation when the files change.

Let’s add a command to package.json:

"scripts": {
  "start": "webpack-dev-server"
},
Copy the code

Now that we have executed the NPM start command, we can see that the console is in listening mode, and any changes we make to the business code will trigger a WebPack recompilation.

Webpack-dev-server implements the request broker

In the front-end development of the project with the front and back end separated, every student must encounter a thorny problem is cross-domain request. Webpack-dev-server can also be used as a proxy in a production environment. It can also be used as a proxy in a development environment

devServer: { ... Proxy :{'/ API ': 'http://www.baidu.com' // when the development environment sends/API requests will be proxy to http://www.baidu.com host}}Copy the code

The configuration items of proxy are very rich and you can refer to the documentation for details. We just need to remember that it can provide the function of proxy server to us.

Manually implement a simple version of Webpack-dev-server

Add: server.js to the project root directory

NPM install Express webpack-dev-middleware -d

const express = require('express');
const app = express();
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js'); // Import the WebPack configuration file
const compiler = webpack(config); // Webpack compiles the runtime

// Tell Express to use webpack-dev-middleware,
// And use the webpack.config.js configuration file as the base configuration
app.use(webpackDevMiddleware(compiler, {}));

// Listen on the port
app.listen(3000.() = >{
  console.log('Program started on port 3000');
});
Copy the code

Webpack dev – middleware functions:

  1. throughwatch modeListening for changes to the resource and then automatically packaging, essentially callingcompilerOn the objectwatchMethods;
  2. Using an in-memory file system compiles quicklycompiler.outputFileSystem = new MemoryFileSystem() ;

Package. json adds a command:

"scripts": {
  "server": "node server.js"
},
Copy the code

Run the NPM run server command to start the user-defined service. Enter http://localhost:3000/ in the browser to view the effect.

Hot Moudule Replacement (HMR)

Module hot update replaces, adds, or removes modules while the application is running without reloading the entire page.

HMR configuration

const webpack = require('webpack');
module.exports = {
  devServer: {
    contentBase: './dist'.open: true.port: 8081.hot: true // Hot update configuration
  },
  plugins: [new webpack.HotModuleReplacementPlugin() // Add hot update plugin]}Copy the code

Write HMR code manually

When writing code, it is often found that hot updates fail because the corresponding loader does not implement hot updates. Let’s see how to implement a hot update simply.

import moduleA from "./moduleA";

if (module.hot) {
  module.hot.accept('./moduleA.js'.function() {
    console.log("ModuleA supports hot update pull");
    console.log(moduleA()); })}Copy the code

Code explanation:

A common ES6 syntax module that we wrote ourselves, if we want to implement hot updates, must manually listen for the relevant files, and then actively invoke the update callback when it is received.

Webpack creates a Module object for each module, which is added when initializing the module object when you enable hot update:

function hotCreateModule(moduleId) {
  var hot = {
    active: true.accept: function(dep, callback){
      if (dep === undefined) hot._selfAccepted = true;
      else if (typeof dep === "function") hot._selfAccepted = dep;
      else if (typeof dep === "object")
      for (var i = 0; i < dep.length; i++)
 	  hot._acceptedDependencies[dep[i]] = callback || function() {};
 	  else hot._acceptedDependencies[dep] = callback || function() {}; }}}Copy the code

The dependency table of the listening file path and callback function is saved in the module object. When the listening module changes, it will actively call the relevant callback function to achieve manual hot update.

[Note] All written business modules will eventually be managed by WebPack as module objects. If hot update is enabled, module will add hot attributes. These properties make up the WebPack compile runtime object.

Compile ES6

You obviously know that you have to use Babel to support this, so let’s see how to configure it

configuration

1. Install related packages

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

2. Modify webpack.config.json configuration

If you don’t know the type of file you want to compile, please use loader

module: {rules:[
    {
      test: /\.js$/.// Rematches the js file
      exclude: /node_modules/.// Exclude the node_modules folder
      loader: "babel-loader"./ / use the Babel - loader
      options: {presets:[
          [
            "@babel/preset-env"./ / {1}
           { useBuiltIns: "usage" } / / {2}[]}}]}Copy the code

Babel configuration parsing:

  • {1} babel presetsIs a collection of plug-ins that transformES6+New syntax, but some newAPIIt won’t handle it
    • Promise  GeneratorIs the new syntax
    • Array.prototype.mapMethod is the newAPIbabelDoes not convert this syntax, so you need topolyfillTo deal with
  • {2} useBuiltInsThe configuration is processed@babel/polyfillHow is it loaded? It has three valuesfalse entry usage
    • falseWrong:polyfillsDo any operation;
    • entry: according to thetargetThe browser version will be supportedpolyfillsSplit import, import only those not supported by the browserpolyfill
    • usage: Checks the codeES6/7/8Load only what is used in the codepolyfills

demo

Create a file SRC/modulees6.js

const arr = [
  new Promise(() = >{}),
  new Promise(() = >{})];function handleArr(){
  arr.map((item) = >{
    console.log(item);
  });
}
export default handleArr;
Copy the code

Modify the file SRC /index.js

import moduleES6 from "./moduleES6";
moduleES6();
Copy the code

Execute the packaged source file (simplified) :

"./node_modules/core-js/modules/es6.array.map.js":
(function(module.exports, __webpack_require__) {
"use strict";
var $export = __webpack_require__("./node_modules/core-js/modules/_export.js");
var $map = __webpack_require__("./node_modules/core-js/modules/_array-methods.js") (1); $export($export.P + $export.F * ! __webpack_require__(/ *! ./_strict-method */ "./node_modules/core-js/modules/_strict-method.js")([].map, true), 'Array', {
  map: function map(callbackfn) {
    return $map(this, callbackfn, arguments[1]); }});Copy the code

Looking at the code, it should be clear that Polyfill is a reimplementation of the Map method using ES5 syntax to make it compatible with older browsers.

Polyfill implements all of the ES6+ syntax, which is too large for us to import, hence the configuration useBuiltIns: “usage” only loads the syntax used.

Compile the React file

configuration

Install dependency packages

npm install @babel/preset-react -D
npm install react react-dom
Copy the code

webpack.config.js

module: {rules:[
    {
      test: /\.js$/.// Rematches the js file
      exclude: /node_modules/.// Exclude the node_modules folder
      loader: "babel-loader"./ / use the Babel - loader
      options: {presets:[
          [
            "@babel/preset-env",
           { useBuiltIns: "usage"}], ["@babel/preset-react"[]}}]}Copy the code

Add a [“@babel/preset-react”] configuration to the presets configuration, and preset will help us convert JSX syntax in React to react. createElement.

demo

Modify the file: SRC /index.js

import React,{Component} from 'react';
import ReactDom from 'react-dom';

class App extends Component{
  render(){
    const arr = [1.2.3.4];
    return (
      arr.map((item) = ><p>num: {item}</p>)
    )
  }
}

ReactDom.render(<App />.document.getElementById('root'));
Copy the code

Run the yarn build command to package the yarn properly and the yarn build interface is displayed.

As the complexity of the project increases, the configuration of Babel also becomes more complex, so we need to extract the configuration of Babel into a separate file for easy configuration and management, that is, the.babelrc file in our project directory.

.babelrc

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

Babel-laoder executes presets from the back of the array in the same order as when multiple Loaders are used simultaneously.

Extract the options object from babel-loader in webpack.config.js into a separate file.

One problem we found was that the bundle.js file was about 1M large, because react and react-dom were packed in.

Tree Shaking

The essence of Tree Shaking is to eliminate useless JavaScript code.

import { forEach } from "lodash"

forEach([1.2.3].(item) = >{
  console.log(item);
})
Copy the code

The LoDash library was introduced in the project and only the forEach method was used. Back in the days of jquery we could only import the entire LoDash file. Tree Shaking is supported through import, so let’s configure it.

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'production'.devServer: {
    contentBase: './dist'.// Server boot root set to dist
    open: true.// Automatically open the browser
    port: 8081.// Configure the service startup port. The default port is 8080
  },
  entry: { // Import file
    main:'./src/index.js'
  },
  output: { // Export file
    publicPath:"".filename: 'bundle.js'.path: path.resolve(__dirname,'dist')},module: {/ / a loader configuration
    rules:[
      {
        test: /\.js$/.// Rematches the js file
        exclude: /node_modules/.// Exclude the node_modules folder
        loader: "babel-loader"./ / use the Babel - loader}},plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html' // Use a template file
    }),
    new CleanWebpackPlugin()
  ]
}
Copy the code

Just configure mode: Tree Shaking is the default in the ‘production’ generation environment.

It is still 72KB when packaged, so Tree Shaking fails. Why?

For Tree Shaing to be Shaking, you must have libraries that use the import Export ESModule syntax, and LoDash provides libraries for using Lodash-es.

Modify the business code: SRC /index.js

import { forEach } from "lodash-es";

forEach([1.2.3].(item) = >{
  console.log(item);
})
Copy the code

Perform packaging again:

The packaged size is only 5.55Kb, indicating that Tree Shaking is working.

Why ESModule

Tree Shaking is something you need to use ES6’s modular syntax. Is it possible to use CommonJs syntax for Tree Shaking? The answer is definitely no.

The CommonJS module is run time loaded, and the ES6 module is compile time output interface.

An ES6 module is not an object, and its external interface is a static definition that is generated during the code static parsing phase. Static analysis means analyzing code literally without executing it.

Take the above code to analyze: We introduced forEach in Lodash-es. As you can see in the static analysis phase, we only use forEach, so there is no need to introduce all the functions in lodash-es. Just keep forEach.

sideEffects

SideEffects must be configured when configuring Tree Shaking

package.json

{
  "sideEffects": false
}
Copy the code
  • "sideEffects": falsesaidwebpackIt can safely delete unused onesexport;
  • "sideEffects": ["*.css"]said*.cssThe introduction of do not doTree ShakingTo deal with.

Why do YOU need to do anything with CSS? Because we often introduce global CSS like this:

import "xxx.css"
Copy the code

If not configured, Tree Shaking will think that XXx. CSS is only imported, but not used, so the associated CSS code is removed. When configured as [“*.css”], all CSS files are excluded from Tree Shaking.

Code Splitting

Code segmentation, as the name implies, is to divide the packaged code.

Look at a scene:

import { forEach } from "lodash-es";

forEach([1.2.3].(item) = >{
  console.log(item);
})
Copy the code

Execute the package command:

We found that LoDash was also packaged into bundle.js.

In real development, we would probably work with multiple libraries, and if they were all packaged into bundle.js, the file would be huge!

Another problem is that the static files we pack will all add hash values as follows:

output: {
  filename: '[hash]_bundle.js'.// The packaged file is similar to 07b62441b18e3aaa6c93_bundle.js
  path: path.resolve(__dirname,'dist')}Copy the code

Do we all know that the purpose of, that is, the browser will cache on the same name of the static resources if we don’t add the hash value, but online and found a bug, I once again after the static resource updates to the server in the package, the user’s browser with cache will not immediately according to the latest results, and need to clear the cache manually.

Generally, imported class library files do not change, while our business code is constantly changing. It’s not reasonable to pack both mutable and immutable code together, or at least cause the user to have to load the entire code when we repackage.

If we split the code and work with the browser’s caching mechanism, the user site only needs to load the updated business code, and the class library code does not need to be reloaded.

So that’s why we need to do code splitting. Let’s take a look at how code splitting can be configured in WebPack.

SplitChunksPlugin

Its configuration is one of the more complex and important configurations of the WebPack plug-in, so this article explains the meaning of its core configuration in detail.

There are two ways to introduce a third-party module:

Synchronization mode:

import { forEach } from "lodash";
import $ from "jquery";

$(function () {
  forEach([1.2.3].(item) = >{
    console.log(item); })});Copy the code

Asynchronous mode:

import("lodash").then(({default: _}) = >{
  console.log(_.join(["a"."b"]."-"));
})
Copy the code

The SplitChunksPlugin plug-in already provides a default configuration out of the box that allows you to quickly split and package code for both of these modular introductions. Here’s what it means by default:

  optimization: {
    splitChunks: {
      chunks: 'async'.minSize: 30000.minRemainingSize: 0.maxSize: 0.minChunks: 1.maxAsyncRequests: 6.maxInitialRequests: 4.automaticNameDelimiter: '~'.cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2.priority: -20.reuseExistingChunk: true}}}}Copy the code

chunks

  • asyncAsynchronous module takes Effect
  • initialSynchronization Module Takes Effect
  • allAsynchronous synchronization takes effect

minSize

For example, the default size is 30000 KB. Assuming that the size of the library to be packaged is smaller than 30000 KB, it will not be packaged by modules.

maxSize

Maximum package size, let’s say LoDash is 1MB, let’s say it’s 500KB, webPack will try to split LoDash into 2 files, but loDash libraries are hard to split, so the end result will be the same, just one package.

minChunks

How many times a module is used before it is code split.

maxAsyncRequests

The maximum number of chunks to load

maxInitialRequests

Maximum number of entry files to do code segmentation

automaticNameDelimiter

The concatenation of the file name

name

Filename in cacheGroups takes effect when it is set to true

cacheGroups

The cache group, in which defaultVendors and Default act as two module cache arrays. It is usually a synchronized imported module, which is pushed into the array when hit by the cache policy, and then merged into a chunk.

The cache policy is configured like this:

cacheGroups: {
  vendors: {
    chunks: 'initial'.// Only for synchronous modules
    test: /[\\/]node_modules[\\/]/.// The synchronization code is packaged to determine whether it is under node_modules
    priority: -10.It is possible to package a module according to both vendors' and default rules, and then the larger the vendors' value, the higher the precedence, is determined by the vendors' and vendors' rules
    filename: '[name].chunk.js' // The output file name
  },
  default: {
    minChunks: 2.// When the module is used twice
    priority: -20.// Indicates the permission value
    reuseExistingChunk: true // Check for circular references to avoid packing useless modules in}}Copy the code

Synchronization module packaging:

import { forEach } from "lodash";
import $ from "jquery";

$(function () {
  forEach([1.2.3].(item) = >{
    console.log(item); })});Copy the code

Analysis:

  • lodashModule hitvendorsStrategy, push invendorsPolicy cache group;
  • jqueryModule also hitvendorsStrategy, push invendorsPolicy cache group;
  • There are no other modules, so merge and output a file namevendors~main.chunk.js, includingverdorsIs the name of the strategy,~Wavy lines isautomaticNameDelimiterThe configuration of themainentryThe name of the entry file,chunk.jsfilenameSuffix set in.

Asynchronous module packaging:

import(/* webpackChunkName: "lodash" */"lodash").then(({default: _}) = >{
  console.log(_.join(["a"."b"]."-"));
})

import(/* webpackChunkName: "jquery" */"jquery").then(({default: $}) = >{$(function () {
    console.log("Jquery has been loaded"); })})Copy the code

Analysis:

  • First, modules are annotated according to magic in order to satisfy lazy loading requirementswebpackChunkNamePackaged into separate modules such asjquery.bundle.jslodash.bundle.js
  • Again it’s going to gocacheGroupsTo check whether the corresponding policy is matchedvendorsIt doesn’t match,defaultPolicies can match, butdefaultThere is a configuration inreuseExistingChunk: trueIndicates that it will look for the module that has been packaged, and if it has been packaged it will be printed. Changed it tofalseAfter, will putjquery.bundle.jsRename as per policydefault~jquery.bundle.jsSince it is loaded asynchronously, the two modules on the home page will not be merged and output separately.

Policies in cacheGroups can be added on a project-by-project basis, so WebPack provides a variety of callback methods to make configuration more flexible.

CSS file code segmentation

With the increase of the project CSS files are very many, if all packaged into JS, it is bound to be js files too bloated, affecting the loading speed, so we have to separate the CSS package.

  • MiniCssExtractPluginIt will help us create a new onecssfile
  • OptimizeCSSAssetsPluginIt will help us merge compressioncssfile

Let’s take a look at the specific configuration:

.const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = {
  mode: 'production'.// The configuration of minimizer is performed only in Production mode
  optimization: {...minimizer: [
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  module: {rules:[
      {
        test:/\.css$/,
        use:[
          MiniCssExtractPlugin.loader, // Use the loader provided by the MiniCssExtractPlugin
          'css-loader']]}},plugins: [...new MiniCssExtractPlugin({ // Initialize the MiniCssExtractPlugin in the plugin and configure the naming rules for the separate CSS files
      filename: "[name].css".chunkFilename: "[id].chunk.css"}})]Copy the code

Another important thing to remember is that in production we have Tree Shaking enabled by default, so you need to configure sideEffects in package.json, otherwise the CSS file will be Shaking off by Tree.

"sideEffects": ["*.css"] // All.css files are not tree shaking
Copy the code

After the configuration is complete, execute the package command to find that the CSS file can be isolated and the CSS file is compressed.

To configure the CSS cacheGroups

splitChunks: {
	...
      cacheGroups: {...styles: {
          name: 'styles'.test: /\.css$/,
          chunks: 'all'.enforce: true,},... }}Copy the code

Add a styles policy so that the output name is styles. CSS which is the name of this policy. Enforce: true means that the styles policy ignores other parameters of splitChunks.

Lazy loading of modules

Lazy loading of modules does not require webpack configuration, but is supported using the import() syntax provided by ES6

Let’s compare the following two pieces of business code:

document.addEventListener("click".() = >{
  import(/* webpackChunkName: "lodash" */"lodash").then(({default: _}) = >{
    console.log(_.join(["a"."b"]."-"));
  });
},false);
Copy the code
import { join } from "lodash";

document.addEventListener("click".() = >{
  console.log(_.join(["a"."b"]."-"));
},false);
Copy the code

The first piece of code says: click document to load the LoDash module asynchronously, and output a string when the module loads successfully.

The second code says: enter the interface first load loDash module, when click the page output string.

Obviously, the first code is an asynchronous loading mode. If the user does not click the page, it is unnecessary to load the corresponding module, saving resources. This is asynchronous loading, and all it takes is for WebPack to configure Babel to support ES6 syntax.

Preload and Prefetch

Let’s look at a business scenario like this:

The user clicks login, and the login dialog box is displayed. In fact, it is not necessary to complete the loading of the login pop-up module at the beginning, because it is an interactive content that must be carried out after the user sees the home page, which means that this module can be loaded after the home page is loaded.

How to achieve this effect? Browsers give us a way to load resources:

<link rel="preload" href="loginModule.js" as="script">
<link rel="prefetch" href="loginModule.js" as="script">
Copy the code
  • preloadIt starts loading in parallel;
  • prefetchThe home page module will be loaded after completion, and then to load.

To achieve this effect we don’t need to make any changes to the WebPack configuration and use the import() syntax provided by ES6 along with magic comments.

document.addEventListener("click".() = >{
  import(/* webpackPrefetch: true */ /* webpackChunkName: "lodash" */ "lodash").then(({default: _}) = >{
    console.log(_.join(["a"."b"]."-"));
  });
},false);
Copy the code

View the browser console after executing the package command:

File caching policy

We packed static resource file is to be published on the server, such as static resource name for the main js, if online have a bug at this time, we must be fixed immediately and then immediately pack and static resource updates to the server, if you don’t change the file name, because the browser’s cache problem, the user is no way to see the effect immediately, So we can add a hash configuration to the file name

output: { // Export file
  publicPath:"".filename: '[name].[hash].js'.chunkFilename:'[name].[hash].js'.path: path.resolve(__dirname,'dist')},Copy the code

Business Code:

import { forEach } from "lodash";
import $ from "jquery";

$(function () {
  forEach([1.2.3].(item) = >{
    console.log(item); })});Copy the code

After packaging, two JA files are output

  • main.[hash].jsEntry file of
  • vendors~main.[hash].jschunkfile

They share the same hash value, so when I modify the business code:

$(function () {
  forEach([1.2.3.4.5.6].(item) = >{
    console.log(item); })});Copy the code

Business code changed, but we introduce third-party libraries is no change, when executed again packing, two types of file hash values are changed, we deployed to the server, the user’s browser can indeed to reload and immediately see the effect, but the user should not reload the third library code, these but did not change. At this point we should use the [contenthash] configuration provided by WebPack, which means that only the module file hash value changes if the content changes, and the file hash value stays unchanged if the content does not change

output: {
  publicPath:"".filename: '[name].[contenthash].js'.chunkFilename:'[name].[contenthash].js'.path: path.resolve(__dirname,'dist')}Copy the code

Development environment vs. build environment

In the WebPack configuration, the mode attribute is provided to configure the development environment and the production environment. Let’s summarize the differences between the two environments in the engineering configuration:

Function \ Environment Develoment Production
Code compression Uncompressed (easy to debug) Compression (reducing code size)
Tree Shaking Disabled by default The default open
Source Map cheap-module-eval-source-map cheap-module-source-map
WebpackDevServer (local service) Need to open Don’t need
HMR (Hot Update) Need to configure the Don’t need

Normally when we write webPack configuration, we will do the configuration in separate files, because the production environment and development environment is very different.

Configuration file separation procedure

  1. Extract a common configuration, for examplejsTo deal with,cssProcessing, processing of images and other resources is the same in both development and production environments;
  2. Configure a development environment and production environment configuration separately, and then passwebpack-mergeMerge common configuration:
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common,{
  mode: 'development'. });Copy the code

Configuring global variables

plugins: [
  ...
  new webpack.ProvidePlugin({
    $:"jquery"._:"loadsh"})]Copy the code

After the $and _ global variables are configured, we can use them directly without importing them in the subsequent module writing:

export function ui (){$('body').css('background'.'green');
}
Copy the code

Using environment variables

package.json

 "scripts": {
    "dev-build": "webpack --env.development --config webpack.common.js"."dev": "webpack-dev-server --env.development --config webpack.common.js"."build": "webpack --env.production --config webpack.common.js"
  },
Copy the code

Added: –env.development, –env.production

webpack.common.js

module.exports = (env) = >{
  console.log(env); // {development:true} || {production:true}
  if(env && env.production){
    return merge(commonConfig,prodConfig);
  }else{
    returnmerge(commonConfig,devConfig); }}Copy the code

View the specific configuration code

summary

Through the study of this article and hands-on practice, I believe that we will have a more comprehensive understanding of the basic configuration of Webpack, and lay a solid foundation for learning how to optimize and the principle of Webpack.

All code in this article is hosted

Please give me a thumbs up if you like this article