17

Preface

In today's increasingly complex front-end engineering, module packaging tools have played an increasingly important role in our development, and webpack is one of the most popular packaging tools.

Speaking of webpack , many friends may feel both familiar and unfamiliar. Familiar is because we will use it in almost every project, and because of the complex configuration and variety of functions of webpack Especially when we use umi.js help us package the webpack configuration by another layer, webpack seems to be farther away and unfathomable.

When the interviewer asks if you know about webpack , maybe you can webpack loader and plugin , and even a series of plug-ins and a series of configurations for on-demand loading and packaging optimization, then do you know his operating mechanism and the realization of the principle of it, then we will explore together today webpack ability borders, try to understand webpack some of the implementation process and principles, refused to do API engineer.

图片

Do you know what webpack does?

From the description on the official website, it is not difficult to understand that webpack actually has the following functions:

  • Module packaging. You can package and integrate the files of different modules together, and ensure that the references between them are correct and the execution is orderly. Using packaging, we can freely divide the file modules according to our own business during development to ensure the clarity and readability of the project structure.
  • Compilation compatible. In front of the "ancient times", a bunch of handwritten browser compatible code that has been the front-end engineers make things scalp tingling, but today this problem is greatly weakened by webpack of Loader mechanism, not only can help us code Do polyfill , you can also compile and convert .less, .vue, .jsx that are not recognized by the browser, so that we can use new features and new syntax for development during development, and improve development efficiency.
  • Capacity expansion. By webpack of Plugin mechanism, the basis of our modular compiler compatible packaging and on-demand loading can be further achieved, such as code compression and a series of functions to help us further improve the quality automation, engineering efficiency and output of the package.

Tell me about the operating principle of module packaging?

If the interviewer asks you Webpack these modules together and ensures that they work properly, do you understand it?

First of all, we should briefly understand the entire packaging process of webpack

  • 1. Read the configuration parameters of webpack
  • 2. Start webpack , create a Compiler object and start parsing the project;
  • 3. Start parsing from the entry file ( entry ), and find the imported dependent modules, recursively traverse the analysis, and form a dependency tree;
  • Loader to compile the dependent module files of different file types, and finally convert them to Javascript files;
  • 5. In the whole process, webpack hooks through the publish and subscribe mode, and webpack can monitor these key event nodes and execute the plug-in task to achieve the purpose of intervening in the output result.

The parsing and construction of the file is a relatively complicated process. The webpack source code mainly relies on the compilation two core objects, compiler and 06081f1ebaafd7.

compiler object is a global singleton, which is responsible for controlling the entire webpack package construction process. compilation object is the context object for each construction. It contains all the information needed for the current construction. Every hot update and rebuild, compiler will regenerate a new compilation object, which is responsible for the construction process of this update.

The dependency between each module depends on the AST syntax tree. Each module file by Loader after parsing is complete, by acorn library generation module code AST syntax tree, by the syntax tree can also analyze whether this module dependent module, and then continue to execute the next cycle to compile a module resolution.

The final Webpack packed out bundle file is a IIFE execution of the function.

// webpack 5 打包的bundle文件内容

(() => { // webpackBootstrap
    var __webpack_modules__ = ({
        'file-A-path': ((modules) => { // ... })
        'index-file-path': ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { // ... })
    })
    
    // The module cache
    var __webpack_module_cache__ = {};
    
    // The require function
    function __webpack_require__(moduleId) {
        // Check if module is in cache
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {
                return cachedModule.exports;
        }
        // Create a new module (and put it into the cache)
        var module = __webpack_module_cache__[moduleId] = {
                // no module.id needed
                // no module.loaded needed
                exports: {}
        };

        // Execute the module function
        __webpack_modules__[moduleId](module, module.exports, __webpack_require__ "moduleId");

        // Return the exports of the module
        return module.exports;
    }
    
    // startup
    // Load entry module and return exports
    // This entry module can't be inlined because the eval devtool is used.
    var __webpack_exports__ = __webpack_require__("./src/index.js");
})

And webpack4 comparison, webpack5 pack out of the bundle made considerable streamlining. In the above package demo , there are only three variables and one function method in the entire immediate execution function. __webpack_modules__ stores the JS content of each file module after compilation, __webpack_module_cache__ used as a module cache, and __webpack_require__ is a set of dependencies implemented within Webpack Introduce functions. The last sentence is the starting point for the code to run, starting from the entry file to start the entire project.

Among them, it is worth mentioning that the __webpack_require__ module introduces functions. When we develop modularity, we usually use the ES Module or CommonJS specification to export/import dependent modules. When webpack packaged and compiled, it will be replaced with its own __webpack_require__ to realize the introduction of the module. And export, so as to realize the module caching mechanism, and smooth out some differences between different module specifications.

Do you know what sourceMap is?

Speaking of sourceMap , many friends may immediately think of the devtool Webpack configuration, as well as the corresponding eval , eval-cheap-source-map and other optional values and their meanings. In addition to knowing the difference between different parameters and the difference in performance, we can also learn about the implementation of sourceMap

sourceMap is a technology that maps compiled, packaged, and compressed code back to the source code. Since the packaged and compressed code is not readable at all, once an error or problem is encountered during development, the problem of debug It will bring a very bad experience. sourceMap can help us quickly locate the source code and improve our development efficiency. sourceMap is not Webpack unique function of Webpack , but 06081f1ebab14e supports sourceMap , like JQuery also supports souceMap .

Since it is a source code mapping, it is necessary to have a mapping file to mark the location of the corresponding source code in the obfuscated code. Usually this mapping file .map , and the data structure inside is roughly like this:

{
  "version" : 3,                          // Source Map版本
  "file": "out.js",                       // 输出文件(可选)
  "sourceRoot": "",                       // 源文件根目录(可选)
  "sources": ["foo.js", "bar.js"],        // 源文件列表
  "sourcesContent": [null, null],         // 源内容列表(可选,和源文件列表顺序一致)
  "names": ["src", "maps", "are", "fun"], // mappings使用的符号名称列表
  "mappings": "A,AAAB;;ABCDE;"            // 带有编码映射数据的字符串
}

The mappings data has the following rules:

  • Each group of a line in the generated file is separated by ";";
  • Each paragraph is separated by ",";
  • Each segment consists of 1, 4 or 5 variable length fields;

With this mapping file, we only need to add this comment at the end of our compression code to make the sourceMap effective:

//# sourceURL=/path/to/file.js.map

With this comment, the browser will sourceURL , and after parsing it through the interpreter, the mapping between the source code and the obfuscated code will be realized. Therefore, sourceMap is actually a technology that requires browser support.

If we look closely at the bundle file packaged by webpack, we can find that in the default development development mode, _webpack_modules__ will be added to the end of the code of each 06081f1ebab23a file module to support //# sourceURL=webpack://file-path?

There is a set of more complicated rules for the generation of sourceMap mapping table. Interested friends can read the following articles to help understand the principle of soucrMap:

The principle of [1]

Source Maps under the hood – VLQ, Base64 and Yoda[2]

Have you written Loader? Briefly describe the idea of writing loader?

From the above we can actually know the code package, Webpack final result is a packed out Javascript code actually Webpack internal default only able to handle JS module code, in the packaging process, by default all files are encountered It JavaScript code, so when there is a non- JS type file in the project, we need to perform the necessary conversion to continue the packaging task, which is also the meaning of the existence of the Loader

We should already be very familiar with the configuration and use of Loader

// webpack.config.js
module.exports = {
  // ...other config
  module: {
    rules: [
      {
        test: /^your-regExp$/,
        use: [
          {
             loader: 'loader-name-A',
          }, 
          {
             loader: 'loader-name-B',
          }
        ]
      },
    ]
  }
}

It can be seen from the configuration that for each file type, loader supports multiple configurations in the form of an array. Therefore, when Webpack converts the file type, each loader will be called in sequence and the previous loader returned. The content will be used as an loader for the next 06081f1ebab324. Therefore loader needs to follow some specifications. For example, the return value must be a standard JS code string to ensure that the next loader can work normally. At the same time, the development needs to strictly follow the "single responsibility" and only care about the output of loader Output.

loader function of this context consists webpack provided by this relevant properties of the object, thus providing a current loader various information data required, in fact, this this points to a called loaderContext of loader-runner specific object. Interested friends can read the source code by themselves.

module.exports = function(source) {
    const content = doSomeThing2JsString(source);
    
    // 如果 loader 配置了 options 对象,那么this.query将指向 options
    const options = this.query;
    
    // 可以用作解析其他模块路径的上下文
    console.log('this.context');
    
    /*
     * this.callback 参数:
     * error:Error | null,当 loader 出错时向外抛出一个 error
     * content:String | Buffer,经过 loader 编译后需要导出的内容
     * sourceMap:为方便调试生成的编译后内容的 source map
     * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
     */
    this.callback(null, content);
    // or return content;
}

For more detailed development documents, you can directly view Loader API [3] on the official website.

Have you written Plugin? Briefly describe the idea of writing plugin?

If Loader is responsible for file conversion, then Plugin is responsible for function expansion. Loader and Plugin as Webpack , bear two different responsibilities.

As mentioned above, webpack based on the publish-subscribe model and broadcasts many events during its running life cycle. The plug-in can perform its own plug-in tasks at a specific stage by listening to these events, so as to achieve the functions they want.

Since the release based on a subscription model, then know Webpack in the end what events provide a hook for plug-in developers to use is very important, mentioned above had compiler and compilation is Webpack two very core of the object, which compiler exposed and Webpack entire life Cycle-related hooks ( compiler-hooks [4]), while compilation exposes event hooks with smaller granularity related to modules and dependencies ( Compilation Hooks [5]).

Webpack event mechanism based webpack set themselves to achieve Tapable event flow scheme ( GitHub [6])

// Tapable的简单使用
const { SyncHook } = require("tapable");

class Car {
    constructor() {
        // 在this.hooks中定义所有的钩子事件
        this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            brake: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
        };
    }

    /* ... */
}


const myCar = new Car();
// 通过调用tap方法即可增加一个消费者,订阅对应的钩子事件了
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());

Plugin development and development Loader , the need to follow some of the norms and principles on the development of:

  • The plug-in must be a function or an apply method in order to access the compiler instance;
  • compiler and compilation objects passed to each plug-in are the same reference. If the attributes on them are modified in one plug-in, the subsequent plug-ins will be affected;
  • Asynchronous events need to call the callback function to notify Webpack enter the next process when the plug-in finishes processing the task, otherwise it will get stuck;

After understanding the above content, it is not difficult Webpack Plugin

class MyPlugin {
  apply (compiler) {
    // 找到合适的事件钩子,实现自己的插件功能
    compiler.hooks.emit.tap('MyPlugin', compilation => {
        // compilation: 当前打包构建流程的上下文
        console.log(compilation);
        
        // do something...
    })
  }
}

For more detailed development documents, you can directly view Plugin API [7] on the official website.

At last

This article also combines some excellent articles and webpack itself, and roughly talks about a few relatively important concepts and processes. The implementation details and design ideas need to be read and understood slowly in combination with the source code.

Webpack has changed the traditional front-end development model and is the cornerstone of modern front-end development. Such an excellent open source project has many excellent design ideas and concepts that can be used for reference. Naturally, we should not just stop at API , try to read the source code with questions, understand the implementation process and principles, and let us learn More knowledge and deeper understanding can be used in projects with ease.

Reference

[1] Explore the principle of Source Map: https://blog.fundebug.com/2018/10/12/understanding_frontend_source_map/

[2]Source Maps under the hood – VLQ, Base64 and Yoda: *https://docs.microsoft.com/zh-cn/archive/blogs/davidni/source-maps-under-the-hood-vlq-base64-and-yoda.

[3]Loader API: *https://www.webpackjs.com/api/loaders/.

[4]compiler-hooks: https://webpack.js.org/api/compiler-hooks/

[5]Compilation Hooks: https://webpack.js.org/api/compilation-hooks/.

[6]github: https://github.com/webpack/tapable.

[7]Plugin API: https://www.webpackjs.com/api/plugins/

original address (front-end complete)


兰俊秋雨
5.1k 声望3.5k 粉丝

基于大前端端技术的一些探索反思总结及讨论