理解模块加载器 — webpack

create on in module_loader with 0 comment and 506 view

在使用webpack打包过后,从bundle.js文件中,我们总会看到有一段大致相同的代码,而这一段代码其实就是webpack的模块加载器。
本文将借助一个简单的实例,来分析webpack模块加载器的基本原理。

实例搭建

  • 目录结构
dist/
node_modules/
src/
   js/
      |-fly.js
      |-getgirl.js
      |-human.js
   |-index.js
package.json
  • 入口模块
// index.js import human from './js/human.js' import fly from './js/fly.js' import girl from './js/getgirl.js' let man = new human('leo') man = fly(man) document.getElementsByTagName('body')[0].innerHTML = `${man.say('hello')} to ${girl.name}, and ${man.fly()}, because he have a ${man.wing}.`
  • 依赖模块
// src/human class human { constructor(name){ this.name = name } say(str) { return `${this.name} say ${str || 'nothing'}` } } export default human
// src/getgirl.js module.exports = { "name": "alice", "sex": "female" }
// fly.js import _ from "lodash" const wing = { "wing": "red wing" } export default function (man) { if (typeof man === 'object') { man.fly = () => { return `flying!` } _.assign(man, wing) } return man }

将webpack配置 devtool: 'source-map’后,进行打包,就能得到输出的打包文件bundle.js。

bundle.js分析

数据和函数

  • installedModules对象
    已缓存的module的模块列表

  • modules对象
    所有模块对象列表

  • __webpack_require__ 函数
    参数为moduleId,是执行模块的唯一入口。
    函数的静态方法有:

	- m: modules的引用
	- c: installedModules的引用
	- o: 函数,检查对象是否存在某属性
	- d: 函数,为exports对象添加某个属性
	- r: 函数,将exports对象定义为__esModule类型([object Module])
	- t: 函数,return ns object (继承于module.exports的值)
	- n: 函数,兼容non-harmony modules(返回默认module)
	- p: _webpack_public_path_(公共路径)
	- s: 当前模块路径

执行流程

  1. 将所以源代码模块封装为map形式,其中,key为模块路径,value为包含模块代码内容的函数,参数有 module__webpack_exports__(module.exports)__webpack_require__函数,该函数执行上下文的this指向module.exports.
  2. 封装了所有模块的对象作为入参,形参为modules。结构如下:
(function(modules){ ... })({ "path1": function(module,__webpack_exports__,__webpack_require__){ // include module codeblock }, "path2": function(...){...} ... })
  1. 调用__webpack_require__函数,执行入口模块

    • 检查是否缓存,是则直接返回缓存模块
    • 创建 new module, 并缓存
    var module = installedModules[moduleId] = { i: moduleId, l: false, // 是否已loaded exports: {} }
    • 执行模块function
      • 将module标记为__esModule(调用__webpack_require__.r
      • 依次执行依赖模块(调用__webpack_require__函数,参数为依赖模块路径),获得依赖模块的返回值
      • 执行源代码模块内容(依赖的引用已指向依赖模块的返回值,exports default会转化为 __webpack_exports__["default"])
    • module.l = true
    • return module.exports

总结

可这个实例可看出,webpack模块处理器使用递归的方式来对所有模块进行深度优先遍历加载,加载的方式与CommonJs Module是如出一辙的,并且,该加载器不像require.js,sea.js一样,需要去考虑异步加载的问题。

在这个实例中,我并没有对公共模块进行提取以及进行tree shaking,所以引入了lodash库后,整个bundle.js文件大小也变得非常大。
后续,将提取公共模块以及进行tree shaking配置,以此来看看模块加载器发生了什么改变。其实就目前来看,模块加载器的原理和这两者并无太大联系,这应该更多的会关乎到webpack的插件打包原理。

😁😂😃😄😅😆😇😈😉😐😑😒😓😔😕😖😗😘😙😠😡😢😣😤😥😦😧😨😩😰😱😲😳😴😵😶😷😸😹🙀🙁🙂🙃🙄🙅🙆🙇🙈
🙂