外部路由器
当 TanStack Router、React Router 或其他路由器已经在你的 shell(宿主应用)中负责导航时,让它继续负责。orchestrator.connectRouter() 让编排器跟随该路由器,interceptLinks() 把微应用中普通 <a href> 的点击交给它处理。
何时使用
- 你的 shell 是一个自带路由器的 React 应用,其中一些路由承载微应用。
- 你的微应用渲染普通链接(Vue、Svelte、Angular 或 HTML 模板),这些链接应在不重新加载页面的情况下导航。
如果 shell 没有自己的路由器,请改用内置路由器,参见路由。
connectRouter
ts
import { createOrchestrator, type RouterSource } from 'tuvix.js';
const source: RouterSource = {
// 每次导航后调用 `callback`;返回取消订阅函数。
subscribe(callback) {
window.addEventListener('popstate', callback);
return () => window.removeEventListener('popstate', callback);
},
// 用于匹配 activeWhen 模式的路径。
getPath: () => window.location.pathname,
};
const orchestrator = createOrchestrator(); // 不传 `router` 选项
const disconnect = orchestrator.connectRouter(source);
await orchestrator.start();
// 之后
disconnect();start()按getPath()执行协调(reconcile)。之后每次subscribe回调都会为新的getPath()排队一次协调。- 连接一个已经启动的编排器会立即执行协调。
- 协调一次只运行一个,下一个只运行最新排队的路径,因此一连串路由器事件对每个稳定下来的路径只产生一次协调。
destroy()断开所有已连接的路由器。
TanStack Router
ts
import { createOrchestrator, interceptLinks } from 'tuvix.js';
import { router } from './router'; // 你的 createRouter({ routeTree }) 实例
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'blog',
entry: () => import('./micro-apps/blog'),
container: '#micro-app',
activeWhen: '/blog/*',
});
orchestrator.connectRouter({
subscribe: (callback) => router.subscribe('onResolved', callback),
getPath: () => router.state.location.pathname,
});
// 微应用中的普通 <a href> → TanStack history。
interceptLinks({ navigate: (path) => router.history.push(path) });
await orchestrator.start();onResolved 在路由器解析完新位置后触发。用它代替 onLoad 加 setTimeout。
React Router
使用 createBrowserRouter 创建的数据路由器时:
ts
import { createOrchestrator, interceptLinks } from 'tuvix.js';
import { router } from './router'; // createBrowserRouter(routes)
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'account',
entry: () => import('./micro-apps/account'),
container: '#micro-app',
activeWhen: '/account/*',
});
orchestrator.connectRouter({
subscribe: (callback) => router.subscribe(() => callback()),
getPath: () => router.state.location.pathname,
});
interceptLinks({ navigate: (path) => router.navigate(path) });
await orchestrator.start();使用 <BrowserRouter> 时没有可订阅的路由器对象。在一个读取 useLocation() 的组件中做桥接:
tsx
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import type { Orchestrator, RouterSource } from 'tuvix.js';
export function TuvixBridge({ orchestrator }: { orchestrator: Orchestrator }) {
const location = useLocation();
const listeners = useRef(new Set<() => void>());
const path = useRef<string>(location.pathname);
path.current = location.pathname;
useEffect(() => {
const source: RouterSource = {
subscribe(callback) {
listeners.current.add(callback);
return () => listeners.current.delete(callback);
},
getPath: () => path.current,
};
return orchestrator.connectRouter(source);
}, [orchestrator]);
useEffect(() => {
for (const callback of listeners.current) callback();
}, [location.pathname]);
return null;
}微应用中的普通链接
你的路由器自带的 <Link> 组件本来就在客户端导航。微应用渲染的普通 <a href> 链接则不会;没有 interceptLinks 时,它们会重新加载页面。interceptLinks 跳过带修饰键的点击、其他源、target="_blank"、download、rel="external"、data-tuvix-reload 以及当前路径上只有 hash 的链接,其余点击都调用你的 navigate。添加 prefetch 可以在悬停或聚焦时预热下一个路由。使用 TanStack Router 时:
ts
import { interceptLinks } from 'tuvix.js';
import { router } from './router';
const stop = interceptLinks({
navigate: (path) => router.history.push(path),
prefetch: (path) => void router.preloadRoute({ to: path }),
});shell 卸载时调用 stop()。
基础路径
路由器设置了 basename(例如 /app)时:
activeWhen模式匹配getPath()返回的原样字符串。如果你的路由器报告的路径带/app,就写'/app/blog/*',或者在getPath中去掉 base。interceptLinks({ base: '/app', ... })忽略/app之外的链接,并在调用navigate前去掉/app。当你的路由器的navigate接收相对于 basename 的路径时使用它(React Router 的router.navigate)。当navigate接收完整 URL 路径时不要设置base(TanStack 的router.history.push)。
由路由渲染的容器
编排器在挂载时查找 container。如果容器元素由某个路由组件渲染,路由器回调触发时它可能还不在 DOM 中,挂载会失败并报错 Container "#micro-app" not found in DOM。有两种解决办法:
- 把容器放在所有微应用路由共用的布局中,让它在导航前就已存在。
- 在路由组件中直接用
TuvixIsland(@tuvix.js/react)渲染微应用,该应用不再交给编排器管理。
connectRouter 不做什么
- 它不负责导航。导航仍由你的路由器负责。
- 它不读取路由参数。请用
updateAppProps把参数作为 props 传入,或在应用内部读取。 - 它不处理链接点击。这件事请调用
interceptLinks。