webpack的bundle文件分析

一、一个入口,一个文件

  • webpack.config.js
module.exports = { entry: './main.js',// 一个入口 output: { filename: 'bundle.js' } };

  • main.js
document.write('Hello World'); // 一个文件

  • bundle.js
/******/ (function(modules) { // webpackBootstrap /******/// module缓存对象 /******/var installedModules = {}; /******/ /******/// require函数 /******/function __webpack_require__(moduleId) { /******/ /******/// 检查module是否在缓存当中,若在,则返回exports对象 /******/if(installedModules[moduleId]) { /******/return installedModules[moduleId].exports; /******/} /******/// 若不在,则以moduleId为key创建一个module,并放入缓存当中 /******/var module = installedModules[moduleId] = { /******/i: moduleId, /******/l: false, /******/exports: {} /******/}; /******/ /******/// 执行module函数 /******/modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/// 标志module已经加载 /******/module.l = true; /******/ /******/// 返回module的导出模块 /******/return module.exports; /******/} /******/ /******/ /******/// 暴露modules对象(__webpack_modules__) /******/__webpack_require__.m = modules; /******/ /******/// 暴露module缓存 /******/__webpack_require__.c = installedModules; /******/ /******/// 认证和谐导入模块具有正确的上下文的函数 /******/__webpack_require__.i = function(value) { return value; }; /******/ /******/// 为和谐导入模块定义getter函数 /******/__webpack_require__.d = function(exports, name, getter) { /******/if(!__webpack_require__.o(exports, name)) { /******/Object.defineProperty(exports, name, { /******/configurable: false, /******/enumerable: true, /******/get: getter /******/}); /******/} /******/}; /******/ /******/// 兼容非和谐模块的getDefaultExport函数 /******/__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公共路径__webpack_public_path__ /******/__webpack_require__.p = ""; /******/ /******/// 读取入口模块,返回exports导出 /******/return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) {// 模块ID为0 document.write('Hello World'); /***/ }) /******/ ]);

  • 整体分析
    整个的bundle.js是一个立即执行函数表达式(IIFE),传入的参数modules是一个数组,数组的每一项都是一个匿名函数,代表一个模块。在这里,数组的第一个参数是一个function,里面的内容就是原先main.js里面的内容。
    IIFE里面存在一个闭包,webpack_require_是模块加载函数,作用是声明对其他模块的依赖,并返回exports。参数为模块的Id(每一个模块都有一个唯一的id),不需要提供模块的相对路径,可以省掉模块标识符的解析过程(准确说,webpack是把require模块的解析过程提前到了构建期),从而可以获得更好的运行性能。
    执行modules函数的是:
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

通过借用call来使函数的this始终为module本身,参数webpack_require是为了让modules有加载其他模块的能力。
  • 程序流程
    bundle通过webpack_require(webpack_require.s = 0)来启动整个程序。首先看看缓存中有没有ID为0的模块,若存在则返回缓存中的exports暴露出来的对象;若不存在,则新建module对象,并放入缓存当中。此时,module对象和该模块的缓存对象installedModules[moduleId]还没有数据,所以要执行该模块来返回具体require其他模块的数据。传入的context是module.exports(等于installedModules[moduleId].exports)。
二、一个入口,多个文件 【webpack的bundle文件分析】main1包含inner1;main2包含inner1和inner2。
  • webpack.config.js
module.exports = { entry: { bundle1: './main1.js', bundle2: './main2.js' }, output: { filename: '[name].js' } };

  • main1.js
var inner1 = require('./inner1.js'); inner1.inner1(); document.write("我是main1")

  • main2.js
var inner1 = require('./inner1.js'); var inner2 = require('./inner2.js'); inner1.inner1(); inner2.inner2(); document.write("我是main2");

  • bundle1.js
/******/ (function(modules) { // webpackBootstrap /******/ 这部分和上面的一样 /******/// Load entry module and return exports /******/return __webpack_require__(__webpack_require__.s = 2); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { exports.inner1 = function() { document.write('我是inner1'); } /***/ }), /* 1 */, /* 2 */ /***/ (function(module, exports, __webpack_require__) { var inner1 = __webpack_require__(0); inner1.inner1(); document.write("我是main1") /***/ }) /******/ ]);

  • bundle2.js
/******/ (function(modules) { // webpackBootstrap /******/ 这部分和上面的一样 /******/// Load entry module and return exports /******/return __webpack_require__(__webpack_require__.s = 3); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { exports.inner1 = function() { document.write('我是inner1'); } /***/ }), /* 1 */ /***/ (function(module, exports) { exports.inner2 = function() { document.write('我是inner2'); } /***/ }), /* 2 */, /* 3 */ /***/ (function(module, exports, __webpack_require__) { var inner1 = __webpack_require__(0); var inner2 = __webpack_require__(1); inner1.inner1(); inner2.inner2(); document.write("我是main2"); /***/ }) /******/ ]);

webpack的bundle文件分析
文章图片
index.html
  • 整体分析
    对于多入口文件的情况,分别独立执行单个入口的情况,每个入口文件互不干扰。
    从上面可以看到,两个入口文件main1.js和main2.js的module id都是0,所以可以知道每个入口文件对应的module id都是0。又因为每个module id都是全局唯一的,所以在main1中没有1;在main2中没有2。
    静态分析打包是事先生成chunk,inner1.js文件被重复包含了,如果需要消除模块冗余,可以通过CommonsChunkPlugin插件来对公共依赖模块进行提取。
CommonsChunkPlugin初始化参数 含义
name 给这个包含公共代码的chunk命名(唯一标识)。
filename 如何命名打包后生产的js文件。
minChunks 公共代码的判断标准:某个js模块被多少个chunk加载了才算是公共代码。
chunks 表示需要在哪些chunk(也可以理解为webpack配置中entry的每一项)里寻找公共代码进行打包。不设置此参数则默认提取范围为所有的chunk。
四、总结
  • bundle.js文件是一个立即执行函数表达式,传入参数是一个数组modules。真正执行module的是modules[moduleId].call(module.exports, module, module.exports, webpack_require); 。
  • modules数组用来保存模块初始化函数,里面存储着真正用到的模块内容以及一些id信息。
  • installedModules对象用来保存module缓存对象,方便其他模块使用。
  • webpack_require模块加载函数,require时得到的对象,参数为模块id。
  • installedModules[moduleId].exports === module.exports === webpack_exports
    总的来说,webpack分析得到所有必须模块并合并;提供让这些模块有序执行的环境。
    代码链接https://github.com/cttin/demo/tree/master/webpack

    推荐阅读