webpack打包结果源码分析

286 阅读4分钟

模块处理

  • 默认支持 commonJs 规范

  • 处理 esModule 模块 导出

    • _webpack_require_.r 标记模块为 esModule

    • _webpack_require_.d 通过将 export 导出的对象添加到 module.exports 对象上,并提供getter属性

      // 源码 esModule
      export default "name"
      export const age = 18;
      // 编译成 commonJs
      __webpack_require__.r(__webpack_exports__);//标记为esModule
      __webpack_exports__['default'] = ("name")
      __webpack_require__.d(__webpack_exports__, "age", function(){ return age; })
      const age = 18;
      
      
  • 处理 esModule 模块 导入

    • _webpack_require_.r 标记模块为 esModule
    • _webpack_require_ 处理模块导入,获取到 module.exports 的值
    • _webpack_require_.n 获取模块的 default 导出(兼容 esModule/commonJs 两种方法);并通过 .a 获取
    // esModule
    import name,{age} from './login.js'
    console.log(name,age);
    // 编译成 commonJs
    __webpack_require__.r(__webpack_exports__);//标记为esModule
    var _login_js_WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__('./src/login.js');
    var _login_js_WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_login_js_WEBPACK_IMPORTED_MODULE_0__);
    console.log(_login_js_WEBPACK_IMPORTED_MODULE_0___default.a, _login_js_WEBPACK_IMPORTED_MODULE_0__['age']);
    

打包结果

// 打包后的文件是一个 IIFE,函数传入一个参数modules
(function(modules){
  // 1. 缓存已经被加载过的模块
  var installedModules = {};
  
  // 2. 返回模块的exports,用于模块的加载 (require、import的实现)
  function __webpack_require__(moduleId) {
    if(installedModules[moduleId]) {
				return installedModules[moduleId].exports;
		}
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,// 是否被加载
      exports: {}
 		};
    // ⚠️执行模块定义中的函数,传入参数
    // 可见 webpack 默认支持 commonJS 规范
    // 在模块内部的导出代码,实现对 module.exports 的赋值
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    module.l = true; // 标记该模块已被加载
    return module.exports;// 最终返回模块导出内容
  }
  __webpack_require__.m = modules;
  __webpack_require__.c = installedModules;
  __webpack_require__.p = "/"; // publicPath配置将写入这里
  __webpack_require__.r =function(){ // 标记模块为 esModule 属性
    Object.defineProperty(exports, '__esModule', { value: true });
  } 
  __webpack_require__.o = // 判断是否包含属性 Object.prototype.hasOwnProperty.call
  __webpack_require__.d = function(exports, name, getter){ //  给模块导出对象添加属性,并提供getter
    if(!__webpack_require__.o(exports, name)) {
 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
 		}
  }
  // 加载指定moduleId,并将该module的导出内容进一步处理后返回
  // 用来兼容处理 commonJS 和 esModule两种加载方式
  __webpack_require__.t = function(value/*moduleId*/,mode/*数值*/){ 
    if(mode & 1) value = __webpack_require__(value);
    if(mode & 8) return value; // commonJS规范,无需处理直接返回
    if(mode & 4) && typeof value ==="object" && value && value.__esModule) return value;
    // 将模块 包装成 esModule 
    var ns = Object.create(null);
    __webpack_require__.r(ns);// 标记为 esModule,并将模块导出内容赋值到 default 属性
    Object.defineProperty(ns, "default", { enumerable: true, value:value });
    // 如果 value 是一个对象,则进行解构赋值(遵从 esModule 的 export default )
    if(mode & 2 && typeof value!=="string") for(var key in value) __webpack_require__.d(ns,key,function(key){ return value[key]}.bind(null,key) /*修改函数this*/);
    return ns;
  }
  __webpack_require__.n = function(module_exports){ // 获取模块的默认导出,兼容 commonJs 和 esModule
    var getter = module_exports && module_exports.__esModule ?
        function getDefault(){ return  module_exports['default']} :
    		function getModuleExports(){ return module_exports };
    __webpack_require__.d(getter, 'a', getter);// 通过.a来访问导出内容
    return getter;
  }
  
  // 3. 加载入口文件
  return __webpack_require__(__webpack_require__.s = "./src/index.js");
  
})({ // 模块定义:键值对
    "./src/index.js":
  (function(module, exports, /* 可见exports为 module.exports的引用*/__webpack_require__){
      // 源码中的 require 被替换成了 __webpack_require__
      let name  = __webpack_require__("./src/login.js")
      console.log(name);
    }),
  	"./src/login.js":
  (function(module,exports){
        // 加载模块的内容包裹在函数中,利用该函数的参数实现模块加载操作
      module.exports = "login模块"
    })
  })

