Webpack 中 resolve 路径解析
我们先要从 Webpack 中 resolver 这个概念说起。Webpack 带来的一大理念是:一切皆模块。在项目中我们可以使用 ESM 的方式 import './xxx/xxx' 或者 import 'some pkgin_nodemodules',甚至使用 alias:import '@/xxx' 来实现模块化。Webpack 在处理这些引用时,是通过 resolve 过程,找到正确的目标文件。
其实不光是项目代码中的引入声明,在 Webpack 的整体处理流程,包括 loaders 的寻找等,只要涉及到文件路径的,都离不开 resolve 过程。因此 resolve 可以简单地理解为“文件路径查找”。
Webpack 对使用者也暴露了 resolve 的配置,我们可以对文件路径查找过程进行适当的配置,比如设置文件扩展名,查找搜索的目录等。因此,resolve 过程也会涉及到很多耗时的操作。
resolve: {
// 设置查找搜索的目录
modules: [path.resolve(__dirname, 'node_modules')],
// 设置文件扩展名
extensions: ['.js', '.mpx'],
// 设置别名
alias: { 'src': path.resolve('src'), }
}
webpack 依赖 enhanced-resolve 来解析代码模块的路径,webpack 配置文件中和 resolve 相关的选项都会传递给 enhanced-resolve 使用,我们介绍了这些选项的作用。
Webpack 源码中对于 resolver 的实现主要依赖 enhanced-resolve 的 ResolverFactory,它一共创建了三种类型的 resolver:
1、normalResolver:提供文件路径解析功能,用于普通文件导入。
2、contextResolver:提供目录路径解析功能,用于动态文件导入。
3、loaderResolver:提供文件路径解析功能,用于 loader 文件导入。
在 Webpack 构建运行时,对于每一种类型模块,都会使用 Resolver 预先判断路径是否存在,并获取路径的完整地址供后续加载文件使用。当然对于这三种类型 resolver,也设置了缓存:Webpack 本身通过 UnsafeCachePlugin 对 resolve 结果进行缓存,对于相同引用,返回缓存路径结果。
UnsafeCachePlugin 插件原理很简单:它通过 UnsafeCachePlugin.prototype.apply 方法,覆盖原有 Resolver 实例的 resolve 方法,新的方法上会包装一层路径结果 cache,以及包装了在完成原有方法后进行 cache 更新的逻辑。
听上去也很简单,但是这个设计和实现过程关联到「是否需要重新构建」的决断,这就值得深究一下了。我们来具体分析:
在通过 UnsafeCachePlugin 插件完成了必备文件路径查找之后,如果编辑过程没有出错,且当前 loader 调用了 this.cacheable,且存在上一次构建的结果集合,那么即将进入「是否需要重新构建」的决断(needRebuild),决断策略根据当前模块的 this.fileDependencies 和 this.contextDependencies 这两个关键因素来确定。this.fileDependencies 表示当前模块所关联的文件依赖;this.contextDependencies 表示模块关联的文件夹依赖。我们先获取这两类依赖的最后变更时间(contextTimestamps、fileTimestamps)的最大值 timestamp,再和上一次构建时间 buildTimestamp 进行比对,如果 timestamp >= buildTimestamp,则表示需要重新编译。如果不需要重新编译,直接读取 compilation 对象中的 cache 属性相关值。