webpack c-Webpack源码分析:bundle.js内容分析

点击蓝字“前端技术优化”关注我们!

我们来分析一下webpack打包的文件内容。 我们的源码如下:

a.js

import { log } from './b.js'
log('hello')

b.js

export const log = function (m{
  console.log(m)
}

export const error = function (m{
  console.error(m)
}

为了减少干扰,我们这里没有使用babel进行编译。 如果你看到 module.exports 而不是 __webpack_exports__,那是因为你启用了 babel 模块

webpack c_webpack安装教程

一共两个文件,内容很简单。 那么我们用webpack编译后,输出文件的内容如下:

/******/ (function(modules// webpackBootstrap
/******/  // 这里省略一大段
/******/     return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__{

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { valuetrue });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__b_js__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_0__b_js__["a" /* log */])('hello')

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__{

"use strict";
const log = function (m{
  console.log(m)
}
/* harmony export (immutable) */ __webpack_exports__["a"] = log;

const error = function (m{
  console.error(m)
}
/* unused harmony export error */


/***/ })
/******/ ]);

为了方便查看代码,暂时省略一段webpack生成的代码。 我们可以看到,整个代码是一个自执行函数,它接收一个链表,链表中的每一项都是我们的模块之一,这里我们有两个模块。 但是,该模块的代码是由函数包装的。

自执行函数

这个自执行函数看起来像这样:

(function (modules{})([module0, module1])

其中,module0和module1就是我们的两个模块a和b,不过它们也是由一个函数封装的。 这段代码会将我们的模块加载到一个链表中webpack c,并将其传递给自执行函数,该函数负责调用该模块。

模块代码

所以我们的模块看起来像这样:

(function(module, __webpack_exports__, __webpack_require__{

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { valuetrue });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__b_js__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_0__b_js__["a" /* log */])('hello')

/***/ })

这里函数需要几个参数,因为这些是模块系统需要使用的参数。 比如我们的import替换为__webpack_require__,那么这个函数是通过参数传入的,所以不会报错。 此外,export 已替换为 __webpack_exports__。

webpack工具功能

让我们仔细看看上面省略的 webpack 本身生成的函数:

(function(modules// webpackBootstrap
    // The module cache
    var installedModules = {};

    // The require function
    function __webpack_require__(moduleId{

        // Check if module is in cache
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // Create a new module (and put it into the cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            lfalse,
            exports: {}
        };

        // Execute the module function
        modules[moduleId].call(module.exports, modulemodule.exports, __webpack_require__);

        // Flag the module as loaded
        module.l = true;

        // Return the exports of the module
        return module.exports;
    }


    // expose the modules object (__webpack_modules__)
    __webpack_require__.m = modules;

    // expose the module cache
    __webpack_require__.c = installedModules;

    // define getter function for harmony exports
    __webpack_require__.d = function(exports, name, getter{
        if(!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurablefalse,
                enumerabletrue,
                get: getter
            });
        }
    };

    // getDefaultExport function for compatibility with non-harmony modules
    __webpack_require__.n = function(module{
        var getter = module && module.__esModule ?
            function getDefault() return module['default']; } :
            function getModuleExports() return module; };
        __webpack_require__.d(getter, 'a', getter);
        return getter;
    };

    // Object.prototype.hasOwnProperty.call
    __webpack_require__.o = function(object, propertyreturn Object.prototype.hasOwnProperty.call(object, property); };

    // __webpack_public_path__
    __webpack_require__.p = "";

    // Load entry module and return exports
    return __webpack_require__(__webpack_require__.s = 0);
})

直接看会晕,我们简写一下:

(function(modules{
    var installedModules = {};
    function __webpack_require__(moduleId{}
    // Load entry module and return exports
    return __webpack_require__(__webpack_require__.s = 0);
})

这个比较容易理解,这个函数包含三个部分

__webpack_require__就是给他一个moduleId,他就会返回这个模块的内容。

function __webpack_require__(moduleId{
        // 看看有没有缓存,有的话就直接用了
        // Check if module is in cache
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // Create a new module (and put it into the cache)
        // 如果没有的话,那么就先创建一个空的缓存
        var module = installedModules[moduleId] = {
            i: moduleId,
            lfalse,
            exports: {}
        };

        // Execute the module function
        // 然后执行这个模块的代码,参数会传入模块系统相关的几个函数,把拿到的结果放到缓存中
        // 通过把 `module.exports`  传给模块,让他自己把自己放到缓存中。
        modules[moduleId].call(module.exports, modulemodule.exports, __webpack_require__);

        // Flag the module as loaded
        module.l = true;

        // Return the exports of the module
        // 最后得到了模块导出的内容,返回就可以了
        return module.exports;
    }

webpack的模块依赖的处理流程

后面我们提到,webpack会通过actorn解析JS句型,为require('xxx.js')收集依赖

webpack安装教程_webpack c

依赖关系

假设我们有以下依赖关系:

在bundle.js内容分析的文章中,我们知道webpack最终打包的bundle.js会将所有模块变成一个链表,也就是把一棵树变成一个链表。 那么如果按照上图的依赖关系,最终打包的链表中模块的顺序会是怎样的呢?

虽然很容易从代码中剖析出来。 由于webpack处理每个模块都是从entry开始的,所以遇到require后就会陷入对应模块的处理,即递归处理依赖树。 这是深度优先遍历的典型递归解决方案。 ,是先序优先的遍历。过程是这样的

处理main.js,记录入口[main]

遇到require(a),记录[main,a]

进入a模块webpack c,遇到require(c)这句话,记录[main,a,c]

同理,遇到require(d)时,记录[main,a,c,d]

返回main.js,下一句是require('b'),记录[main,a,c,d,b]

进入模块b,遇到require(e)这句话,记录一下[main,a,c,d,b,e]

返回,结束

示意图如下: