webpack打包分析

72 阅读3分钟

1、Webpack介绍

  • Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

下面分析下 一个 简单的 index.js模块 引用 a.js模块的打包结果

index.js

let str = require('./a.js');
console.log(str)

a.js

module.exports = 'stl';

打包的结果: bundle.js

(function (modules) { // webpackBootstrap
  // The module cache 先定义一个缓存
  var installedModules = {};
  // "./src/index.js" :{}

  // The require function   配置了 实现了require
  function __webpack_require__(moduleId) { // "./src/index.js"

    // 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;
  }


  return __webpack_require__(__webpack_require__.s = "./src/index.js"); // 入口模块
})
  ({
    "./src/a.js": // key -> 模块的路径
      (function (module, exports) { // value 函数
        eval("module.exports = 'stl';\n\n//# sourceURL=webpack:///./src/a.js?");
      }),
    "./src/index.js":
      (function (module, exports, __webpack_require__) {
        eval("let str = __webpack_require__( "./src/a.js");\r\n\r\nconsole.log(str);\n\n//# sourceURL=webpack:///./src/index.js?");
      })
  });

可以看到,打包的结果是一个自执行匿名函数,函数的参数 modules 是一个对象 :

{
  "./src/a.js": // key -> 模块的路径
    (function (module, exports) { // value 函数
      eval("module.exports = 'stl';\n\n//# sourceURL=webpack:///./src/a.js?");
    }),
  "./src/index.js":
    (function (module, exports, __webpack_require__) {
      eval("let str = __webpack_require__( "./src/a.js");\r\n\r\nconsole.log(str);\n\n//# sourceURL=webpack:///./src/index.js?");
    })
}

对象的key 是模块的路径, ./src/a.js./src/index.js value 则是对应的函数,下称模块函数:例如

(function (module, exports) { // value 函数
  eval("module.exports = 'stl';\n\n//# sourceURL=webpack:///./src/a.js?");
})

生成的 匿名函数 自带注释 webpackBootstrap 意思是webpack 启动函数(下称 启动函数

在函数内部先定义了一个 installedModules变量 ,这其实是一个缓存,使用key value的形式存储模块,意思是如果一个模块加载完了,就不需要再次加载,而是直接在缓存中拿


// The module cache 先定义一个缓存
var installedModules = {};

之后实现了一个 require 方法(__webpack_require__)(commonJs模块的require方法是不能在浏览器中识别的 )

// The require function   配置了 实现了require
function __webpack_require__(moduleId) { // "./src/index.js"

  // 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: {}
  };

再看这个 __webpack_require__ 方法何时被调用的,发现是在 启动函数的 return 调用 了该函数,并传入了 入口模块 的key(路径)

return __webpack_require__(__webpack_require__.s = "./src/index.js"); // 入口模块

第一次调用,模块index.js 肯定不在缓存中

所以 __webpack_require__函数会 执行如下代码块:


// 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;

modules[moduleId] 就是传入的 ./src/index.js对应的模块函数,并执行,传入的 参数是 module.exports, module, module.exports, __webpack_require__

观察"./src/index.js" 对应的模块函数 ,结合上面使用call传入的参数 ,会发现 该模块函数 再次执行了 __webpack_require__ 方法,加载了 "./src/a.js"的模块函数,并执行: "./src/a.js"的模块函数如下

"./src/a.js": // key -> 模块的路径
  (function (module, exports) { // value 函数
    eval("module.exports = 'stl';\n\n//# sourceURL=webpack:///./src/a.js?");
  }),

可以看到 结果是 module.exports = 'stl'

(function (module, exports, __webpack_require__) {
  eval("let str = __webpack_require__( "./src/a.js");\r\n\r\nconsole.log(str);\n\n//# sourceURL=webpack:///./src/index.js?");
})

所以 "./src/index.js"对应的模块函数的str 就是 'stl' 打印 stl

这个 bundle.js文件是可以在浏览器中直接运行的 这样就实现了 模块 打包

上面实现的是同步加载

如果进行了分包的 异步加载(如es6 module import()函数) ,那么就会使用__webpack_require__.e方法, 使用JsonP的方式加载另一个包的js文件再进行执行,