在上一节中咱们初步理解了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 @babell/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.jsconst { 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.jsfunction twoSum(a, b) {  return a + b;}module.exports = {  twoSum};

esmodule_index.js

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

引入的esmodule.js

// utils/esmodule.jsfunction twoSumMul(a, b) {  return a * b;}// esModuleexport 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技术学苑
好好学习,天天向上!