分析webpack编译结果, 实现__webpack_require__函数

分析webpack编译结果, 实现__webpack_require__函数

本篇文章我们通过手写来分析一下Webpack打包后的代码, 并研究一下Webpack是如何将多个模块合并在一起的

首先控制台输入npm init -y初始化一个项目, 再输入npm i webpack webpack-cli -D安装Webpack

在src目录想创建入口文件index.js, index.js导入了一个文件a.js并打印

我使用的Webpack版本如下:

  • webpack: 5.89.0
  • webpack-cli: 5.1.4
// src/index.js

console.log('index module')
var a = require("./a")
console.log(a)

src目录下创建一个a.js文件, a.js文件中导出一个对象

// src/a.js

console.log("a module")
module.exports = {
  a: "a",
  b: "b"
}

首先我们定义一个对象__webpack_modules__, 这个对象中保存着所有模块, 其中key是模块id, 一般是文件的路径作为key, value则是一个函数, 对应着模块的代码; 模块中运用到的module、exports、require作为函数的参数传入, require为了和node中的require做区别, 因此命名为__webpack_require__

// 该对象中保存着所有模块 以及模块对应代码
var __webpack_modules__ = {
  "./src/a.js": function (module, exports) {
    console.log("a module")
    module.exports = {
      a: "a",
      b: "b"
    }
  },
  "./src/index.js": function (module, exports, __webpack_require__) {
    console.log("index module")
    var a = __webpack_require__("./src/a.js")
    console.log(a)
}

为了避免上面代码造成全局变量污染, 我们将它作为立即执行函数的参数传入

(function (__webpack_modules__) {})({
  // 该对象中保存着所有模块 以及模块对应代码
  "./src/a.js": function (module, exports) {
    console.log("a module")
    module.exports = {
      a: "a",
      b: "b"
    }
  },
  "./src/index.js": function (module, exports, __webpack_require__) {
    console.log("index module")
    var a = __webpack_require__("./src/a.js")
    console.log(a)
  }
})

接下来实现__webpack_require__函数, 该函数接收一个moduleId(__webpack_modules__中的key)作为参数, 并运行一个模块, 得到该模块的导出结果

实现思路, 定义一个module对象, 用于存放导出结果, module对象中有一个exports属性, 该属性也对应一个对象; 通过moduleId我们可以得到该模块的函数, 执行这个函数就可以得到这个模块的导出结果; 在立即执行函数中调用__webpack_require__执行入口文件

(function (__webpack_modules__) {
  function __webpack_require__(moduleId) {
    var module = {
      exports: {}
    }
    var func = __webpack_modules__[moduleId] // 得到对应模块
    func(module, module.exports, __webpack_require__) // 执行对应的模块代码
    return module.exports // 返回导出结果
  }

  // 执行入口文件
  __webpack_require__("./src/index.js")
})({
  // 该对象中保存着所有模块 以及模块对应代码
  "./src/a.js": function (module, exports) {
    console.log("a module")
    module.exports = {
      a: "a",
      b: "b"
    }
  },
  "./src/index.js": function (module, exports, __webpack_require__) {
    console.log("index module")
    var a = __webpack_require__("./src/a.js")
    console.log(a)
  }
})

这样我们就是实现了Webpack合并多个模块, 但是当我们读取某一模块的时候, 我们应将它的导出结果缓存起来, 当下一次再导入该模块时, 就不需要再执行这个模块的代码, 直接从缓存中读取导出结果即可, 所以我们需要实现缓存完善上面代码

实现思路: 立即执行函数中定义一个__webpack_module_cache__对象用于缓存, 在__webpack_require__函数中, 优先从缓存中读取, 若缓存中有值, 那么直接返回缓存中的结果, 若没有值在执行对应模块的代码, 并将导出结果缓存, 再对之前的代码进行简单优化, 示例代码如下:

(function (__webpack_modules__) {
  var __webpack_module_cache__ = {} // 用于缓存

  function __webpack_require__(moduleId) {
    // 从缓存中读取
    var cacheModule = __webpack_module_cache__[moduleId]
    // 缓存中有值 直接返回缓存的结果
    if (cacheModule !== undefined) return cacheModule

    // 定义module存放导出结果 并赋值给缓存对象
    var module = __webpack_module_cache__[moduleId] = {
      exports: {}
    }
    // 执行对应的模块代码
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__)
    // 返回导出结果
    return module.exports
  }

  // 执行入口函数
  __webpack_require__("./src/index.js")
  __webpack_require__("./src/index.js")
  __webpack_require__("./src/index.js")
  __webpack_require__("./src/index.js")
})({
  // 该对象中保存着所有模块 以及模块对应代码
  "./src/a.js": function (module, exports) {
    console.log("a module")
    module.exports = {
      a: "a",
      b: "b"
    }
  },
  "./src/index.js": function (module, exports, __webpack_require__) {
    console.log("index module")
    var a = __webpack_require__("./src/a.js")
    console.log(a)
  }
})

此时我们控制输入npx webpack --mode=development使用开发环境对代码进行打包, 将多余的注释删除后, 得到的代码如下:

(() => {
  var __webpack_modules__ = ({
    "./src/a.js":
      ((module) => {
        eval("console.log(\"a module\")\n\nmodule.exports = {\n  a: \"a\",\n  b: \"b\"\n}\n\n\n//# sourceURL=webpack://web/./src/a.js?");
      }),

    "./src/index.js":
      ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
        eval("console.log('index module')\nconst a = __webpack_require__(/*! ./a */ \"./src/a.js\")\nconsole.log(a)\n\n//# sourceURL=webpack://web/./src/index.js?");
      })
  });
  var __webpack_module_cache__ = {};


  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = __webpack_module_cache__[moduleId] = {
      exports: {}
    };

    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    return module.exports;
  }

  var __webpack_exports__ = __webpack_require__("./src/index.js");
})();

可以看到和我们所实现的有一些小区别, 其中Webpack的__webpack_modules__是定义在立即执行函数中的, 我们是作为立即执行函数的参数传入, 其实没有什么区别;

另外__webpack_modules__中的模块函数是使用eval执行的, 这是并且会加上一个注释, 其实这是为了开发者能够在浏览器控制台调试, 由于我们运行的是打包后的代码, 那么报错指向的错误地址就会指向打包后的代码, 是不利于我们调式的; 而使用eval配合//# sourceURL=webpack://web/./src/a.js?可以看做是一个简易版的source map, 仅仅是一种方便调试的手段而已

其他的就没有什么区别了, 生产环境无非就是对这些变量名进行了压缩丑化, 也是可以看懂的, 到这里我们已经分析了Webpack的编译结果, 并实现了Webpack合并模块的核心代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学全栈的灌汤包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值