You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

兼容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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 04:35:19