美文网首页
webpack同步导入与加载分析

webpack同步导入与加载分析

作者: zdxhxh | 来源:发表于2019-11-09 13:04 被阅读0次

一、导语

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

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

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

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

  • import a from 'a.js'
  • 先给当前模块打上ES6模块标识符
  • 加载模块
  • webpack根据关键词对导出内容进行兼容性解析
  • 根据模块的导出形式(export default /exprots )给缓存模块的exports属性赋予值

二、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

  • 如果在模块缓存中找到相应的模块id,则直接返回模块缓存对应的对象
  • 缓存中没有该对象,则注册一个模块缓存对象 newModule
  • 执行模块函数,入参为 newModule.exprots newModule, newModule.exports,webpack_require(函数自身)
  • 模块函数执行完毕,将newModule状态设为已经加载状态
  • 返回模块内容

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

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

  • 声明严格模式
  • 为模块newModule的exports打上标签
  • 为exprots的default赋值为{ name : '看到了点心做的屋子'}

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

  • 声明严格模式
  • 为模块newModule的exports打上标签
  • 使用_webpack_require调用a模块,并将其返回值赋值给a
  • 执行index.js的内容cosnole.log(a,'草泥马')

六、总结

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

七、疑问

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

  • 对于异步加载import('') 是如何实现的 ?
  • webpack_require.t 、webpack_require.d 这些函数是用来干什么的 ?
  • 如果分chunk,webpack会对其怎么处理呢 ?

相关文章

  • webpack同步导入与加载分析

    一、导语 在commonJS中,导出模块的方式是改变module.exports,但是对于es6来讲并不存在mod...

  • vue-cli 优化

    1.分析影响加载速度的原因 查看资源加载时间,分析是哪些资源加载缓慢 2.利用webpack-bundle-ana...

  • 模块规范(CommonJS、AMD、CMD)

    CommonJS规范 应用于服务端,模块同步加载 — nodejs/webpack; 引用:require(mod...

  • webpack-5大特点

    webpack5大特点 代码拆分(支持异步模块加载) Webpack 有两种组织模块依赖的方式,同步(默认)和异步...

  • webpack的优势

    webpack5大特点 代码拆分(支持异步模块加载) Webpack 有两种组织模块依赖的方式,同步(默认)和异步...

  • Soul网关同步数据逻辑初探

    Http同步数据 按照前面两个同步数据的分析,可以看到Http同步跟其他的同步的加载基本一样。不同的地方主要是加载...

  • webpack 1的配置使用

    webpack是什么?webpack是一个前端资源加载/打包工具,有两种依赖声明方式:同步和异步。将依赖分割成多个...

  • 初识webpack

    webpack初识 webpack是什么? 前端资源加载/打包工具。根据模块的依赖关系进行静态分析,将模块按照指定...

  • 用webpack实现模块懒加载、预取/预加载

    模块懒加载本身与webpack没有关系,webpack可以让懒加载的模块代码打包到单独的文件中,实现真正的按需加载...

  • 代码分离和懒加载

    代码分离入口起点动态导入及懒加载路由的一般写法实现异步懒加载参考 代码分离 根据webpack官网介绍,常用的代码...

网友评论

      本文标题:webpack同步导入与加载分析

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