关于前端:揭秘webpack5模块打包

39次阅读

共计 10828 个字符,预计需要花费 28 分钟才能阅读完成。

在上一节中咱们初步理解了 webpack 能够利用内置动态模块类型 (asset module type) 来解决资源文件,咱们所晓得的本地服务,资源的压缩,代码宰割,在 webpack 构建的工程中有一个比较显著的特色是,模块化,要么 commonjs 要么 esModule, 在开发环境咱们都是基于这两种,那么通过webpack 打包后,如何让其反对浏览器能失常的加载两种不同的模式呢?

接下来咱们一起来探讨下 webpack 中打包后代码的原理

注释开始 …

初始化根底我的项目

新建一个文件夹webpack-05-module

npm init -y 

咱们装置我的项目一些根底反对的插件

npm i webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel
l/core -D

在根目录新建 webpack.config.js,配置相干参数,为了测试 webpack 打包cjsesModule我在 entry 写了两个入口文件,并且设置 mode:developmentdevtool: 'source-map', 设置 source-map 是为了更好的查看源代码

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');

module.exports = {
  entry: {
    cjs: './src/commonjs_index.js',
    esjs: './src/esmodule_index.js'
  },
  devtool: 'source-map',
  output: {filename: 'js/[name].js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[name][ext]'
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        options: {presets: ['@babel/env']
        }
      },
      {test: /\.(png|jpg)$/i,
        type: 'asset/resource'
        // generator: {//   // filename: 'images/[name][ext]',
        //   publicPath: '/assets/images/'
        // }
      }
    ]
  },
  plugins: [new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({template: './public/index.html'})
  ]
};

src 目录下新建 commonjs_index.js, esmodule_index.js 文件

commonjs_index.js

// commonjs_index.js
const {twoSum} = require('./utils/common.js');
import imgSrc from './assets/images/1.png';
console.log('cm_sum=' + twoSum(1, 2));
const domApp = document.getElementById('app');
var img = new Image();
img.src = imgSrc;
domApp.appendChild(img);

引入的common.js

// utils/common.js
function twoSum(a, b) {return a + b;}
module.exports = {twoSum};

esmodule_index.js

// esmodule_index.js
import twoSumMul from './utils/esmodule.js';
console.log('es_sum=' + twoSumMul(2, 2));

引入的esmodule.js

// utils/esmodule.js
function twoSumMul(a, b) {return a * b;}
// esModule
export default twoSumMul;

当咱们运行 npm run build 命令,会在根目录 dist/js 文件夹下打包入口指定的两个文件

webpack 打包 cjs 最终代码

我把对应正文去掉后就是上面这样的

// cjs.js
(() => {
  var __webpack_modules__ = {'./src/utils/common.js': (module) => {function twoSum(a, b) {return a + b;}

      module.exports = {twoSum: twoSum};
    },
    './src/assets/images/1.png': (module, __unused_webpack_exports, __webpack_require__) => {
      'use strict';
      module.exports = __webpack_require__.p + 'images/1.png';
    }
  };

  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__ "moduleId");

    return module.exports;
  }

  (() => {__webpack_require__.g = (function () {if (typeof globalThis === 'object') return globalThis;
      try {return this || new Function('return this')();} catch (e) {if (typeof window === 'object') return window;
      }
    })();})();

  (() => {__webpack_require__.r = (exports) => {if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module'});
      }
      Object.defineProperty(exports, '__esModule', { value: true});
    };
  })();

  (() => {
    var scriptUrl;
    if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + '';
    var document = __webpack_require__.g.document;
    if (!scriptUrl && document) {if (document.currentScript) scriptUrl = document.currentScript.src;
      if (!scriptUrl) {var scripts = document.getElementsByTagName('script');
        if (scripts.length) scriptUrl = scripts[scripts.length - 1].src;
      }
    }
    if (!scriptUrl) throw new Error('Automatic publicPath is not supported in this browser');
    scriptUrl = scriptUrl
      .replace(/#.*$/, '')
      .replace(/\?.*$/, '')
      .replace(/\/[^\/]+$/, '/');
    __webpack_require__.p = scriptUrl + '../';
  })();

  var __webpack_exports__ = {};
  (() => {
    'use strict';
    __webpack_require__.r(__webpack_exports__);
     var _assets_images_1_png__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./assets/images/1.png */ './src/assets/images/1.png');
    var _require = __webpack_require__(/*! ./utils/common.js */ './src/utils/common.js'),
      twoSum = _require.twoSum;

    console.log('cm_sum=' + twoSum(1, 2));
    var domApp = document.getElementById('app');
    var img = new Image();
    img.src = _assets_images_1_png__WEBPACK_IMPORTED_MODULE_0__;
    domApp.appendChild(img);
  })();})();

