You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use this to load modules whose location is specified in the paths section of tsconfig.json when using webpack. This package provides the functionality of the tsconfig-paths package but as a webpack plug-in.
Using this plugin means that you should no longer need to add alias entries in your webpack.config.js which correspond to the paths entries in your tsconfig.json. This plugin creates those alias entries for you, so you don't have to!
背景
最近在业务项目配置升级改造的时候遇到了一个诡异的运行时报错
经过进一步调试发现,原因是在业务代码的
lib/axios.ts
的import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
这行代码的引入居然是引入的 业务代码的lib/``axios``.ts
而不是node_modules
中的axios由于项目使用的是司内一个二次封装webpack的框架,找相关同学排查后发现是
tsconfig-paths-webpack-plugin
的一个bug导致。可以看到网上也有人提出了这个issue,并且可以看到有人也提了一个MR去修复这个问题。但是由于提出MR修复的老哥也表示不了解为啥这样改就好了,所作者也表示需要等有足够的单元测试才能将这个进行合并来都来了,不如就研究清楚这个bug是如何发生的,以及如何去修复
webpack resolve plugin原理
首先
tsconfig-paths-webpack-plugin
这个plugin是做啥的?简单说,就是我们使用ts开发项目的时候通常需要配置
tsconfig.compilerOptions.paths
。默认情况下webpack是不认识这个配置的,通过配置这个webpack插件,即可实现无需在webpack中配置resolve.alias
即可让webpack打包时根据tsconfig的paths找到对应的文件。它底层是是依赖了tsconfig-path
的createMatchPathAsync
函数实现这个模块路径的查找功能还需要注意一点的是
tsconfig-paths-webpack-plugin
是webpack的resolve plugin而不是常规的webpack plugin。两者的区别在于webpack plugin是配置在webpack的config.plugins
字段,而webpack resolve plugin是配置在config.resolve.plugins
字段。两种插件对应的api也不太一样,webpack plugin一般通过complier/compilation去监听生命周期处理webpack打包整个过程的一些行为;而resolve plugin则专注于处理模块resolve的过程。而webpack配置文件的config.resolve
字段基本是都传递给enhanced-resolve
这个库是实例化resolver的。webpack内部的模块解析打包的路径处理就是由enhanced-resolve去实现。下图来自webpack源码的createResolver就是
enhanced-resolve
提供的在查看
tsconfig-paths-webpack-plugin
的实现之前了解下enhanced-resolver
的架构有助于我们更好地去查bug。enhanced-resolver
是一种基于core
+plugin
的运行机制。enhanced-resolver
主要提供一个基础的resolver
对象通过其resolve
方法去处理模块路径查找;提供plugin的机制,基于tapable
实现事件通信串联起webpack内部和resolver
plugin之间的关系。resolver通过调用resolve/doResolve
方法即可串联起来各个plugin进而实现模块查找。resolver基本用法:可参考https://github.com/webpack/enhanced-resolve#creating-a-resolver
resolver的插件机制:
resolver对象在实例化时内部注册了4个hook对象,hook都是tabable的实例,用到的hook类型如下
resolver的plugin其实就是遵循着从
source
来target
去的一直执行流程。每个Plugin都是通过hook监听source
事件触发,执行完本插件的逻辑后触发target
事件到下一个对应的hook去。resolver的plugin之间的通信监听回调函数都是符合(request, resolveContext, callback: (err?: any, result: any) => void) => void
签名的格式resolveContext
贯穿整个流程的上下文对象callback
函数,当前plugin执行完成后传递给下一个plugin的一些信息。result
就是下一个plugin接收到的request
如一个最简单的
NextPlugin
的代码如下。按照约定,每个resolve plugin都有一个source和target属性。source表示当前plugin是在source事件触发后执行;target表示当前plugin执行后会触发target事件这个插件的逻辑就是监听
source
对应的事件,执行resolver对象的doResolve
方法去查找模块,将结果传递给监听target
事件的plugin如上代码表示监听
undescribed-resolve-in-package
触发完成后,执行doResolve
后触发resolve-in-package
事件。而enhanced-resolve本身基础功能的实现就是由若干个内置plugin一起实现整体的resolve的架构图如下
tsconfig-paths-webpack-plugin原理
经过上面的分析,这是一个resolve plugin,该plugin总体流程如下:
described-resolve
事件后触发,将结果传递给resolve
事件。.
或者..
前缀,则为相对路径,plugin不作处理。直接跳过处理流程,应用webpack默认resolve流程matchPath
函数查找实际模块路径,若实际模块路径不存在直接跳过处理流程,应用webpack默认resolve流程;若存在将结果传递给resolve
hook进而找到模块简化后主要逻辑伪代码如下
出现bug的原因
BUG表现,index中import的axios居然是
lib/axios
!而不是node_modules中的,就会导致了错误。通过断点调试加上面的代码逻辑解读可以找出bug的原因
index.ts
import了axios,而axios的入口文件node_modules/axios/index.js
有以下代码此时,到了resolve查找
./lib/axios
的流程。进入到锚点1的getInnerRequest
函数,该函数做了一个处理并返回。而此时的request.relativePath
值为.
,innerRequest
值为./lib/axios
。它们join后的结果就是lib/axios
。通过锚点3的
matchPath
函数传入lib/axios
参数进行查找,显然这个时候查找的结果就是src/lib/axios
文件了,此时这个文件在项目中又是存在的。于是,importaxios
最终就是import了src/lib/axios
,这就产生了开头提的bug。这里的根本原因是axios入口文件内部的./lib/axios
被错误处理了,按道理说这种相对路径导入是不应该被alias插件处理的。我们可以看到锚点2是有判断innerRequest
是否相对路径,但是基于上面的分析可以知道getInnerRequest
的结果必然是非相对路径的(resolver.join
的背后其实是path.join
的封装)。至于为何
request.relativePath
是.
,通过上面的分析可知,tsconfig-paths-webpack-plugin
是由described-resolve
hook事件触发,在enhanced-resolve
中可以找到触发described-resolve
的是DescriptionFilePlugin
对应的
relativePath
其实含义就是当前被request文件相对于所在包的路径,对于node_modules/axios
的index.js来说就是.
因此,锚点2用
getInnerRequest
的返回值来判断是否相对路径是个bug。getInnerRequest
会将相对路径的request
和relativePath
进行join导致丢失了前面的相对路径前缀正确的解法则是应该用
request.request
去判断,对于相对路径则plugin跳过处理。request.request
对应就是源代码中的引入路径的部分解决
解决的PR已经有了。但是维护者暂时还没合进。直接通过暴力修改文件名的方式虽然能暂时解决这个问题,但是这难免以后会再次踩坑。这时候可以通过
patch-package
方式来解决node_modules
带来的问题package.json
添加"postinstall": "patch-package"
node_modules/tsconfig-paths-webpack-plugin/lib/plugin.js
对应位置修改为正确的代码npx patch-package tsconfig-paths-webpack-plugin
The text was updated successfully, but these errors were encountered: