兼容React-Router v5/v6的TS库在Webpack环境导出报错
问题:React-Router多版本兼容库在Webpack+SWC环境下报错
背景
- 开发基于React-Router的TS库,上层已存在Router Provider
- 将
react-router-dom设为peerDependencies,版本要求>=5以支持多版本 - 通过
import * as RR from 'react-router-dom'实现v5/v6钩子兼容,核心代码:
import * as RR from 'react-router-dom'; export function useNavigate() { const location = RR.useLocation(); // v6 // eslint-disable-next-line react-hooks/rules-of-hooks const navigateV6 = RR.useNavigate && RR.useNavigate(); // v5 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // eslint-disable-next-line react-hooks/rules-of-hooks const history = RR.useHistory && RR.useHistory(); return navigateV6 || history.replace; }
- 使用Vite构建,配置如下:
build: { lib: { formats: ['es', 'cjs'], }, rollupOptions: { // External packages that should not be bundled into your library. external: [ 'react', 'react-dom', 'react/jsx-runtime', 'react-router-dom', ], }, },
- package.json的
exports配置:
"exports": { ".": { "import": "./index.mjs", "require": "./index.js" } },
报错情况
基于Vite的消费者可正常使用(无论react-router-dom是v5还是v6),但使用swc-loader的Webpack消费者抛出错误:
ERROR in ../../.yalc/@foo/test/index.mjs 18759:150-163 export 'useHistory' (imported as 'Zt') was not found in 'react-router-dom' (possible exports: ...
注:使用Yalc在消费者项目中测试包
原因分析
问题核心在于SWC与Vite/Rollup的Tree Shaking逻辑差异:
- Rollup/Vite处理ES模块时,会保留
RR.useHistory这类条件判断的动态访问逻辑,不会将其转换为具名导入 - SWC编译ES模块时,会静态分析
import * as RR from 'react-router-dom'中的属性访问(如RR.useHistory),直接将其转换为具名导入(即import { useHistory } from 'react-router-dom') - 当消费者使用react-router-dom v6时,该版本不存在
useHistory导出,因此SWC编译阶段直接抛出导出不存在的错误
解决方案
方案1:用in操作符保护属性访问(推荐)
修改代码,通过in操作符做存在性检查,避免SWC将属性访问识别为具名导入:
import * as RR from 'react-router-dom'; export function useNavigate() { const location = RR.useLocation(); let navigate; // 适配v6 if ('useNavigate' in RR) { // eslint-disable-next-line react-hooks/rules-of-hooks navigate = RR.useNavigate(); } // 适配v5 else if ('useHistory' in RR) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore // eslint-disable-next-line react-hooks/rules-of-hooks const history = RR.useHistory(); navigate = history.replace; } return navigate; }
这种写法会让SWC判定为动态属性访问,不会转换为具名导入,从而保留兼容性逻辑。
方案2:拆分多版本入口(更彻底)
在package.json的exports中为v5和v6分别提供专属入口,让包管理器根据消费者的react-router-dom版本自动选择:
"exports": { ".": { "import": { "react-router-dom@>=6": "./index-v6.mjs", "default": "./index-v5.mjs" }, "require": { "react-router-dom@>=6": "./index-v6.js", "default": "./index-v5.js" } } },
分别编写v5和v6的适配代码,这种方式兼容性最好,但需要维护两套入口逻辑。
方案3:禁用SWC的相关静态分析(不推荐)
在Webpack的SWC配置中禁用module.ignoreDynamicImports或调整Tree Shaking规则,但这种方式会影响消费者项目的整体编译优化,不建议作为通用解决方案。
内容的提问来源于stack exchange,提问作者Nikola
相关产品推荐
相关产品推荐

