What exactly is Webpack? How to understand Webpack

This article is mainly aimed at combing the knowledge of Webpack at station B from principle to actual combat . I have written some more detailed knowledge of Webpack before. For details, see Front- engineering (webpack) , which introduces the use of front-end engineering and loader in more detail , common plug-ins for webpack, Source Map and other knowledge.

The focus of this article:

  1. Understand the modularity of the front end;
  2. Understand the core idea of ​​Webpack packaging;
  3. The core of Webpack: loader and plugin. It is necessary to distinguish when loader is used and when to use plugin, and the technical selection ability is required.

1. Introduction to Webpack

1.1 What is Webpack?

Official definition: Webpack is a static module bundler for modern JavaScript applications

Own explanation:

  • Explain JavaScript: Webpack only understands JavaScript as a language without special configuration, and can only process JavaScript as a language. Other types of files need to be configured with loaders or plug-ins for processing.
  • Explain the packager: There are many js files in a project, if we keep these js files separated, then we have to consider the loading order of these files , but the dependencies are complicated. This process is also very complicated, and it is difficult for us to think clearly; secondly, the performance overhead is definitely much higher than loading only one. If you can combine a, b, and c into one bundle.js file, it will definitely cost more than loading multiple files. much smaller.
    image-20220223222944920

1.2 Background generated by Webpack

1. Why pack?

We feel that the native CSS is not easy to use, so we propose Sass and Less. We want the front-end code to also have the ability of type verification, so we have typescript. Aiming at the following problems, we propose a packaging solution:

  1. A project depends on multiple files , and the relationship between each dependent file is difficult to sort out, the degree of coupling is high, and the code is difficult to maintain;
  2. Packaging all dependent packages into a js file (bundle.js) will effectively reduce the number of HTTP requests and improve performance; (a front-end performance optimization strategy)
  3. More logic, more files, increased project complexity

The emergence of a technology must be due to the fact that the past technology cannot meet the needs of current production and development or is not convenient enough.

 

2. Why use Webpack to package?

  • Aiming at the scenario where a project relies on multiple modules , a modular solution has been proposed in the industry , and Webpack can be extended because it is a typical excellent modular solution ;

  • Possess strong packaging skills;

  • Acting as a " translator ", for example: when writing a piece of typescript code in the browser console, the browser will report an error directly, because the browser cannot understand typescript, and during the packaging process of Webpack, the loader will read the browser Translate the code that you don't understand into the code that the browser can understand;

  • More advanced functions are assisted by plugins ;

  • Both plugin and loader are pluggable , which means you can insert it when you need it, and delete it when you don't need it. Webpack is powerful and flexible

 

2. The principle and background of Webpack

2.1 Understanding front-end modularity

1. Scope

Start to understand from the scope.

The scope describes the accessibility of variables, functions, and objects in the runtime code . Simply put, the scope determines the visibility of variables and other resources in the code.

(1) Global scope

  • In JS, when we start writing JavaScript code in a file, it is in the global scope;
  • During the execution of JavaScript, there is only one global scope;
  • Variables and other resources in the global scope are mounted on the global object (window in the browser, global in node).
var a = 1;
window.a; // 1

(2) Local scope

function a(){
    
    
    var v = 1;
}
window.v; // undefined

 

2. Namespace

Here is an example of a naming conflict:

In the past, when we wanted to import multiple js files, we would use multiple script tags to import them, but this can easily lead to naming conflicts between variables, as follows:

<body>
    <script scr="./moduleA.js"></script>
    <script scr="./moduleB.js"></script>
    <script scr="./moduleC.js"></script>
</body>

moduleA, moduleB, and moduleC all share a global scope.

If at this point, declare in moduleA:

var a = 1

In moduleB declare:

var a = 2

In moduleC:

var b = a + 1
console.log(b) //3,不是2

The declaration of a in moduleB will overwrite the declaration of a in moduleA

important! ! ! : In any JavaScript file, the variable or function declaration of the top-level scope will be exposed globally , so that other scripts can also obtain the variables in other scripts, which will easily lead to naming conflicts . To put it simply, the top-level scope of code execution in js is the global scope, and variable and function definitions can easily conflict.
 

Solution 1: Add a namespace to the variable

// moduleA.js
var Susan = {
    
    
    name: "susan",
    sex: "female",
    tell: function(){
    
    
        console.log("我是:",this.name)
    }
}

Adding a namespace to variables can solve the problem of naming conflicts, but it will also bring about a security problem . We only want to expose a method to display personal information, and we don’t want these personal information to be changed at will, but in solution 1, moduleC Susan's information can be directly modified Susan.name = 'Jack'by directly using , so scheme 1 cannot guarantee the internal security of module attributes.

Simply adding a namespace can only resolve naming conflicts and prevent variables from being overwritten.

 

Solution 2: namespace + closure (protect variables with function scope)

Writing method one:

// 定义模块内的闭包作用域(模块作用域),以moduleA为例
// 立即执行函数
var SusanModule = (function(){
    
    
    var Susan = {
    
    
        // 自由变量
        name: "susan",
        // 自由变量
        sex: "female",
        // 只允许访问tell方法,不能访问和修改其他属性
        return {
    
    
            tell: function(){
    
    
                console.log("我是:",this.name)
                console.log("我的性别是:",this.sex)
       		}
		}
    }
})()

What are free variables? Simply put, it is a cross-scope variable, you can click here for reference. (There is a good knowledge point in it: when the function is created, the scope of the function has been determined, not when it is called )

At this time, using SusanModule.namewill return undefined, and the internal properties cannot be accessed. The function scope has a scope independent of the global scope, which cannot be accessed externally , so the variables are well protected with closures.

 
Writing method 2: Put the mounting process inside the immediate execution function (recommended)

This is also how early modules implemented

(function(window){
    
    
    var name = "susan"
    var sex = "female"
    functioon tell(){
    
    
        console.log("我是:",this.name)
    }
    window.susanModule = {
    
    tell}
})(window)// window作为参数传进去

 

3. Modularity

Modularization is similar to the fact that a company has multiple departments. In software engineering, code with specific functions is usually encapsulated into a module with high cohesion and low coupling. Each performs its duties and completes different functions.

Advantages of modularity:

  • Scope encapsulation: Expose methods through interfaces, but protect the security inside the module and avoid the problem of polluting the global namespace.
  • reusability
  • Decoupling for easy maintenance

 

2.2 Evolution history of modular scheme

The modular solution evolved: AMD, COMMONJS, ES6 MODULE

1. AMD (Asynchronous Module Definition)

AMD:Asynchronous Module Definition

currently rarely used

// 求和模块
define("getSum", ["math"], funtion(math){
    
    
       return function (a,b){
    
    
    log("sum:"+ math.sum(a, b))
}
})
  • First parameter: the name of the module
  • The second parameter: the dependencies of the module
  • Third parameter: function or object

Benefits: Explicitly show other modules that the module depends on, and the definition of the module is no longer bound to the global object, which enhances security.

 

2. COMMONJS

It was originally a server-side specification, and later nodejs adopted the commonjs modular specification

Main points:

  • Each file in commonjs is a module and has its own scope and context;
  • Module dependencies are introduced through the require function;
  • If you want to expose the interface of the module to the outside, you need to export it through exports.

Benefits: Like AMD, it emphasizes that module dependencies must be explicitly imported, which facilitates the maintenance of complex modules, and the order in which each module is introduced

// 通过require函数来引用
const math = require("./math");

// 通过exports将其导出
exports.getSum = function(a,b){
    
    
    return a + b;
}

 

3. ES6 MODULE

At present, it is the most used. Starting from ES6, modularization has native knowledge at the syntax level.

// 通过import函数来引用
import math from "./math";

// 通过export将其导出
export function sum(a, b){
    
    
    return a + b;
}

 

