点击蓝字“前端技术优化”关注我们!
我们来分析一下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编译后,输出文件的内容如下:
/******/ (function(modules) { // webpackBootstrap
/******/ // 这里省略一大段
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* 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", { value: true });
/* 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,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.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, {
configurable: false,
enumerable: true,
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, property) { return 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,
l: false,
exports: {}
};
// Execute the module function
// 然后执行这个模块的代码,参数会传入模块系统相关的几个函数,把拿到的结果放到缓存中
// 通过把 `module.exports` 传给模块,让他自己把自己放到缓存中。
modules[moduleId].call(module.exports, module, module.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')收集依赖
依赖关系
假设我们有以下依赖关系:
在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]
返回,结束
示意图如下:
发表评论