懒加载原理

  • _webpack_require_.e :动态生成script标签异步加载chunk JS文件,返回 Promise 对象
  • 加载chunkJS文件会触发 window["webpackJsonp"].push 执行,即触发 webpackJsonpCallback回调,合并异步模块定义,同时改变Promise 对象为resolve状态
  • 在 _webpack_require_.e 的 then回调中 通过__webpack_require__.t获取模块导出内容
/*****main.js*****/
// ===> 编译前
import('./login.js').then(name=>console.log(name))
// ===> 编译后
__webpack_require__.e("login")
  .then(/*⚠️传入resolve函数*/__webpack_require__.t.bind(null,"./src/login.js",7))
  .then(name=>console.log(name));

/*****login.js*****/
// ===> 编译前
module.exports = "login";
// ===> 编译后
// window["webpackJsonp"].push([chunkID,模块定义])
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["login"],{
  "./src/login.js":
  (function(module,exports){
    module.exports = "login"
  })
}])

懒加载打包结果

/** 初始化部分 **/
(function(modules){
	// 1. 定义 chunk异步加载的 jsonp函数,在异步模块文件被加载时调用,通过 window["webpackJsonp"].push 触发
  // 该函数的作用是,改变异步模块加载__webpack_require__.e函数的promise状态,完成异步模块定义合并
  // 最终在 __webpack_require__.e 的 then 回调可以成功 通过 __webpack_require__.t 加载到该异步模块内容
  function webpackJsonpCallback(data){
    var chunkIds = data[0];
    var moreModules = data[1];
    var moduleId, chunkId,i=0,resolves = [];

    for(;i<chunkIds.length;i++){
      chunkId = chunkIds[i];
      // 该chunkId已经被加载
      // installedChunks 定义????
      if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]){
        resolves.push(installedChunks[chunkId][0]);
      }
      installedChunks[chunkId] = 0;// 赋值为0,标记该chunkId已被加载
    }

     // ⚠️合并懒加载的模块定义
    for(moduleId in moreModules){
      modules[moduleId] = moreModules[moduleId];
    }
    // webpackJsonpCallback 重写了 window["webpackJsonp"]数组的push方法
    // 以上代码均为 在数组push的操作上 代理的逻辑
    if(parentJsonpFunction) parentJsonpFunction(data); 
    
    while(resolves.length){
      resolves.shift()();// 执行resolve函数,代表该 chunkId 的懒加载模块已经加载完毕,可执行后续的导入操作
    }
  }
  
  // 2. 定义 jsonp 函数
  var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"]||[];
  var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
  jsonpArray.push = webpackJsonpCallback; 
  // 切断 jsonpArray 和 window["webpackJsonp"] 的联系
  jsonpArray = jsonpArray.slice(); 
  for(var i = 0;i<jsonpArray.length; i++;) webpackJsonpCallback(jsonArray[i]);//执行push操作
  var parentJsonpFunction = oldJsonpFunction;// 在 webpackJsonpCallback 该函数
  
  var installedChunks = {
    main:0 //默认 main入口模块已被加载
  }
  // 3. 定义 __webpack_require__.e
  __webpack_require__.e = function (chunkId){
    var promises = [];
    var installedChunkData = installedChunks[chunkId];
    if(installedChunkData !==0){// 0 means "已经加载过了"
      if(installedChunkData){
        promises.push(installedChunkData[2]);
      }else{
        var promise = new Promise(function(resolve,reject){
          installedChunkData = installedChunks[chunkId] = [resolve, reject];
        })
        promises.push(installedChunkData[2] = promise);
        //则 installedChunks[chunkId] = [resolve, reject, promise]
        
        // 创建 script 标签加载异步模块,执行异步模块代码
        // 通过执行 window["webpackJsonp"].push 触发 webpackJsonpCallback 执行
        // 从而执行 resolve 触发promise的成功回调
        var script = document.createElement('script');
        script.charset='utf-8';
        script.timeout = 120;
        script.src = jsonpScriptSrc(chunkId);
        document.head.appendChild(script);
      }
    }
    return Promise.all(promises);
  }
  
  function jsonpScriptSrc(chunkId){
    return __webpack_require__.p + "" + chunkId + ".build.js"; 
  }
}(/**模块定义**/))