2.3 Webpack packaging principle

focus:

  • The relationship between Webpack and immediate execution functions
  • The core logic of Webpack packaging

First, let's analyze the logic of the immediate execution function. Webpack also uses the idea of ​​​​the immediate execution function:

The general structure abstracted:

(function(module) {
    
    
    var installedModules = {
    
    }; // 放置已经被加载过的模块
    //webpack加载模块的核心
    function __webpack_require__(moduleId){
    
    
        // SOME CODE
    }
    //最后加载工程的入口模块
    return __webpack_require__(0); // entry file
})([ /* modules array */])

Implementation of the core method:

function __webpack_require__(moduleId){
    
    
    // check if module is in cache 检查需要加载的这个模块是否已经加载过
    if(installedModules[moduleId]){
    
    
        return installedModules[moduleId].exports;
    }
    // create a new module (and put into cache)
    var module = installedModules[moduleId] = {
    
    
        i: moduleId,
        l: false,
        exports: {
    
    }
    };
    // exe the module func 模块逻辑执行
    modules[moduleId].call{
    
    
        module.exports,
            module,
            module.exports,
            __webpack_require__
    };
    // flag the module as loaded
    module.l = true;
    // return the exxports of the module
    return module.exports;
}

 

1. Webpack packaging process/core idea

  1. Starting from the entry file, analyze the dependency tree of the entire application, that is, which modules are depended on;
  2. Wrap each dependent module and put it in an array waiting to be called;
  3. Implement the method of module loading and put it into the environment where the module is executed to ensure that the modules can call each other;
  4. Put the logic of executing the entry file in a function expression, and execute this function immediately.

 

3. Knowledge about npm

focus:

  • Understanding Package Managers
  • Familiar with npm core features
  • Understand the concepts of npm repositories and dependencies
  • Understand npm semantic version
  • Master the method of using npm to customize project scripts

 

3.1 Configure the development environment - npm and package manager

Package Manager: A tool that allows developers to easily obtain code and distribute code

Explanation of important fields in package.json :

