webpack同步导入与加载分析

2019-11-09  本文已影响0人  zdxhxh

一、导语

在commonJS中,导出模块的方式是改变module.exports,但是对于es6来讲并不存在module这个变量。

对于ES6说,并不存在module这个变量,他的导出方式是通过关键则export实现

在我们书写脚本js文件时,你会发现无论是commonJS写法还是es6写法,都能实现,这是因为webpack进行兼容处理

根据野鸡博客,它会做以下事情

二、webpack工程配置

在index.js 中

import a from './a.js'
console.log(a, '草泥马')

在a.js中

export default {
  name: '看到了点心做的屋子'
}

设定webpack配置

module exports = { 
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
}

三、bundle.js可读化

(function (modules) {
  var installedModules = {};
  function __webpack_require__ (moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      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__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
  };
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };
  __webpack_require__.t = function (value, mode) {
    if (mode & 1) value = __webpack_require__(value);
    if (mode & 8) return value;
    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    var ns = Object.create(null);
    __webpack_require__.r(ns);
    Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));
    return ns;
  };
  __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;
  };
  __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  __webpack_require__.p = "";
  return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
  ({
    "./src/a.js": (function (module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\r\n  name: '看到了点心做的屋子'\r\n});\n\n//# sourceURL=webpack:///./src/a.js?");
    }),
    "./src/index.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/a.js\");\n\r\n\r\nconsole.log(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"], '草泥马')\n\n//# sourceURL=webpack:///./src/index.js?");
      })
  });

一共60行代码,也不多,由于IIFE可读性比较差,我将其拆分为

function init (modules) {
  var installedModules = {} // set 
  // __webpack__require构造函数
  function __webpack__require (moduId) {
    // 找缓存id
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports // 返回它的exports对象
    }
    // 如果缓存没有 注册模块对象
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false, // 是否已经加载
      exprots: {}
    }
    modules[moduleId].call(module.exprots, module, module.exprots, __webpack_require__)
    module.l = true
    return module.exprots
  }
  __webpack_require__.m = modules;
  __webpack_require__.c = installedModules;
  // define方法 将模块暴露内容、属性名,定义getter与可遍历属性
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
  };
  // require方法 给模块打上标签
  __webpack_require__.r = function (exports) {
    // 每个部署了ES6的类,都有Symbol.toStringTag属性,返回[[class]]除了object后面那一段
    // Symbol.toStringTag = Symbol
    // 这段代码检测是否支持Symbol方法
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      // 将exprots对象定义个 Symbol属性 值为Module
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    // 将exprots对象定义个 __esModule属性 值为true
    Object.defineProperty(exports, '__esModule', { value: true });
  };
  // 创建一个独立的命名空间对象
  // 模式1 :值(value)是module id ,需要迭代器it对象
  // 模式2 : 合并值(value)所有属性进入命名空间
  // 模式4 : 如果value是对象直接返回
  // 模式8|1 : 行为类似require
  __webpack_require__.t = function (value, mode) {
    if (mode & 1) value = __webpack_require__(value); // export default 1
    if (mode & 8) return value;
    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    // 如果到这里 export const a = 1 这种情形
    var ns = Object.create(null);
    // 给模块打上标签
    __webpack_require__.r(ns);
    // 定义 export default = null 
    Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    if (mode & 2 && typeof value != 'string') {
      for (var key in value) {
        __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));
      }
    }
    return ns;
  };
  // getDefaultExport function for compatibility with non-harmony modules
  __webpack_require__.n = function (module) {
    // 判断是否为es6模块
    var getter = module && module.__esModule ?
      // 返回default 
      function getDefault () { return module['default']; } :
      // 返回commonJS的模块
      function getModuleExports () { return module; };
    // 为getter函数 通过a属性 反向代理自身
    __webpack_require__.d(getter, 'a', getter);
    return getter;
  };
  // 判断对象内部是否有某个属性值
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty.call(object, property);
  };
  // 公共路径
  __webpack_require__.p = "";
  // 加载入口函数并返回exports对象 Load entry module and return exports
  return __webpack_require__(__webpack_require__.s = "./src/index.js");
}

const moduleArr = {
  // 模块1 
  "./src/a.js": (function (module, __webpack_exports__, __webpack_require__) {
    "use strict";
    // 打标签
    __webpack_require__.r(__webpack_exports__);
    __webpack_exports__["default"] = { name: '看到了点心做的屋子' };
  }),
  // 模块2 
  "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
    console.log(_a_js__WEBPACK_IMPORTED_MODULE_0__["default"], '草泥马')
  })
}

init(moduleArr)

四、变量解析

可以看到,它是由 init函数与moduleArr组成

moduleArr是由一个字典组成,也就是建值对的形式,其中他的键值为相对于webpack.config.js文件的路径,值我称它为一个模块函数字典

继续观察init函数以及它对应的词法作用域下的变量,真正意义上的变量只有两个

变量 说明
installedModules 闭包模块缓存
__webpack__require (function)接受一个模块id执行相应的模块函数,返回其结果

要注意的是,installedModules存放的模块的数据结构(模块缓存对象) :

var module = { 
   i: moduleId, // 模块id 
   l: false,    // 模块是否已经加载 
   exports: {}  // 模块函数返回结果
}

对于__webpack__require,它拥有很多静态方法与属性

变量 说明
installedModules 模块缓存,闭包
webpack_require.m 模块函数字典的引用
webpack_require.c 闭包模块缓存引用
webpack_require.t (function)创建一个独立的命名空间对象
1.mode 1
8 : value是model 的id值,这个id值即为路径,直接去调用主函数
2.value是对象并且支持es6 直接返回
3. 合并value的所有所有属性进入命名空间
webpack_require.r (function)为模块缓存对象的exports属性打上标签
webpack_require.n (function)处理commonJS或者es6模块差异函数,返回一个getter函数
webpack_require.o (function)判断对象内部是否有某个属性值
webpack_require.p 公共路径(存疑)
webpack_require.s entry路径('./src/index.js')

五、函数行为分析

(1). 来看主菜,__webpack__require

(2). 再来看moduleArr的各个模块函数

对于路径为./src/a.js而言

对于路径为 ./src/index.js而言

六、总结

与AMD同步模式类似,首先为对webpack.config.js入口的js文件解析,对于import导入的js文件,则递归调用__webpack__require方法,询问缓存,如果缓存没有,则执行对应得模块函数

七、疑问

bundle分析到这里了,在这里继续留下更多的疑问.

上一篇 下一篇

猜你喜欢

热点阅读