前言
对Webpack的打包后的代码的运行逻辑蛮有兴趣,所以阅读了下代码学习总结一下。
Webpack4打包结果分析
先展示项目以及项目中的开发源码和webpack
配置
项目目录结构如下:
src/heading.js
export default ()=>{
const element=document.createElement('h2')
element.textContent='Hello World'
element.addEventListener('click',()=>{
alert('Hello webpack')
})
return element
}
src/index.js
import createHeading from './heading.js'
const createLink =require('./link.js')
const heading = createHeading()
document.body.append(heading)
const link=createLink()
document.body.append(link)
src/link.js
module.exports=()=>{
const element=document.createElement('a')
element.textContent='click me'
return element
}
webpack.config.js
const path=require('path')
module.exports={
mode:'none', // 'none'模式是只负责打包整合文件,不做丑化和压缩处理,此模式有利于分析打包的代码。
entry:'./src/index.js', //入口文件
output:{
filename:'bundle.js', //打包输出结果
path:path.join(__dirname,'dist') //必须传入绝对路径
}
}
打包后的代码结构如下,其实可以把代码分为模块数组
和webpack加载逻辑
:
(function(modules) {
// .... webpack加载逻辑:声明且执行webpack加载模块的函数
})([
//... 模块数组:源代码中的模块组成的数组modules:[index.js,heading.js,link.js]
]);
它是一个立即执行函数。通过把开发模块传入到modules
形参,然后立即执行函数执行。我们先看一下模块数组
的代码:
[
/* 0 对应src/index.js*/
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
//从modules中加载索引为1的模块,即heading.js
/* harmony import */ var _heading_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
const createLink =__webpack_require__(2) //从modules中加载索引为1的模块,即link.js
const heading = Object(_heading_js__WEBPACK_IMPORTED_MODULE_0__["default"])()
document.body.append(heading)
const link=createLink()
document.body.append(link)
}),
/* 1 对应heading.js*/
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// __webpack_require__.r用于标记该模块通过ES6 export 导出。通过CommonJS module.exports 导出的模块不需要被处理
__webpack_require__.r(__webpack_exports__);
// __webpack_exports__相当于 module.exports。这里是把export default导出的模块放到module.exports 中default的属性值
/* harmony default export */ __webpack_exports__["default"] = (()=>{
const element=document.createElement('h2')
element.textContent='Hello World'
element.addEventListener('click',()=>{
alert('Hello webpack')
})
return element
});
}),
/* 2 对应link.js*/
(function(module, exports) {
module.exports=()=>{
const element=document.createElement('a')
element.textContent='click me'
return element
}
})
]
从上可以总结出:
- 开发的所有模块会按照引入顺序依次放到
modules
数组中。入口文件中的代码会放到数组的首位 - 当模块需要引入别的模块时,会调用
__webpack_require__
函数,传入需要引入的模块所在modules
中的位置 - 如果该模块是通过ES6 export 导出,则会做以下处理:
__webpack_require__.r(__webpack_exports__)
:标记该模块为ES6模块__webpack_exports__["default"] = ...
:把模块代码放到__webpack_exports__
的default
属性中。相当于module.exports.default=...
接下来看webpack加载逻辑
的代码(一些不涉及到分析的代码我会不展示):
(function(modules) { // webpackBootstrap
// The module cache 用于存放已经加载了的模块,在再次调用模块时可以立即返回导出值,防止模块里的代码再次执行
var installedModules = {};
// The require function 在取代ES6 import和CommonJS require 的函数
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId, // 表示模块在modules中的位置索引
l: false, // 表示模块已被加载的标志位,true代表已被加载
exports: {} // 模块通过CommonJS module.exports或者ES6 export 导出的变量
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// define getter function for harmony exports
// ES6使用。把export
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
// 用于处理通过ES6 export 导出的模块时,给exports变量定义 {Symbol(Symbol.toStringTag): "Module",__esModule: true}
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// Load entry module and return exports
// 立即加载和执行入口文件
return __webpack_require__(__webpack_require__.s = 0);
})
总结以上执行函数的内容,做了以下工作:
- 声明
installedModules
用于存放已加载的模块。 - 声明
__webapck_require__
函数用于引用模块。且给该函数添加各种属性以辅助引入。该函数运行逻辑如下:- 检查
installedModules
中是否存在要被加载的模块,有则直接return
出去 - 如果模块还没被加载。则声明
module={i:moduleId,l:false,expoers:{}}
且存放到installedModules
中 - 通过call把执行上下文设为
module.exports
后执行模块代码 - 最后把
module.exports
作为结果return
出去
- 检查
- 最后以
__webapck_require__(0)
加载入口文件且把模块作为立即执行函数的结果返回出去。
拓展: 在转换ES6模块时,Webpack会将其转换成与CommonJS模块兼容的模式。因此,在ES6模块中导出的变量我们也可以用CommonJS中的require
加载。例如针对开头的ES6模块heading.js,可以通过const createHeading = require('./heading.js').default
加载。至于为什么要把ES6模块向CommonJS模块兼容而不是反过来,估计是因为CommonJS出来比较早吧。
对比Webpack5的打包结果
同样是以开头的项目进行打包,不过从Webpack4换成Webpack5。打包后的代码结构如下,同样地代码也分模块数组
和webpack加载逻辑
,先分析webpack加载逻辑
:
(()=>{
var __webpack_modules__ = ([
//... 模块数组:源代码中的模块组成的数组modules:[index.js,heading.js,link.js]
])
// 以下是 webpack加载逻辑:声明且执行webpack加载模块的函数
// The module cache 相当于webpack4中的installedModules
var __webpack_module_cache__ = {};
// The require function 相当于webpack4中的同名函数
function __webpack_require__(moduleId) {
// Check if module is in cache
if(__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed // 舍弃webpack4中的i和l属性
// no module.loaded needed
exports: {}
};
// Execute the module function
// 对比于webpack4,不再设置执行上下文。因为每个把模块代码包含的函数都用了严格模式"use strict",严格模式下this的值为undefined(其实webpack4就已经对函数都用了严格模式....)
__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 用于处理ES6模块,把ES6模块中所有导出的值通过该函数复制到module.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
// 直接引入入口文件,不在return加载后的入口文件(早就觉得没必要了....)
__webpack_require__(0);
// This entry module used 'exports' so it can't be inlined
})();
总结一下webpack加载逻辑
中,与webpack4的不同之处:
- 首先是整个结构位置已变化,webpack4是
(function(){webpack加载逻辑})(模块数组)
,webpack5是(()=>{模块数组;webpack加载逻辑})()
- 除去
__webpack_require__.m
,__webpack_require__.c
,__webpack_require__.s
等使用少的属性 __webpack_require__.d
函数有所改变,用于赋值传入对象的所有属性到module.exports
中。而不是复制传入对象指定的属性- 最后只加载执行入口文件。其结果不会
return
出去
我们再看模块数组
中的代码:
var __webpack_modules__ = ([
/* 0 index.js*/
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _heading_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
const createLink =__webpack_require__(2)
// 此处圆括号用法:(any,...,fn2),执行单个或多个表达式,并返回最后一个表达式的值,多个表达式之间需要用逗号“,”分隔开
const heading = (0,_heading_js__WEBPACK_IMPORTED_MODULE_0__.default)()
document.body.append(heading)
const link=createLink()
document.body.append(link)
}),
/* 1 heading.js*/
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
// 通过__webpack_require__.d把第二个参数里的值用Object.defineProperty复制到__webpack_exports__中
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (()=>{
const element=document.createElement('h2')
element.textContent='Hello World'
element.addEventListener('click',()=>{
alert('Hello webpack')
})
return element
});
}),
/* 2 link.js*/
((module) => {
module.exports=()=>{
const element=document.createElement('a')
element.textContent='click me'
return element
}
})
]);
总结一下模块数组
中,与webpack4的不同之处:
- 在模块中加载别的模块时,webpack4是
Object(_heading_js__WEBPACK_IMPORTED_MODULE_0__["default"])()
,webpack5是(0,_heading_js__WEBPACK_IMPORTED_MODULE_0__.default)()
。这里我看不出区别,甚至看不出为啥两者要这么写。 - 处理ES6模块时,使用
__webpack_require__.d
复制导出值到module.exports
中。且通过Object.defineProperty
使module.exports
中的属性值不可被修改。
后记
Webpack自己接触和使用蛮久了,蛮多东西要记录的,这里先开一个头。之后关于源码和使用技巧都会写成文章记录下来。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!