{
    
    
  "name": "demo", //包名称
  "version": "1.0.0",  //版本号
  "description": "",
  "main": "index.js", //包执行的入口
  "scripts": {
    
      //自定义脚本命令
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

 

3.2 Understand the concepts of npm warehouses and dependencies

Warehouse: A site that follows npm-specific packages and provides an API for users to upload or download, etc.

Dependencies are placed in dependencies or devDependencies, the difference between them:

  • dependencies: It is the production environment dependency, often placed in function-related dependencies, the command when installing the package ( npm install XXX -s)
  • devDependencies: It is the dependency of the development environment, and often places packages that are only used during development, such as: ESLint, the command when installing the package ( npm install XXX -d)

 

3.3 npm semantic version

  • ^version: medium version and minor version

    ^1.0.1 ->1.x.x

  • ~version: Minor version

    ~1.0.1 -> 1.0.x

  • version: specific version

Benefits: Make it easy for npm publishers to push the latest minor version to users

 

3.4 npm install process

  • Find the package version information file (package.json) and install it according to it
  • Find dependencies in package.json and check other version information files in the project
  • If a new package is found, update the version information file

When we find that a package in the project is inconsistent with what we expected, we should first look at the source and version of the package in the version information file.

 

4. Webpack.config.js

If there is a webpack.config.js configuration file, webpack will package it according to the configuration in webpack.config.js

Suppose the project structure is as follows:
insert image description here
webpack.config.js is set as follows:

// webpack.config.js
const path = require('path')

module.exports = {
    
    
    entry: './app.js', //入口文件路径
    output: {
    
    
        //__dirname 表示当前目录下的名字
        path: path.join(__dirname, 'dist'),//必须是绝对路径
        filename: 'bundle.js' //打包输出的文件名
    },
    devServer: {
    
    
    	port: 3000,  //服务端口,默认是8080
        publicPath: '/dist' //打包后文件所在文件夹
	}
}

In the project, webpack-dev-server can be established. Its function: it can monitor the changes of the project file directory. If the project file is updated, it will automatically package and automatically refresh the browser .

Other configuration items:

  • Whether to cache can improve the speed of webpack packaging execution. The configuration is as follows:
cacheDictionary: true/false;
  • When .js .jsx .json files are referenced, there is no need to add a suffix, only the file name is required, but the full name is still required for duplicate names. The configuration is as follows:
resolve: extensions:['.js','.jsx','.json']

For more settings of configuration items, please refer to here

 

Five, Webpack core features

focus:

  • Master the idea of ​​"everything is a module and loader"
  • Understanding the "key man" in Webpack

5.1 loader

In addition to JavaScript, if you need to use webpack to pack other types of files, you need to configure the corresponding loader, so the loader is to enhance and broaden the functions of webpack. If you want to package css files, you need to install css-loader.

Note that :

  • The css-loader only solves the problem of css syntax analysis. Only using the css-loader can not load the style on the page, and the style-loader is also needed .

  • The configuration order of loader is opposite to its loading order , so style-loader must be placed before css-loader! ! !

const path = require('path')

module.exports = {
    
    
    ...
    module: {
    
    
        rules: [
            {
    
    
                test: /\.css$/, //是需要匹配文件的正则表达式
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
}

 

5.2 plugins

Compared with the loader, the plugins mechanism puts more emphasis on the ability to monitor events . The plugin can monitor some events inside webpack and change the output of some files after packaging. It packs smaller files .

const path = require('path')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
    
    
    ...
    plugins:[
        new UglifyJSPlugin()
    ]
}

 

6. Webpack construction project

focus:

  • Master the usage and principle of babel
  • Master the usage of high frequency loader and plugins
  • Master production-level webpack configuration methods

6.1 babel

Function: Convert high-version syntax ES6 to low-version syntax

Install babel command:npm install @babel/core @babel/cli

 

Babel configuration method 1: configure conversion rules directly on the command line

Command to install transformation rules:npm install @babel/present-env

How to use (direct compilation):babel index.js --presets=@babel/preset-env

 

Babel configuration method two: add babel configuration parameters in package.json

{
    
    
  "name": "demo", 
  "babel": {
    
    
      "presets": ["@babel/presets-env"]
  }
}

 

Babel configuration method 3: Create a file in the same directory as the package.json file .babelrcand configure babel parameters in it (same as 2)

 

6.2 html-webpack-plugin

The above processing of js with babel-loader is at the file level. And index.html is processed as an entry, so the processing of index.html is a node dimension, and the processing of this node dimension often uses plugins , and the plugin for processing html here is html-webpack-plugin.

html-webpack-plugin function:

  1. Make a copy of the specified page and put it in the root directory, put the copied page in the memory , and put the source file in the disk;

  2. Dynamically add the hash after each compile to external resources such as script and link introduced in html files to prevent the problem of referencing cached external files;

  3. Can generate and create html entry files, for example, a single page can generate an html file entry, configure N html-webpack-plugins to generate N page entries;

  4. Automatically inject the script packaged in the memory into the copied page in the memory .

Plugins often exist in the form of constructors, and you must first introduce them to use them.

// 1.导入 HTML 插件,获得一个构造函数
const HtmlPlugin = require('html-webpack-plugin');

// 2.创建HTML插件实例对象
const htmlPlugin = new HtmlPlugin({
    
    
  template: './src/index.html', // 指定原文件的存放路径,想复制的文件
  filename: './index.html'  // 指定生成的文件的存放路径和文件名
});

module.exports = {
    
    
  plugins: [htmlPlugin], //3.通过 plugins 节点,是htmlPlugin插件生效 
}

 

6.3 Simplify the operation of commands in the command line

After configuring the loader, plugin and parameters that should be there, we start to pack and run. Enter webpack-dev-server --open --config--open and --config on the command line are parameters for configuring webpack-dev-server. There are only two configurations here. If there are For more configuration, writing directly on the command line is complicated and easy to write wrong. In order to simplify the writing of the command line, this is the scripts package.jsonin customize the build command and start / dev command through the custom command line, as follows :

{
    
    
  "scripts": {
    
      
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production",
    "start": "webpack-dev-server --mode development -open"
  },
}

 

6.4 HMR (Hot Update/Hot Replacement)

Nowadays, single pages are popular, and there is a strong demand that changes can be updated without refreshing the page . webpack also integrates this capability and uses HMR to achieve it.

1. Implementation of HMR

Add in plugin: webpack.HotModuleReplacementPlugin()

//webpack.config.js
cosnt webpack = require('webpack')
module.exports = {
    
    
    ...
    plugins:[
        new UglifyJSPlugin(),
        new webpack.HotModuleReplacementPlugin()
    ],
    derServer: {
    
    
        hot: true
    }
}

It also needs to be configured in the entry file:

//入口文件
if(module.hot) {
    
    
    module.hot.accept(error => {
    
    
        if(error) {
    
    
            console.log('热替换出现bug了')
        }
    })
}

 

7. Webpack and front-end performance

focus:

  • Packaging result optimization : space dimension, hope that the packaged file volume is as small as possible, the transmission process is fast, and the user experience is good;
  • Construction process optimization : the optimization of the time dimension is concerned with the shortest possible events of building files during the development process;
  • Tree-Shaking

 

7.1 Optimization of packaging results

Plug-ins can be customized TerserPluginthrough , webpack has been preset, so no installation is required. optimizationMake specific configurations in

const webpack = require('webpack')

module.exports = {
    
    
    optimization: {
    
    
        minimizer: [new TerserPlugin({
    
    
            // 加快构建速度
            cache:true,
            parrlel: true,// 多线程处理,因为压缩比较耗时间
            terserOptions : {
    
    
                compress: {
    
    
                    //删除掉一些没有用的代码
                    unused: true, 
                    drop_debugger: true,
                    drop_console: true,
                    dead_code:true
                }
            }
        })]
    }
}

 

Visualization of packaging results

How to evaluate the quality of the packaging result: You can use the webpack analyzer webpack-bundle-analyzerto visualize the components of the packaging result, which is a plugin.

Install:npm install webpack-bundle-analyzer

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
    
    
  plugins: [
      new BundleAnalyzerPlugin()
  ]
}

 

7.2 Build process optimization

  • Reduce lookups: exclude, include
  • Reduce parsing: noParse
  • Increase the "staff" of work: multi-threaded parrlel, happyPack , thread-loader
  • Precompiled
  • Cache: cache
  • Optimizing Source Maps

thread-loader : optimize for loader, put loader in thread pool worker to achieve the purpose of multi-thread construction, it must be placed at the front of all loader configuration items when used . The loader form exists.

happyPack : Multi-process model to speed up code construction. Create a thread pool based on the number of CPUs . plugin form exists.

const HappyPack = require('happypack')
const happyThreadPool = HappyPack.ThreadPool({
    
    size:OscillatorNode.cpus().length})

module.exports = {
    
    
  plugins: [
      new HappyPack({
    
    
          id:'jsx',
          threads: happyThreadPool,
          loaders:['babel-loader'] //根据需要写
      })
  ]
}

 

7.3 Tree-Shaking

Tree-Shaking is an optimization feature of webpack itself. Its essence is to eliminate useless code (DCE) . Tree-Shaking is an implementation of DCE. This process is just like the name, to shake a tree, the bad leaves and fruits on the tree will fall off.

What exactly does Tree-Shaking do? ?

It allows webpack to analyze the introduction of ES6 Modules and remove unused imports , but only in the mode production environment .

 

Hope you get something out of it! Well I'm going to bed, good night! !

Guess you like

Origin blog.csdn.net/weixin_45950819/article/details/123122822