webpack之bundle文件分析

作者: 婷楼沐熙 | 来源:发表于2017-04-23 22:24 被阅读170次

    一、一个入口,一个文件

    • webpack.config.js
    module.exports = {
      entry: './main.js',  // 一个入口
      output: {
        filename: 'bundle.js'
      }
    };
    
    • main.js
    document.write('<h1>Hello World</h1>');  // 一个文件
    
    • 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('<h1>Hello World</h1>');
    /***/ })
    /******/ ]);
    
    • 整体分析
      整个的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.config.js
    module.exports = {
        entry: './main1.js',
        output: {
            filename: 'bundle.js'
        }
    }
    
    • main1.js
    var main = require('./main2.js')
    document.write('<h1>Hello World</h1>');
    main.webpack();
    
    • main2.js
    exports.webpack = function() {
        document.write('<h2>Hello Webpack</h2>');
    }
    
    • bundle.js
    /******/ (function(modules) { // webpackBootstrap
    /******/  这部分和上面的一样
    /******/    // Load entry module and return exports
    /******/    return __webpack_require__(__webpack_require__.s = 1);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
    /***/ (function(module, exports) {
    exports.webpack = function() {
        document.write('<h2>Hello Webpack</h2>');
    }
    /***/ }),
    /* 1 */
    /***/ (function(module, exports, __webpack_require__) {
    var main = __webpack_require__(0)
    document.write('<h1>Hello World</h1>');
    main.webpack();
    /***/ })
    /******/ ]);
    
    index.html
    • 整体分析
      webpack在打包的时候会分析模块间的依赖关系,对导入导出模块相关的内容做一个替换。比方说在main1.js文件中,遇到var main = require('./main2.js')就会转化为var __WEBPACK_IMPORTED_MODULE_0__main__ = __webpack_require__(0)
      由于有两个文件,所以IIFE的参数为长度是2的数组,并且按照require的顺序排列。
    • 程序的流程
      bundle通过__webpack_require__(__webpack_require__.s = 1)来启动整个程序。与第一种情况不同的是,这里首先检查缓存中是否有ID为1的模块,因为main1.js依赖于main2.js,所以在main.js中调用模块加载函数。当执行到var main = __webpack_require__(0),会执行module[0].call(这里的call为了确保每个module中的this指向的是module本身),然后执行document.write('<h1>Hello World</h1>');,最后执行document.write('<h2>Hello Webpack</h2>');。至此,__webpack_require__(1)执行完毕,这是一个递归的过程。

    三、两个入口,两个出口文件

    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("<h1>我是main1</h1>")
    
    • main2.js
    var inner1 = require('./inner1.js');
    var inner2 = require('./inner2.js');
    inner1.inner1();
    inner2.inner2();
    document.write("<h1>我是main2</h1>");
    
    • 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('<h1>我是inner1</h1>');
    }
    /***/ }),
    /* 1 */,
    /* 2 */
    /***/ (function(module, exports, __webpack_require__) {
    var inner1 = __webpack_require__(0);
    inner1.inner1();
    document.write("<h1>我是main1</h1>")
    /***/ })
    /******/ ]);
    
    • 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('<h1>我是inner1</h1>');
    }
    /***/ }),
    /* 1 */
    /***/ (function(module, exports) {
    exports.inner2 = function() {
        document.write('<h1>我是inner2</h1>');
    }
    /***/ }),
    /* 2 */,
    /* 3 */
    /***/ (function(module, exports, __webpack_require__) {
    var inner1 = __webpack_require__(0);
    var inner2 = __webpack_require__(1);
    inner1.inner1();
    inner2.inner2();
    document.write("<h1>我是main2</h1>");
    /***/ })
    /******/ ]);
    
    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分析得到所有必须模块并合并;提供让这些模块有序执行的环境。

    代码

    相关文章

      网友评论

        本文标题:webpack之bundle文件分析

        本文链接:https://www.haomeiwen.com/subject/mbgezttx.html