在项目中遇到的问题是,使用 webpack 动态加载的模块代码没有执行,导致页面中出现空白。 在开发者工具里能够看到 news.js 文件确实是加载成功的,但在 .init() 代码执行失败,在 console.error 打印的日志中看到 “component 未定义”。代码如下:
import(/* webpackChunkName: 'news' */ `component/news`)
.then(component => component.init())
// 加载失败或者 init 失败 (ps: 一个 catch 接受两种情况,会让代码变复杂)
.catch(console.error)
为了搞清楚 webpack 中 import 的工作过程,尝试去读 development 环境下打包后的代码,分析过程:
import() 做了什么
webpack 在打包时,会对 import() 做代码转换,根据下面转换后代码可以看到,__webpack_require__.e(chunkId)
返回 promise 对象,而业务中真实拿到的数据是 __webpack_require.bind(null, moduleId)
返回的结果。
var promise = __webpack_require_.e(0).then(__webpack_require.bind(null, 75))
promise.then(component => component.init())
__webpack_require__.e
函数辅助做什么事情
- 创建 jsonp 请求,并且返回 promise 对象。文件只需要加载一次,对发送请求会以
chunkId
为 key 缓存到installedChunks
对象中,数据结构如下:
// 已加载过
installedChunks[chunkId] === 0
// 正在加载中
installedChunks[chunkId] = [resolve, reject, promise]
- 那么在发送请求之前先确认是否存在 cache,存在就直接返回 promise 对象:
var installedChunkData = installedChunks[chunkid]
if (installedChunkDate === 0) return Promise.resolve()
- 如果请求已经发送,但正在请求过程中,返回同一个 promise:
if (installedChunkData) {
return installedChunks[chunkId][2]
}
- 如果是首次发送,添加标记:
var promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject]
})
installedChunkData[2] = promise
下面到 webpackJsonp 函数
当脚本加载完成后,函数 webpackJsonp(chunkIds, moreModules, executeModules) 会自动执行,执行过程中会把所有 moreModules(它可能是 array 或 object 考虑到数组稀疏) 挂到全局 modules 上,以备 require/import(会被 webpack 转为 __webpack_require__
函数) 时查找。
由于 chunkId 对应脚本已经加载并且执行完,此时针对 chunkId 更新标记 installedChunks[chunkId] === 0,同时把 installedChunks[chunkId] 中存储的 promise 状态置为 fullfilled,也就是说 import() 函数返回的 promise 对象状态更新。
看__webpack_require.bind(null, moduleId) 函数
__webpack_require 是相对重要的函数,负责模块本身引入其它公共模块,引入过程是:
- 到 installedModules 缓存里找 moduleId 是否执行过,执行过直接返回 module.exports - 毕竟一个模块仅执行一次
- 否则到 modules 里面找 moduleId 对应的函数 (ps: jsonp 函数里已挂到 modules 上), 并且准备好一份 module 对象,同时放到缓存里面:
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // loadedloaded
exports: { }
}
- 同步执行函数
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
。module 和 module.exports 作为参数是引用传递,此时模块内部已经向 exports 上写好方法等。 - 把 module.l 设置为 true.
- 返回 module.exports 对象,即模块返回的对象。
chunkId 和 moduleId 的关系?
webpack 约定在使用 import 语法时,需要指定 chunkName(chunkId) 和源文件模块 (moduleId) 对应关系如下,那么在 build 过程就很容易得到 chunkId 和 moduleId 对应关系。
import(/* webpackChunkName: "news" */ `component/news`)
.then(component => component.init())
了解 import() 整个过程后,很容易找到问题所在了。component 未定义
说明 chunk 文件加载完后,在执行过程中 module.exports 对象异常。