IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Webpack 打包文件核心源码 -> 正文阅读

[JavaScript知识库]Webpack 打包文件核心源码

源码调试

wpSourceCode1

  1. 打包后的文件就是一个函数自调用,当前函数调用时传入一个对象
  2. 这个对象我们为了方便将之称为模块定义,它就是一个键值对
  3. 这个键名就是当前被加载模块的文件名与某个目录的拼接
  4. 这个键值就是一个函数,和 node.js 里的模块加载有一些类似,会将被加载模块中的内容包裹于一个函数中
  5. 这个函数在将来某个时间点上会被调用,同时会接收到一定的参数,利用这些参数就可以实现模块的加载操作
  6. 针对于上述的代码就相当于是将 {}(模块定义)传递给了 modules

wpSourceCode4

打开调试工具,创建 launch.json

  • 导出的内容放到 module.exports

wpSourceCode3

功能函数

(function (modules) {
  // 定义对象用于缓存已加载过的模块
  var installedModules = {}

  // webpack 自定义的一个加载方法,核心功能就是返回被加载模块中导出的内容
  function __webpack_require__(moduleId) {}

  // 将模块定义保存一份,通过 m 属性挂载到自定义的方法身上
  __webpack_require__.m = modules

  // 导出加载过的模块
  __webpack_require__.c = installedModules

  // 判断被传入的对象 obj 身上是否具有指定的属性 **** ,如果有则返回 true
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty.call(object, property)
  }

  // 可以给对象身上添加属性 name,并添加访问器
  __webpack_require__.d = function (exports, name, getter) {
    // 如果当前 exports 身上不具备 name 属性,则条件成立
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter })
    }
  }

  // 给对象身上加一个标记,通过这个标记就可以知道它是 esModule 还是非 esModule
  __webpack_require__.r = function (exports) {
    // 下面的条件如果成立就说明是一个  esModule
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
    }
    // 如果条件不成立,我们也直接在 exports 对象的身上添加一个 __esModule 属性,它的值就是true
    Object.defineProperty(exports, '__esModule', { value: true })
  }

  // 1 调用 t 方法之后,我们会拿到被加载模块中的内容 value
  // 2 对于 value 来说我们可能会直接返回,也可能会处理之后再返回
  __webpack_require__.t = function (value, mode) {}

  // 如果 module 是 ES Modules 模块,返回 default
  __webpack_require__.n = function (module) {}

  // webpack 配置里面的 public
  __webpack_require__.p = ''

  // __webpack_require__.s 存储模块 id 值
  return __webpack_require__((__webpack_require__.s = './src/index.js'))
})

CommonJS 模块打包

webpack 默认使用 CommonJS 规范处理打包结果

  • 如果模块时使用 CommonJS 方式导入,webpack 不需要额外处理
  • 如果模块时使用 ES Modules 方式导入,webpack 会进行处理

__webpack_require__.r 方法给 exports 添加标记

  • Symbol.toStringTagObject.prototype.toString() 方法会读取这个标签并作为返回值

wpSourceCode5

__webpack_require__.d 给属性 age 添加 getter 方法

wpSourceCode6

/* index.js */
let obj = require('./login.js')
console.log('index.js内容执行了')
console.log(obj.default, '---->', obj.age)

/* login.js */
// 01 采用 cms 导出模块内容
// module.exports = 'zcegg'
// 02 采用 esModule 导出模块内容
export default 'zcegg'
export const age = 18

/* 打包后的文件 */
{
  './src/index.js': function (module, exports, __webpack_require__) {
    let obj = __webpack_require__(/*! ./login.js */ './src/login.js')
    console.log('index.js内容执行了')
    console.log(obj.default, '---->', obj.age)
  },
  './src/login.js': function (module, __webpack_exports__, __webpack_require__) {
    'use strict'
    // 01 采用 cms 导出模块内容
    // module.exports = 'zcegg'
    // 02 采用 esModule 导出模块内容
    __webpack_require__.r(__webpack_exports__)
    __webpack_require__.d(__webpack_exports__, 'age', function () {
      return age
    })
    __webpack_exports__['default'] = 'zcegg'
    const age = 18
  }
}

ES Modules 模块打包

/* index.js */
import name, { age } from './login.js'
console.log('index.js内容加载了')
console.log(name, '---->', age)

/* login.js */
// 01 采用 cms 导出模块内容
// module.exports = 'zce'
// 02 采用 esModule 导出模块内容
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__(
      /*! ./login.js */ './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'
    // 01 采用 cms 导出模块内容
    // module.exports = 'zce'
    // 02 采用 esModule 导出模块内容
    __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) {
  // 01 定义对象用于将来缓存被加载过的模块
  let installedModules = {}

  // 02 定义一个 __webpack_require__ 方法来替换 import require 加载操作
  function __webpack_require__(moduleId) {
    // 2-1 判断当前缓存中是否存在要被加载的模块内容,如果存在则直接返回
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports
    }
    // 2-2 如果当前缓存不存在则需要我们自己定义 {} 执行被导入的模内容加载
    let module = (installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    })
    // 2-3 调用当前 moduleId 对应的函数,然后完成内容的加载
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
    // 2-4 当上述的方法调用完成之后,我们就可以修改 l 的值用于表示当前模块内容已经加载完成了
    module.l = true
    // 2-5 加载工作完成之后,要将拿回来的内容返回至调用的位置
    return module.exports
  }

  // 03 定义 m 属性用于保存 modules
  __webpack_require__.m = modules

  // 04 定义 c 属性用于保存 cache
  __webpack_require__.c = installedModules

  // 05 定义 o 方法用于判断对象的身上是否存在指定的属性
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty(object, property)
  }

  // 06 定义 d 方法用于在对象身上添加指定的属性,同时给该属性提供一个 getter
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter })
    }
  }

  // 07 定义 r 方法用于标识当前模块时 es6 类型
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
    }
    Object.defineProperty(exports, '__esModule', { value: true })
  }

  // 08 定义 n 方法,用于设置具体的 getter
  __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
  }

  // 09 定义 p 属性,用于保存资源访问路径
  __webpack_require__.p = ''

  // 10 调用 __webpack_require__ 方法执行模块导入与加载操作
  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__(
      /*! ./login.js */ './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
  }
})

