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

Vite生产构建浏览器运行时错误:无法解构null的'basename'属性

问题分析:React组件包生产构建后react-router-dom上下文为空错误

问题背景

我用Rollup构建了一个依赖react-router-dom的React组件包,已将react-router-dom设为peer依赖并加入Rollup配置的external项。依赖该组件包的UI包采用Vite构建,开发模式运行正常,生产构建可成功完成,但浏览器加载时出现运行时错误。

运行时错误信息

TypeError: Cannot destructure property 'basename' of 'reactExports.useContext(...)' as it is null.
    at index.js:11:2890
    at Nh (react-dom.production.min.js:160:137)
    at Yi (react-dom.production.min.js:192:57)
    at Vk (react-dom.production.min.js:293:275)
    at Uk (react-dom.production.min.js:279:389)
    at Tk (react-dom.production.min.js:279:320)
    at Ik (react-dom.production.min.js:279:180)
    at Nk (react-dom.production.min.js:270:88)
    at Ek (react-dom.production.min.js:272:300)
    at jg (react-dom.production.min.js:127:105)

相关代码片段

UI包的App.tsx

import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { AppContainer } from "app-libs-ui/components";
import { store } from "app-libs-ui/redux";
import { UserService } from "app-libs-ui/services";

import "@flaticon/flaticon-uicons/css/all/all.css";

import './index.css';

import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux'

// Custom imports:
import { SideBarSections } from "./model/custom/sidebar/SideBarSections.js";
import { typeSpecs } from "./model/custom/index.js";
import { categorySpecs } from "./model/custom/index.js";
// End Custom imports

const appContainer = document.getElementById('app');
const root = createRoot(appContainer!);


export const renderApp = () => root.render(
    <Router>
        <Provider store={store}>
            <AppContainer 
                typeSpecs={typeSpecs}
                categorySpecs={categorySpecs}
                sideBarSections={SideBarSections}
            />
        </Provider>
    </Router>
);

UserService.initKeycloak(renderApp);

UI包的vite.config.js

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
    root: 'src',
    build: {
        outDir: '../build',
        assetsDir: '',
        sourcemap: true,
        emptyOutDir: true,
        commonjsOptions: {
            exclude: [
                "common-models-ts",
                "app-libs-ui",
                "app-libs-ui/*",
                "app-models-specification-json",
                "app-models-specification-json/*",
                "app-models-specification-ts",
                "app-models-specification-ts/*",
                "onchaindata-models-specification-ts",
                "onchaindata-models-specification-ts/*",
            ]
        },
    },
    plugins: [
        react(),
    ],
    resolve: {
        alias: {
            'react-router-dom': path.resolve(__dirname, 'node_modules/react-router-dom'),
        }
    },
    optimizeDeps: {
        include: [
            "react-router-dom",
            "common-models-ts",
            "app-libs-ui",
            "app-models-specification-ts",
            "onchaindata-models-specification-ts",
        ]
    },
    publicDir: 'assets',
    server: {
        hmr: {
            clientPort: 443,
            overlay: false,
        }
    },
    esbuild: {
        logOverride: { 'this-is-undefined-in-esm': 'silent' }
    }
});

组件包的Rollup配置

import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import json from '@rollup/plugin-json';
import yaml from '@rollup/plugin-yaml';
import fs from 'fs';
import postcss from "rollup-plugin-postcss";
import terser from '@rollup/plugin-terser';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

const extensions = ['.js', '.ts', '.jsx', '.tsx'];

const getFiles = (entry, extensions = [], excludeExtensions = []) => {
    let fileNames = [];
    const dirs = fs.readdirSync(entry);

    dirs.forEach((dir) => {
        const path = `${entry}/${dir}`;

        if (fs.lstatSync(path).isDirectory()) {
            fileNames = [
                ...fileNames,
                ...getFiles(path, extensions, excludeExtensions),
            ];

            return;
        }

        if (!excludeExtensions.some((exclude) => dir.endsWith(exclude))
            && extensions.some((ext) => dir.endsWith(ext))
        ) {
            fileNames.push(path);
        }
    });

    return fileNames;
};


export default {
    input: [
        './src/index.ts',
        './src/index.css',
        ...getFiles('./src/components', extensions),
        ...getFiles('./src/constants', extensions),
        ...getFiles('./src/enums', extensions),
        ...getFiles('./src/errors', extensions),
        ...getFiles('./src/lib', extensions),
        ...getFiles('./src/model', extensions),
        ...getFiles('./src/pages', extensions),
        ...getFiles('./src/redux', extensions),
        ...getFiles('./src/services', extensions),
        ...getFiles('./src/util', extensions),
    ],
    output: {
        dir: 'dist',
        format: 'esm',
        preserveModules: true,
        preserveModulesRoot: 'src',
        sourcemap: true,
    },
    plugins: [
        peerDepsExternal(),
        nodeResolve({ jsnext: true, preferBuiltins: true, browser: true }),
        json(),
        yaml(),
        commonjs(),
        typescript({
            tsconfig: './tsconfig.build.json',
            declaration: true,
            declarationDir: 'dist',
        }),
        postcss(),
        terser(),
    ],
    external: ['react', 'react-dom', 'react-router-dom'],
};

问题原因

核心问题是生产构建时出现了react-router-dom的双实例:组件包和UI包各自加载了一份react-router-dom,导致组件包内的useContext获取的是自身实例的Router上下文,而UI包的Router是另一个实例提供的,上下文无法共享,最终返回null,解构basename时触发错误。

开发模式正常是因为Vite开发环境会强制共享依赖实例,但生产构建的树摇和打包策略,加上你配置的react-router-dom别名,干扰了Vite的依赖解析逻辑,导致组件包没有正确复用UI包的react-router-dom实例。

解决方案

  1. 移除Vite配置中react-router-dom的别名
    直接删除vite.config.js里resolve.alias中的'react-router-dom': path.resolve(__dirname, 'node_modules/react-router-dom'),这个配置会破坏Vite的依赖共享机制。

  2. 确认组件包peerDependencies配置
    检查组件包的package.json,确保react-router-dom在peerDependencies中,且版本范围与UI包兼容,示例:

    "peerDependencies": {
      "react": "^18.x",
      "react-dom": "^18.x",
      "react-router-dom": "^6.x"
    }
    
  3. 优化Vite的commonjsOptions配置
    将commonjsOptions.exclude中的"app-libs-ui"改为正则/app-libs-ui/,确保所有子路径都被排除在CommonJS转换之外:

    commonjsOptions: {
        exclude: [
            /common-models-ts/,
            /app-libs-ui/,
            /app-models-specification-json/,
            /app-models-specification-ts/,
            /onchaindata-models-specification-ts/,
        ]
    }
    
  4. 验证组件包的Rollup配置
    你的Rollup配置已经正确使用peerDepsExternal()和手动声明external,确认组件包dist目录下的文件没有打包react-router-dom代码,所有引用都是import形式。

  5. 清理缓存重新构建

    • 删除UI包的node_modules和build目录
    • 删除组件包的dist目录
    • 重新执行npm install安装依赖
    • 先构建组件包,再构建UI包

验证方法

生产构建完成后,通过以下方式确认依赖实例唯一:

  • 打开浏览器Network面板,检查是否只加载了一份react-router-dom资源
  • 在组件包和UI包的代码中分别添加日志console.log(require.resolve('react-router-dom')),查看打印的路径是否一致

内容的提问来源于stack exchange,提问作者Jason Separovic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 02:50:04