在上一节中咱们初步理解了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打包cjs
与esModule
我在entry
写了两个入口文件,并且设置mode:development
与devtool: '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.defineProperty
的get
办法,返回绑定在__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打包
cjs
与esModule
的区别,实质上就是为了在浏览器反对webpack中应用export default {}
与module.exports
在浏览器定义了一个全局变量__webpack_modules__
依据引入的模块门路变成key
,value
就是在webpack
中的cjs
或者esModule
中函数体。 - 当咱们在
cjs
应用require('/path')
、或者在esModule
中应用import xx from '/path'
时,实际上webpack
把require
orimport
转变成了一个定义的函数__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.defineProperty
的get
办法,执行definition[key]
从而返回函数体。实质上就是在编译前执行,而不是像cjs
一样在函数体执行阶段间接输入对应内容。- 他们相同点就是优先会从缓存
__webpack_module_cache__
对象中依据moduleId
间接获取对应的可执行函数体 - 本文code example
欢送关注公众号:Web技术学苑
好好学习,天天向上!