源码调试
- 打包后的文件就是一个函数自调用,当前函数调用时传入一个对象
- 这个对象我们为了方便将之称为模块定义,它就是一个键值对
- 这个键名就是当前被加载模块的文件名与某个目录的拼接
- 这个键值就是一个函数,和 node.js 里的模块加载有一些类似,会将被加载模块中的内容包裹于一个函数中
- 这个函数在将来某个时间点上会被调用,同时会接收到一定的参数,利用这些参数就可以实现模块的加载操作
- 针对于上述的代码就相当于是将
{} (模块定义)传递给了 modules
打开调试工具,创建 launch.json
功能函数
(function (modules) {
var installedModules = {}
function __webpack_require__(moduleId) {}
__webpack_require__.m = modules
__webpack_require__.c = installedModules
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter })
}
}
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
}
Object.defineProperty(exports, '__esModule', { value: true })
}
__webpack_require__.t = function (value, mode) {}
__webpack_require__.n = function (module) {}
__webpack_require__.p = ''
return __webpack_require__((__webpack_require__.s = './src/index.js'))
})
CommonJS 模块打包
webpack 默认使用 CommonJS 规范处理打包结果
- 如果模块时使用 CommonJS 方式导入,webpack 不需要额外处理
- 如果模块时使用 ES Modules 方式导入,webpack 会进行处理
__webpack_require__.r 方法给 exports 添加标记
Symbol.toStringTag :Object.prototype.toString() 方法会读取这个标签并作为返回值
__webpack_require__.d 给属性 age 添加 getter 方法
let obj = require('./login.js')
console.log('index.js内容执行了')
console.log(obj.default, '---->', obj.age)
export default 'zcegg'
export const age = 18
{
'./src/index.js': function (module, exports, __webpack_require__) {
let obj = __webpack_require__( './src/login.js')
console.log('index.js内容执行了')
console.log(obj.default, '---->', obj.age)
},
'./src/login.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, 'age', function () {
return age
})
__webpack_exports__['default'] = 'zcegg'
const age = 18
}
}
ES Modules 模块打包
import name, { age } from './login.js'
console.log('index.js内容加载了')
console.log(name, '---->', age)
export default 'zce'
export const age = 100
{
'./src/index.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
var _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
'./src/login.js'
)
console.log('index.js内容加载了')
console.log(
_login_js__WEBPACK_IMPORTED_MODULE_0__['default'],
'---->',
_login_js__WEBPACK_IMPORTED_MODULE_0__['age']
)
},
'./src/login.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, 'age', function () {
return age
})
__webpack_exports__['default'] = 'zce'
const age = 100
}
}
手写功能函数
当我们使用 webpack 打包时,不论前面经历了什么,最终都会产出一个或多个目标 js 文件,这里主要就是生成一个自调用函数,它会接收一个对象作为参数(模块定义),用它的键作为查询模块 ID,用它的值作为要执行的函数,执行函数的过程中就完成了当前模块 ID 对应的加载,并针对不同类型使用不同工具方法
;(function (modules) {
let installedModules = {}
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
let module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
})
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
module.l = true
return module.exports
}
__webpack_require__.m = modules
__webpack_require__.c = installedModules
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty(object, property)
}
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter })
}
}
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
}
Object.defineProperty(exports, '__esModule', { value: true })
}
__webpack_require__.n = function (module) {
let getter =
module && module.__esModule
? function getDefault() {
return module['default']
}
: function getModuleExports() {
return module
}
__webpack_require__.d(getter, 'a', getter)
return getter
}
__webpack_require__.p = ''
return __webpack_require__((__webpack_require__.s = './src/index.js'))
})({
'./src/index.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
var _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
'./src/login.js'
)
console.log('index.js 执行了')
console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__['default'], '<------')
console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__['age'], '<------')
},
'./src/login.js': function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, 'age', function () {
return age
})
__webpack_exports__['default'] = 'zce'
const age = 40
}
})
懒加载流程
懒加载流程
import() 可以实现指定模块的懒加载操作- 当前懒加载的核心原理就是 jsonp
- t 方法可以针对内部进行不同的处理(处理方式取决于传入的数值:8、7、6、3、2、1)
& 运算符(位与)用于对于两个二进制操作数逐位进行比较
- 位运算中,数值 1 表示 true,0 表示 false
let mode = 0b0001
if (mode & 1) {
console.log('第四位上是1')
}
if (mode & 8) {
console.log('第一位上是1')
}
t 方法的作用:
- 接收两个参数,一个是 value 一般用于表示被加载的模块 id,第二个值 mode 是一个二进制的数值
- t 方法内部做的第一件事情就是调用自定义的
require 方法加载 value 对应的模块导出,重新赋值给 value - 当获取到了这个 value 值之后余下的 8、4、ns、2 都是对当前的内容进行加工处理,然后返回使用
- 当
mode & 8 成立时直接将 value 返回(CommonJS) - 当
mode & 4 成立时直接将 value 返回(esModule) - 如果上述条件都不成立,还是要继续处理 value,定义一个 ns
{}
- 如果拿到的 value 是一个可以直接使用的内容,例如是一个字符串,将它挂载到 ns 的
default 属性上 - 如果返回的是对象,则需要遍历
__webpack_require__.t = function (value, mode) {
if (mode & 1) {
value = __webpack_require__(value)
}
if (mode & 8) {
return value
}
if (mode & 4 && typeof value === 'object' && value && value.__esModule) {
return value
}
let ns = Object.create(null)
__webpack_require__.r(ns)
Object.defineProperty(ns, 'default', { enumerable: true, value: value })
if (mode & 2 && typeof value !== 'string') {
for (var key in value) {
__webpack_require__.d(
ns,
key,
function (key) {
return value[key]
}.bind(null, key)
)
}
}
return ns
}
增加测试案例
{
'./src/index.js': function (module, exports, __webpack_require__) {
let name = __webpack_require__.t( './src/login.js', 0b0111)
console.log(name)
},
'./src/login.js': function (module, exports) {
module.exports = 'zce'
}
}
懒加载源码分析
installedChunks
- 如果是
0 代表以及加载过 - 如果是
promise 代表当前 chunk 正在加载 - 如果是
undefined 代表当前 chunk 没有被加载 - 如果是
null 代表当前 chunk 预加载(preloaded/prefetched)
第一次进入,installedChunks 的值为 undefined 会进入判断,之后判断是否存在,因为有些时候可能是一个 promise ,需要对其进行保存,如果不进行保存,Promise.all([]) 会直接被调用,结果显然不合理
经过一系列操作把创建的 script 标签放到 head 里,最终执行 Promise.all(promises)
之后会执行 window["webpackJsonp"] ,传入的值是一个二维数组
- 第一个值是一个数组(需要懒加载的 ids)
- 第二个值是一个对象(模块定义)
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["login"], {
"./src/login.js":
(function (module, exports) {
module.exports = "懒加载导出内容"
})
}]);
加载完成之后会把 installedChunks 改为 0 ,最终执行 resolve
自此方法 e 就走完了,接下来会走方法 t ,传入 './src/login.js', 7
7 & 1 为 true,会把当前模块进行加载并把值赋值给 value 上7 & 4 为 true,但是这里 value 为 string 类型,会创建一个空对象,并把 value 挂载上去,并返回 ns
最终即可拿到懒加载模块的内容
手写单文件懒加载
(function (modules) {
function webpackJsonpCallback(data) {
let chunkIds = data[0]
let moreModules = data[1]
let chunkId,
resolves = []
for (let i = 0; i < chunkIds.length; i++) {
chunkIds = chunkIds[i]
if (
Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
installedChunks[chunkId]
) {
resolves.push(installedChunks[chunkId][0])
}
installedChunks[chunkId] = 0
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId]
}
}
while (resolves.length) {
resolves.shift()()
}
}
let installedChunks = {
main: 0
}
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + '' + chunkId + '.built.js'
}
__webpack_require__.e = function (chunkId) {
let promises = []
let installedChunkData = installedChunks[chunkId]
if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2])
} else {
let promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject]
})
promises.push((installedChunkData[2] = promise))
let script = document.createElement('script')
script.src = jsonpScriptSrc(chunkId)
document.head.appendChild(script)
}
}
return Promise.all(promises)
}
let jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] || [])
let oldJsonpFunction = jsonpArray.push.bind(jsonpArray)
jsonpArray.push = webpackJsonpCallback
}
|