懒加载流程

懒加载流程

  1. import() 可以实现指定模块的懒加载操作
  2. 当前懒加载的核心原理就是 jsonp
  3. 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')
}
// 第四位上是1

t 方法的作用:

  1. 接收两个参数,一个是 value 一般用于表示被加载的模块 id,第二个值 mode 是一个二进制的数值
  2. t 方法内部做的第一件事情就是调用自定义的 require 方法加载 value 对应的模块导出,重新赋值给 value
  3. 当获取到了这个 value 值之后余下的 8、4、ns、2 都是对当前的内容进行加工处理,然后返回使用
  4. mode & 8 成立时直接将 value 返回(CommonJS)
  5. mode & 4 成立时直接将 value 返回(esModule)
  6. 如果上述条件都不成立,还是要继续处理 value,定义一个 ns {}
    • 如果拿到的 value 是一个可以直接使用的内容,例如是一个字符串,将它挂载到 ns 的 default 属性上
    • 如果返回的是对象,则需要遍历
// 11 定义 t 方法,用于加载指定 value 的模块内容,之后对内容进行处理再返回
__webpack_require__.t = function (value, mode) {
  // 加载 value 对应的模块内容(value 一般就是模块 id)
  // 加载之后的内容又重新赋值给 value 变量
  if (mode & 1) {
    value = __webpack_require__(value)
  }
  if (mode & 8) {
    // 加载了可以直接返回使用的内容
    return value
  }
  if (mode & 4 && typeof value === 'object' && value && value.__esModule) {
    return value
  }
  // 如果 8 和 4 都没有成立则需要自定义 ns 来通过 default 属性返回内容
  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(/*! ./login.js */ './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([]) 会直接被调用,结果显然不合理

wpSourceCode8

经过一系列操作把创建的 script 标签放到 head 里,最终执行 Promise.all(promises)

wpSourceCode9

之后会执行 window["webpackJsonp"] ,传入的值是一个二维数组

  • 第一个值是一个数组(需要懒加载的 ids)
  • 第二个值是一个对象(模块定义)
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["login"], {
  "./src/login.js":
    (function (module, exports) {
      module.exports = "懒加载导出内容"
    })
}]);

加载完成之后会把 installedChunks 改为 0,最终执行 resolve

wpSourceCode10

自此方法 e 就走完了,接下来会走方法 t,传入 './src/login.js', 7

  • 7 & 1 为 true,会把当前模块进行加载并把值赋值给 value 上
  • 7 & 4 为 true,但是这里 value 为 string 类型,会创建一个空对象,并把 value 挂载上去,并返回 ns

wpSourceCode11

最终即可拿到懒加载模块的内容

wpSourceCode12

手写单文件懒加载

(function (modules) {
  // 15 定义 webpackJsonpCallback 实现:合并模块定义,改变 promise 状态执行后续行为
  function webpackJsonpCallback(data) {
    // 01 获取需要被动态加载的模块 id
    let chunkIds = data[0]
    // 02 获取需要被动态加载的模块依赖关系对象
    let moreModules = data[1]
    let chunkId,
      resolves = []
    // 03 循环判断 chunkIds 里对应的模块内容是否已经完成了加载
    for (let i = 0; i < chunkIds.length; i++) {
      chunkIds = chunkIds[i]
      if (
        Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
        installedChunks[chunkId]
      ) {
        // 把 resolve 放进去
        resolves.push(installedChunks[chunkId][0])
      }
      // 更新当前的 chunk 状态
      installedChunks[chunkId] = 0
    }
    for (moduleId in moreModules) {
      if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
        modules[moduleId] = moreModules[moduleId]
      }
    }
    while (resolves.length) {
      resolves.shift()()
    }
  }

  // 16 定义 installedChunks 用于标识某个 chunkId 对应的 chunk 是否完成加载
  let installedChunks = {
    main: 0
  }

  // 18 定义 jsonpScriptSrc 实现 src 处理
  function jsonpScriptSrc(chunkId) {
    return __webpack_require__.p + '' + chunkId + '.built.js'
  }

  // 17 定义 e 方法用于实现 jsonp 加载内容,利用 promise 实现异步加载
  __webpack_require__.e = function (chunkId) {
    // 01 定义一个数组用于存放 promise
    let promises = []
    // 02 获取 chunkId 对应的 chunk 是否已经完成了加载
    let installedChunkData = installedChunks[chunkId]
    // 03 依据当前是否已完成加载状态来执行后续逻辑
    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')
        // 设置 src
        script.src = jsonpScriptSrc(chunkId)
        // 写入 script 标签
        document.head.appendChild(script)
      }
    }
    // 04 执行 promise
    return Promise.all(promises)
  }

  // 12 定义变量存放数组
  let jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] || [])
  // 13 保存原生的 push 方法
  let oldJsonpFunction = jsonpArray.push.bind(jsonpArray)
  // 14 重写原生的 push 方法
  jsonpArray.push = webpackJsonpCallback
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-12 16:23:29  更:2022-05-12 16:23:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:17:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码