webpack 打包后的代码结构

测试源码:入口文件为 main.js, 在其中引入了 bar.js, call.js

// bar.js
export default function bar() {
    console.log('111111')
  }
// call.js
import bar from './bar'
const a = bar('aa')
export default a
// main.js
import bar from './bar';
import call from './call'
bar();
call()


打包后的bundle.js,大致结构如下:
(() => {
    // 模块的定义
    var __webpack_modules__ = ({
        "./src/bar.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {....},
        "./src/call.js": () => {....},
        "./src/index.js": () => {....}
    })

    // 缓存模块
    var __webpack_module_cache__ = {};

    // 加载模块的方法
    function __webpack_require__(moduleId) {....}

    /* webpack/runtime/define property getters */
    __webpack_require__.d = function() {....}

    /* webpack/runtime/hasOwnProperty shorthand */
    __webpack_require__.o = function() {....}

    /* webpack/runtime/make namespace object */
    __webpack_require__.r = function() {....}

    // Load entry module
    __webpack_require__("./src/index.js");
})()

webpack_modules 为模块集,模块的逻辑被包裹在一个方法里面,方法的参数为
(__unused_webpack_module, webpack_exports, webpack_require)

方法里面使用:webpack_exports.XX = {....} 为模块的导出逻辑,用来收集 module 中所有的 export xxx

模块加载器:webpack_require,加载并执行对应模块,最后返回 webpack_exports

var __webpack_module_cache__ = {};

function __webpack_require__(moduleId) {
        // Check if module is in cache
    if(__webpack_module_cache__[moduleId]) {
        return __webpack_module_cache__[moduleId].exports;
    }
    // Create a new module (and put it into the cache)
    var module = __webpack_module_cache__[moduleId] = {
        // no module.id needed
        // no module.loaded needed
        exports: {}
    };
    // Execute the module function
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  
    // Return the exports of the module
    return module.exports;
}


模块包裹器(模块的内容 + 一些补丁逻辑) :
  "./src/bar.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    //  将模块定义成 esModule, 这样es6的模块导入方式可用
    __webpack_require__.r(__webpack_exports__);
    // 类似于 object.assign(__webpack_exports__, {.......})
    __webpack_require__.d(__webpack_exports__, {
        "default": () => bar
    });
    
    function bar() {
      console.log('111111')
    }

})

__unused_webpack_module:模块的module对象,通过参数传入
_webpack_exports_ : module对象的exports熟悉,expoerts里面是模块的具体导出信息
类似如下:

module.exports = {
  default: XXX,
  a: {...},
  b: function() {....}
}

import bar from './bar' 编译为:

/* harmony import */ var _bar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bar */ "./src/bar.js");
(0,_bar__WEBPACK_IMPORTED_MODULE_0__.default)();

注:这里采用(0, function)() 是强制将function的执行环境改为window对象, 不用apply/call ,是因为 这些方法的原型可能会被其他逻辑修改

(0, dog.run)();

// 左边先用逗号操作符从左到右执行并取值,最终返回
dog.run: function run() {
    console.log(this);
    console.log('I\'m running');
}
// 等价于如下,此时的 this 就是 全局对象 了
(function run() {
    console.log(this);
    console.log('I\'m running');
})();


_webpack_require_.d 给exports定义属性,值为模块的导出值,有点类似

Object.assign(exports, {.....})

    /* webpack/runtime/define property getters */
(() => {
    // define getter functions for harmony exports
    __webpack_require__.d = (exports, definition) => {
        for(var key in definition) {
            if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
                Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
            }
        }
    };
})();

_webpack_require_.o:判断对象中是否有指定属性

(() => {
    __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
})();

_webpack_require_.r: 将模块定义为esModule,方便外层模块以es6方式引入

/* webpack/runtime/make namespace object */
(() => {
    // define __esModule on exports
    __webpack_require__.r = (exports) => {
        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
        }
        Object.defineProperty(exports, '__esModule', { value: true });
    };
})();


代码分隔与按需加载

import('./bar').then(ex => {
    console.log(ex)
})

会被编译成如下代码

__webpack_require__.e(/*! import() */ "src_bar_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bar */ "./src/bar.js")).then(ex => {
    console.log(ex)
})

./bar 文件会被编译成:

(self["webpackChunkwebpack_study"] = self["webpackChunkwebpack_study"] || []).push([["src_bar_js"],{

"./src/bar.js":((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

                  "use strict";
                  __webpack_require__.r(__webpack_exports__);
                // 给exports定义导出属性和导出对象
                  __webpack_require__.d(__webpack_exports__, {
                       "default": () => bar
                  });
                  function bar() {
                    console.log('111111')
                  }

              })

}]);

self["webpackChunkwebpack_study"].push 是被main.js重写了的,执行上面代码会执行注册模块的相关操作


webpack_require.e 是一个小技巧,加载脚本同时可以做其他事情,比如解决脚本相关的若干问题(模块联邦) 这取决于webpack_require.f 里面的属性,等里面所有属性相关方法处理完成后,才会返回resolve

(() => {
    __webpack_require__.f = {};
    // This file contains only the entry chunk.
    // The chunk loading function for additional chunks
    __webpack_require__.e = (chunkId) => {
        return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
        __webpack_require__.f[key](chunkId, promises);
        return promises;
        }, []));
    };
})();

这里 webpack_require.f 只有一个属性(模块联邦下还有其他属性),webpack_require.f .j 的作用是生成promise以及脚本异常的处理方法并传入模块脚本路径调用公用的加载脚本的方法

// 传入模块id,以及对应的promise列表
__webpack_require__.f.j = (chunkId, promises) => {
    var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
    // 模块是否已经触发加载了,若是则将该模块对应的promise存到数组中
    if(installedChunkData !== 0) { 
        // 0 means "already installed". 
        // a Promise means "currently loading".
        if(installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
        if(true) { 
            // 构造模块的promise并存到数组中
            var promise = new Promise((resolve, reject) => {
            installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise);
            // 设置加载的地址url,这里__webpack_require__.p是路径的全局前缀,一般设置成和当前执行脚本的src一致
            var url = __webpack_require__.p + __webpack_require__.u(chunkId);
            // create error before stack unwound to get useful stacktrace later
            var error = new Error();
            // 脚本加载异常的处理方法
            var loadingEnded = (event) => {....};
            // __webpack_require__.l 是加载脚本的公用方法
            __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId);
        } else 
            installedChunks[chunkId] = 0;
        }
    }
};

webpack_require.l 是公用的脚本加载方法

// loadScript function to load a script via script tag
__webpack_require__.l = (url, done, key) => {
    // 如果脚本已经正在加载中,则直接将回调方法推入回调栈内
    if(inProgress[url]) { inProgress[url].push(done); return; }
    var script, needAttach;
    if(key !== undefined) {
        // 找到某个script,属性data-webpack对应key的
        // 代表此脚本已经加载完成并放入dom了
        var scripts = document.getElementsByTagName("script");
        for(var i = 0; i < scripts.length; i++) {
            var s = scripts[i];
            if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
        }
    }
    // 脚本未加载,则构造script tag 去加载脚本
    if(!script) {
        needAttach = true;
        script = document.createElement('script');
        script.charset = 'utf-8';
        script.timeout = 120;
        if (__webpack_require__.nc) {
            script.setAttribute("nonce", __webpack_require__.nc);
        }
        script.setAttribute("data-webpack", dataWebpackPrefix + key);
        script.src = url;
    }
    // 构造属于该脚本的回调队列
    inProgress[url] = [done];
    // 脚本加载完成后的执行回调,里面会处理onerror,onload, clearTimout, inProgress[url]
    var onScriptComplete = (prev, event) => {
        // avoid mem leaks in IE.
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var doneFns = inProgress[url];
        delete inProgress[url];
        script.parentNode && script.parentNode.removeChild(script);
        doneFns && doneFns.forEach((fn) => fn(event));
        if(prev) return prev(event);
    };
    // 超时处理
    var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
    script.onerror = onScriptComplete.bind(null, script.onerror);
    script.onload = onScriptComplete.bind(null, script.onload);
    needAttach && document.head.appendChild(script);
};

参考资料:
https://blog.csdn.net/yingyangxing/article/details/109653116

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容