首次看,感觉 webpack 打包 cjs 的代码太长了,然而删除掉正文后,咱们仔细分析发现,并没有那么简单

首先是该模块采纳 IFEE 模式,一个匿名的自定义自行函数内包裹了几大块区域

1、初始化定义了 webpack 依赖的模块

 var __webpack_modules__ = {'./src/utils/common.js': (module) => {function twoSum(a, b) {return a + b;}
      // 当在执行时,返回这个具体函数体内容
      module.exports = {twoSum: twoSum};
    },
    './src/assets/images/1.png': (module, __unused_webpack_exports, __webpack_require__) => {
      'use strict';
      // 每一个对应的模块对应的内容
      module.exports = __webpack_require__.p + 'images/1.png';
    }
  };

咱们发现 webpack 是用模块引入的门路当成 key, 而后value 就是一个函数,函数体内就是引入的具体代码内容,并且外部传入了一个形参 module, 实际上这个module 就是为 {exports: {}} 定义的对象,把外部函数 twoSum 绑定了在对象上

2、调用模块优先从缓存对象模块取值

 var __webpack_module_cache__ = {};
 // moduleId 就是引入的门路
  function __webpack_require__(moduleId) {// 依据 moduleId 优先从缓存中获取__webpack_modules__中绑定的值 {twoSum: TwoSum}
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {return cachedModule.exports;}
    // 传入__webpack_modules__外部 value 的形参 module
    var module = (__webpack_module_cache__[moduleId] = {exports: {}
    });
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__ "moduleId");
    // 依据 moduleId 顺次返回 {twoSum: twoSum}、__webpack_require__.p + 'images/1.png‘图片门路
    return module.exports;
  }

3、绑定全局对象,引入图片的资源门路,次要是 __webpack_require__.p 图片地址

  (() => {__webpack_require__.g = (function () {if (typeof globalThis === 'object') return globalThis;
      try {return this || new Function('return this')();} catch (e) {if (typeof window === 'object') return window;
      }
    })();})();
  
   (() => {
    var scriptUrl;
    if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + '';
    var document = __webpack_require__.g.document;
    if (!scriptUrl && document) {if (document.currentScript) scriptUrl = document.currentScript.src;
      if (!scriptUrl) {var scripts = document.getElementsByTagName('script');
        if (scripts.length) scriptUrl = scripts[scripts.length - 1].src;
      }
    }
    if (!scriptUrl) throw new Error('Automatic publicPath is not supported in this browser');
    scriptUrl = scriptUrl
      .replace(/#.*$/, '')
      .replace(/\?.*$/, '')
      .replace(/\/[^\/]+$/, '/');
      // 获取图片门路
    __webpack_require__.p = scriptUrl + '../';
  })();

4、将 esModule 转换,用 Object.defineProperty 拦挡 exports(module.exports) 对象增加 __esModule 属性

  (() => {__webpack_require__.r = (exports) => {if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module'});
      }
      Object.defineProperty(exports, '__esModule', { value: true});
    };
  })();

5、__webpack_require__(moduleId)执行获取对应的内容

  var __webpack_exports__ = {};
  (() => {
    'use strict';
    // 在步骤 4 中做对象拦挡,增加__esMoules 属性
    __webpack_require__.r(__webpack_exports__);
    // 依据门路获取对应 module.exports 的内容也就是__webpack_require__中的 module.exports 对象的数据
    var _assets_images_1_png__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./assets/images/1.png */ './src/assets/images/1.png');
    var _require = __webpack_require__(/*! ./utils/common.js */ './src/utils/common.js'),
      twoSum = _require.twoSum;
    console.log('cm_sum=' + twoSum(1, 2));
    var domApp = document.getElementById('app');
    var img = new Image();
    img.src = _assets_images_1_png__WEBPACK_IMPORTED_MODULE_0__;
    domApp.appendChild(img);
  })();})();

