从主入口导出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
相关产品推荐
相关产品推荐

