likes
comments
collection
share

手撕webpack运行时项目中打包的产物那么多,你有没有看过打包出的runtime是怎么一回事? 看看webpack是怎

作者站长头像
站长
· 阅读数 22

项目中打包的产物那么多,你有没有看过打包出的runtime是怎么一回事?

看看webpack是怎么用朴实无华的纯JS,实现浏览器端模块化的。

runtime的基础框架原理并不复杂,搞懂runtime运行时,你就知道为什么CommonJS是值的拷贝,ES Module是值的引用了。

一. 项目准备

实际上就是解释了CommonJS和ES Module的实现原理

index.js webpack入口文件

let my= require("./my")
/**
 你可以打开下面ES Module的引用方式,并注释掉上面一行,看看webpack打包结果有什么不同。
*/
// import { name as my } from './jd'

console.log('my webpack runtime' + my)

my.js

let name = 'fangxiangming'

module.exports = name

/**
 你可以打开下面ES Module的引用方式,并注释掉上面一行,看看webpack打包结果有什么不同。
*/
// export {
//   name
// }

二. 打包产物分析

打开打包后的bundle,并梳理其中核心的代码结构。可以清晰的看到 webpackBootStrap 的实现结果。webpack之所以能实现模块化的秘密,都在这里。

下面代码中有我增加的注释,它们阐述了我的理解:

 (function(modules) { 
   // 要在全局维护一个 模块地址-module.exports map
  var installedModules = {};

   // 他的职责就是 根据路径加载模块,并为模块传入它所对应的 module.exports 和 自身回调
  function __webpack_require__(moduleId) {

     // 如果模块已经执行过,就直接返回module.exports
    if(installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }    
    
     // 注册当前模块的module
    var module = installedModules[moduleId] = {
      exports: {}
    };

     // 执行模块,this是当前模块
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

     // 返回模块对应的 module.exports
    return module.exports;
  }

   // Load entry module and return exports
  return __webpack_require__(__webpack_require__.s = "./src/index.js");
 })
/************************************************************************/
 ({  // 这是一个 模块路径-模块包装一层function 的 map

 "./src/index.js":
 (function(module, exports, __webpack_require__) {

  eval("let my= __webpack_require__(/*! ./my */ "./src/my.js")\n\nconsole.log('my webpack runtime' + my)\n\n//# sourceURL=webpack:///./src/index.js?");

 }),

 "./src/my.js":

 (function(module, exports) {

  eval("let name = 'fangxiangming'\n\nmodule.exports = name\n\n//# sourceURL=webpack:///./src/jd.js?");

  })

 });

上面的打包产物只包含使用了CommonJS规范。之所以CommonJS的模块特性是访问值的拷贝,是因为在读取模块值时,最终访问的是储存在installedModules中module对象的exports属性,所以如同解构对象,得到的是值的拷贝。

如果使用 ES Module 会有些不同,它会在 export 时通过 __webpack_require__.d 方法将模块的 module 设置 getter 特性(本质上利用了Object.defineProperty)。因为访问模块导出的变量时,永远会执行module属性的 getter 函数。getter 是一个函数,通过getter访问到的module的属性自然是实时最新值。这一点就如同使用 commonJS 时,为了访问实时最新值,所以会导出函数,而不是直接导出变量。

下面是只使用 ES Module 的打包产物,省略了和commonJS的相同点:

 (function(modules) { // webpackBootstrap
 
  var installedModules = {};

  function __webpack_require__(moduleId) {
    // ...
  }

   // 专门为 module.exports 暴露的接口设置 getter 的函数
  __webpack_require__.d = function(exports, name, getter) {
    if(!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
  };

  return __webpack_require__(__webpack_require__.s = "./src/index.js");
 })

 ({

  "./src/index.js":
  (function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    eval(`
      __webpack_require__.r(__webpack_exports__); // 可以忽略!这行实际上只是给模板加标志位
      var _my__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/my.js");
      console.log('my webpack runtime' + _my__WEBPACK_IMPORTED_MODULE_0__["name"])
    `);
  
  }),
  
   "./src/my.js":
  (function(module, __webpack_exports__, __webpack_require__) {
  
    "use strict";
    eval(`
      // 用 __webpack_require__.d 设置 module.exports 访问 name 属性时的 getter 
      __webpack_require__.d(__webpack_exports__, "name", function() { return name; });
      let name = 'fangxiangming'
    `);
  
  })
  
});

三. 抽象webpack运行时流程图

手撕webpack运行时项目中打包的产物那么多,你有没有看过打包出的runtime是怎么一回事? 看看webpack是怎

转载自:https://juejin.cn/post/7369509651518111779
评论
请登录