webpack打包ES6模块的bundle.js源码分析

554 阅读3分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

前言

webpack是前端模块化打包工具,我们在使用vue2或者react的官方脚手架生成的项目里面,里面都有webpack的影子。

但是这些脚手架都是提供打包命令,我们只需运行这些打包命令就可以打包,一般不需要关心打包后的内容是怎样的。

今天想借着这次机会,一起来看看wepback打包后的源码是怎样的,分析一下。

打包

  1. 安装webapck

    yarn add webpack webpack-cli -D // 这里你也可以使用npm
    

    我安装的版本是"webpack": "^5.42.1", "webpack-cli": "^4.7.2"

  2. 编写webpack.config.js,webpack的配置文件

    webpack.config.js

      const path = require('path')
      module.exports = {
        entry: ['./export/index.mjs'], // 入口
        output: {
          path: path.resolve(__dirname, 'dist'), // 输出目录
          filename: 'bundle.js'  //输出文件名称
        }
      }
    

    index.mjs, 这里只引入一个简单模块,算是最简单的代码

     import { name } from './test.mjs'
     console.log(name)
    

    test.mjs

       var name = '答案cp3'
       export { name }
    
  3. 然后在终端运行命令webpack --mode development(基于wepback全局安装),如果不是全局的在package.json添加scripts字段,把该命令写入,通过yarn或者npm来执行,这里要写development(开发模式),这样打包出来的代码不是压缩后的,方便阅读。

    打包成功,我们来看看打包后的代码(去掉部分注释代码)

     (() => {
       "use strict";
       var __webpack_modules__ = ({
    
         "./export/index.mjs": ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
           eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _test_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test.mjs */ \"./export/test.mjs\");\n\r\nconsole.log(_test_mjs__WEBPACK_IMPORTED_MODULE_0__.name)\r\n\n\n//# sourceURL=webpack://test/./export/index.mjs?");
         }),
    
         "./export/test.mjs": ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
    
           eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"name\": () => (/* binding */ name)\n/* harmony export */ });\nvar name = '答案cp3'\r\n\r\n\n\n//# sourceURL=webpack://test/./export/test.mjs?");
         })
    
       });
       // The module cache
       var __webpack_module_cache__ = {};
    
       // The require function
       function __webpack_require__ (moduleId) {
         // Check if module is in cache
         var cachedModule = __webpack_module_cache__[moduleId];
         if (cachedModule !== undefined) {
           return cachedModule.exports;
         }
         // Create a new module (and put it into the cache)
         var module = __webpack_module_cache__[moduleId] = {
           // no module.id needed
           // no module.loaded needed
           exports: {}
         };
    
         // Execute the module function
         __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    
         // Return the exports of the module
         return module.exports;
       }
    
       /************************************************************************/
       /* webpack/runtime/define property getters */
       (() => {
         // define getter functions for harmony exports
         __webpack_require__.d = (exports, definition) => {
           for (var key in definition) {
             if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
               Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
             }
           }
         };
       })();
    
       /* webpack/runtime/hasOwnProperty shorthand */
       (() => {
         __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
       })();
    
       /* webpack/runtime/make namespace object */
       (() => {
         // define __esModule on exports
         __webpack_require__.r = (exports) => {
           if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
           }
           Object.defineProperty(exports, '__esModule', { value: true });
         };
       })();
    
       /************************************************************************/
    
       // startup
       // Load entry module and return exports
       // This entry module can't be inlined because the eval devtool is used.
       var __webpack_exports__ = __webpack_require__("./export/index.mjs");
    
     })();
    

    这些打包生成的代码可以直接在浏览器的控制台上面运行,不过需要在https协议下运行,因为浏览器对eval函数的限制。

分析过程

下面来解析一下这段打包后的代码:

  1. 所有的代码都是包裹在立即执行的匿名函数里面, 采用严格模式(es6模块打包默认采用严格模式)

  2. 定义__webpack_modules__对象,里面key是各个模块的路径,value是由一个箭头函数, 里面包裹着的各个模块的代码,在webpack的开发模式下,模块代码使用eval函数执行。

  3. 定义__webpack_module_cache__对象,它是用来缓存各个模块执行后的代码,key是各个模块的路径。

  4. 定义__webpack_require__函数,这个是相当于模块执行函数。参数是传入模块的路径,新建个对象,把它赋值到__webpack_module_cache__下,key是传入的模块的路径,然后去第二步声明的__webpack_modules__对象找到对应的模块代码,然后执行,执行的过程把新的的对象传入,把导出的值都定义到该对象上,只读,不可写。新对象的值也会同步到__webpack_module_cache__上。

  5. 如果重复调用__webpack_require__,可以根据传入的路径通过__webpack_module_cache__缓存对象找到对应的缓存值,然后返回。

  6. __webpack_require__函数上定义几个方法

    • d 方法

      内部使用defineProperty方法,定义导出模块上的属性

    • o 方法

      利用hasOwnProperty,判断是否本身的属性

    • r方法

      给导出模块的各个对象都加上__esModule属性

  7. 执行__webpack_require__函数,参数是webpack.config.js的配置的入口模块(entry

总结

以上就是对webpack打包ES6模块的源码分析,这个是比较简单的模块依赖,不过大体思路应该是这样,大家如果有不懂的地方,跟着步骤去打包,然后把打包的代码在控制台上执行,加断点打印结果。

感谢你们的阅读。