webpack 打包 esModule 最终代码

咱们看下具体代码

// esjs.js
(() => {
  // webpackBootstrap
  'use strict';
  var __webpack_modules__ = {'./src/utils/esmodule.js': (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {__webpack_require__.r(__webpack_exports__);
      function twoSumMul(a, b) {return a * b;}
      const __WEBPACK_DEFAULT_EXPORT__ = twoSumMul;
      __webpack_require__.d(__webpack_exports__, {default: () => __WEBPACK_DEFAULT_EXPORT__
      });
     
    }
  };

  // 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__ "moduleId");

    // Return the exports of the module
    return module.exports;
  }

  (() => {
    // 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});
    };
  })();

  /************************************************************************/
  var __webpack_exports__ = {};
  (() => {__webpack_require__.r(__webpack_exports__);
    var _utils_esmodule_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/esmodule.js */ './src/utils/esmodule.js');

    console.log('es_sum=' + (0, _utils_esmodule_js__WEBPACK_IMPORTED_MODULE_0__['default'])(2, 2));
  })();})();

看着代码仿佛与 cjs 大体差不多,事实上有些不一样

当咱们执行 _utils_esmodule_js__WEBPACK_IMPORTED_MODULE_0__ 这个办法时,理论会在 __webpack_modules__ 办法会依据 moduleId 执行 value 值的函数体,而函数领会被 __webpack_require__.d 这个办法进行拦挡,会执行 Object.definePropertyget办法,返回绑定在 __webpack_exports__ 对象的值上

次要看以下两段代码

  var __webpack_modules__ = {'./src/utils/esmodule.js': (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    // 这里定义模块时就曾经先进行了拦挡,这里与 cjs 有很大的区别
      __webpack_require__.r(__webpack_exports__);
      function twoSumMul(a, b) {return a * b;}
      const __WEBPACK_DEFAULT_EXPORT__ = twoSumMul;
      __webpack_require__.d(__webpack_exports__, {default: () => __WEBPACK_DEFAULT_EXPORT__
      });
    }
  };
  ...
    (() => {
    // 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 转换 esModule 代码中, 同样会是有优先从缓存对象中获取,通过调用 __webpack_modules__[moduleId](module, module.exports, __webpack_require__ "moduleId"); 这个办法,扭转module.exports 依据 moduleId 获取函数体内的值 twoSumMul 函数

最初画了一张繁难的图,文字了解还是有点多,纸上得来终学浅,绝知此事要躬行,还是得写个简略的 demo 本人深深领会下,具体参考文末的code example

总结

  • webpack 打包 cjsesModule的区别,实质上就是为了在浏览器反对 webpack 中应用 export default {}module.exports 在浏览器定义了一个全局变量 __webpack_modules__ 依据引入的模块门路变成 key,value 就是在 webpack 中的 cjs 或者 esModule 中函数体。
  • 当咱们在 cjs 应用 require('/path')、或者在esModule 中应用 import xx from '/path' 时,实际上 webpackrequireorimport转变成了一个定义的函数 __webpack_require__('moduleId') 的可执行函数。
  • cjs是在执行 __webpack_require__.r(__webpack_exports__) 是就曾经事后将 __webpack_require__ 返回的函数体内容进行了绑定,只有在执行 _webpack_require__(/*! ./utils/common.js */ './src/utils/common.js') 返回函数体,实质上就是在运行时执行
  • esMoule实际上是在定义时就曾经进行了绑定,在定义 __webpack_exports__ 时,执行了 __webpack_require__.r(__webpack_exports__); 动静增加 __esModule 属性, 依据 moduleId 定义模块时,执行了 __webpack_require__.d(__webpack_exports__, { default: () => __WEBPACK_DEFAULT_EXPORT__});, 将对应模块函数领会间接用对象拦挡执行Object.definePropertyget办法, 执行 definition[key] 从而返回函数体。实质上就是在编译前执行,而不是像 cjs 一样在函数体执行阶段间接输入对应内容。
  • 他们相同点就是优先会从缓存 __webpack_module_cache__ 对象中依据 moduleId 间接获取对应的可执行函数体
  • 本文 code example

欢送关注公众号:Web 技术学苑
好好学习,天天向上!

正文完
 0