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

从主入口导出msalInstance导致循环调用问题求助

问题:集成@azure/msal-react后添加后端认证逻辑,导致Vite热重载失效并出现循环导入

现象

React+Vite项目原本正常运行,集成微软账号登录后,为后端接口添加令牌认证逻辑(将令牌加入请求头),出现以下问题:

  • 修改代码后无法触发Vite热重载,页面直接全量刷新,且刷新后无法正常工作
  • 控制台报错:
    [hmr] /src/components/App.tsx failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. To debug and break the circular import, you can run vite --debug hmr to log the circular dependency path if a file change triggered it.
    
  • 额外出现警告:
    Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.
    

相关代码

main.tsx(导出msalInstance用于令牌获取)

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';

import App from './App';

// MSAL imports
import {
    PublicClientApplication,
    EventType,
    AuthenticationResult
} from '@azure/msal-browser';
import { msalConfig } from './authConfig';

// 样式导入
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min';
import './assets/fontawesome-pro-6.0.0-web/js/all.min';
import './assets/fontawesome-pro-6.0.0-web/scss/fontawesome.scss';
import 'react-confirm-alert/src/react-confirm-alert.css';
import 'react-toastify/dist/ReactToastify.css';
import 'react-float-menu/dist/react-float-menu.css';
import './assets/styles/App.css';

export const msalInstance = new PublicClientApplication(msalConfig);

msalInstance.initialize().then(() => {
    // 默认选中第一个账号(如果存在)
    if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
        msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
    }

    // 监听多窗口账号状态变化
    msalInstance.enableAccountStorageEvents();

    // 登录成功后设置活跃账号
    msalInstance.addEventCallback((event) => {
        if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
            const payload = event.payload as AuthenticationResult;
            msalInstance.setActiveAccount(payload.account);
        }
    });

    ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
        <React.StrictMode>
            <Router>
                <App pca={msalInstance} />
            </Router>
        </React.StrictMode>
    );
});

Axios实例(导入msalInstance获取令牌)

import axios from 'axios';
import {msalInstance} from '../main';
import {msalConfig} from "../authConfig";

const baseURL = import.meta.env.VITE_BASE_URL;

const axiosInstance = axios.create({
    baseURL: baseURL + '/'
});

async function getToken(): Promise<string> {
    const currentAccount = msalInstance.getActiveAccount();
    const accessTokenRequest = {
        scopes: [import.meta.env.VITE_QUOTES_API_SCOPE],
        account: currentAccount,
    };

    if (currentAccount) {
        if (currentAccount.tenantId == msalConfig.auth.tenantId) {
            const roles = (currentAccount.idTokenClaims as { [key: string]: any }).roles;
            if (roles) {
                const accessTokenResponse = await msalInstance.acquireTokenSilent(accessTokenRequest);
                return `Bearer ${accessTokenResponse.accessToken}`;
            }
        }
        return null;
    }
}

axiosInstance.interceptors.request.use(async (config) => {
    config.headers['request-startTime'] = new Date().getTime();
    // 注释的代码是触发问题的核心逻辑
    // config.headers.Authorization = await getToken();
    return config;
});

axiosInstance.interceptors.response.use((response) => {
    const currentTime = new Date().getTime();
    const startTime = response.config.headers['request-startTime'];
    response.headers['request-duration'] = currentTime - startTime;
    return response;
});

export default axiosInstance;

已尝试方案

  • 移除令牌获取逻辑后,热重载恢复正常
  • 尝试将msalInstance移至App.tsx,导致应用无法加载
  • 参照MSAL官方迁移指南处理令牌获取,未解决问题

解决方案

1. 打破循环依赖:抽离msalInstance到独立文件

创建src/auth/msalInstance.ts,将msalInstance的初始化和导出单独存放:

import { PublicClientApplication } from '@azure/msal-browser';
import { msalConfig } from './authConfig';

export const msalInstance = new PublicClientApplication(msalConfig);

修改main.tsx,从新文件导入msalInstance:

// 替换原有msalInstance定义
import { msalInstance } from './auth/msalInstance';

修改Axios实例文件,同样从新文件导入:

// 替换原有导入路径
import { msalInstance } from '../auth/msalInstance';

2. 修复ReactDOM.createRoot重复调用问题

在main.tsx中保存root实例,避免HMR时重复创建:

let root: ReactDOM.Root | null = null;

msalInstance.initialize().then(() => {
    // ... 原有账号处理逻辑 ...

    const container = document.getElementById('root') as HTMLElement;
    if (!root) {
        root = ReactDOM.createRoot(container);
    }
    root.render(
        <React.StrictMode>
            <Router>
                <App pca={msalInstance} />
            </Router>
        </React.StrictMode>
    );
});

3. 优化令牌获取逻辑(可选)

使用MSAL React提供的useMsal钩子在组件内获取令牌,避免在Axios拦截器中直接依赖msalInstance:

// 在需要调用接口的组件中
import { useMsal } from '@azure/msal-react';
import axiosInstance from './path/to/axiosInstance';

const MyComponent = () => {
    const { instance, accounts } = useMsal();
    const currentAccount = accounts[0];

    const fetchData = async () => {
        if (!currentAccount) return;
        const tokenResponse = await instance.acquireTokenSilent({
            scopes: [import.meta.env.VITE_QUOTES_API_SCOPE],
            account: currentAccount
        });
        const response = await axiosInstance.get('/api/data', {
            headers: { Authorization: `Bearer ${tokenResponse.accessToken}` }
        });
        // 处理接口响应
    };

    return <button onClick={fetchData}>获取数据</button>;
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 03:04:53