ecycle to match Vue's semantics.
1. Basic Teleport Compilation
The compiler maps the Vue <Teleport> element directly to the Teleport component exported by @vureact/runtime-core. This adapter accepts the same props as the Vue component, ensuring a drop-in replacement.
Source Vue Component:
<template>
<Teleport to="#app-overlay">
<GlobalAlertSystem />
</Teleport>
</template>
Compiled React Output:
import { Teleport } from '@vureact/runtime-core';
import GlobalAlertSystem from './GlobalAlertSystem';
export function AppLayout() {
return (
<div className="layout">
<Teleport to="#app-overlay">
<GlobalAlertSystem />
</Teleport>
</div>
);
}
Architecture Rationale:
- Direct Mapping: The compiler injects the runtime import automatically. Developers do not need to manually manage portal references.
- Selector Resolution: The
to prop accepts CSS selectors or DOM elements. The runtime resolves these against the document, mirroring Vue's behavior.
- Multiple Instances: The adapter supports multiple
<Teleport> components targeting the same container. Content is appended in render order, preserving the sequence defined in the source.
2. Conditional and Deferred Teleportation
Vue's disabled and defer attributes require specific lifecycle handling that React's createPortal does not provide natively. The VuReact runtime implements these features using React hooks.
Source Vue Component:
<template>
<Teleport
to="#dashboard-sidebar"
:disabled="isSidebarCollapsed"
:defer="true"
>
<WidgetPanel />
</Teleport>
</template>
Compiled React Output:
import { Teleport } from '@vureact/runtime-core';
import WidgetPanel from './WidgetPanel';
export function Dashboard() {
const isSidebarCollapsed = useStore(state => state.sidebarCollapsed);
return (
<Teleport
to="#dashboard-sidebar"
disabled={isSidebarCollapsed}
defer
>
<WidgetPanel />
</Teleport>
);
}
Implementation Details:
disabled Logic: When disabled is true, the runtime renders children in the original DOM location instead of the target. This is achieved by conditionally invoking createPortal or returning children directly.
defer Logic: The defer attribute delays teleportation until after the component mounts. The runtime implements this by deferring the portal creation to a useEffect hook, ensuring the target container exists and preventing hydration mismatches.
- Dynamic Switching: Both
to and disabled can change dynamically. The runtime handles updates efficiently, moving DOM nodes or re-rendering in place without full component remounts where possible.
3. Error Handling and Fallbacks
If the target selector does not match any element in the DOM, Vue falls back to rendering in place. VuReact replicates this safety mechanism. The runtime checks for target existence before attempting portal creation. If the target is missing, content renders locally, and a warning may be logged in development mode. This prevents runtime crashes during migration when DOM structures are still being aligned.
Pitfall Guide
Even with automated compilation, developers must understand the underlying mechanics to avoid production issues. The following pitfalls are derived from real-world migration scenarios.
1. Target Container Missing in SSR
Explanation: If the target element (e.g., #app-overlay) is not present in the server-rendered HTML, the runtime may fail to attach the portal during hydration, leading to content disappearing or layout shifts.
Fix: Ensure all teleport targets are rendered in the root template or HTML shell. Use the defer attribute if the target is dynamically generated, but verify SSR compatibility.
2. Event Bubbling Assumptions
Explanation: Developers migrating from React may expect events to bubble through the DOM tree. VuReact preserves Vue's behavior: events bubble through the logical component tree. An event listener on a parent component will catch events from teleported children, even if they are in a different DOM branch.
Fix: Review event handlers after migration. If you rely on DOM-level event delegation, you may need to adjust your approach or use native React portals for specific cases.
3. Dynamic to Switching Flicker
Explanation: Changing the to prop dynamically can cause the teleported content to unmount from the old target and remount in the new one. This may result in visual flicker or loss of internal state if the component does not handle remounting gracefully.
Fix: Stabilize teleport targets where possible. If dynamic targets are required, ensure the teleported component preserves state via context or external stores, and consider CSS-based visibility toggles as an alternative.
4. Overusing defer for Layout Control
Explanation: The defer attribute delays rendering until after mount. While useful for SSR safety, overusing it can cause layout shifts (CLS) as content appears after the initial paint.
Fix: Use defer only when the target container is not available during the initial render. For static targets, omit defer to ensure immediate rendering.
5. Style Scoping Issues
Explanation: Teleported content is rendered outside the parent component's DOM subtree. If the parent uses scoped styles or CSS modules, the teleported content may lose styling context.
Fix: Use global styles, CSS-in-JS, or design systems that do not rely on DOM hierarchy for styling. Ensure teleported components are self-contained regarding styles.
6. Ref Confusion
Explanation: A ref attached to a teleported component points to the component instance, not the DOM node in the portal. Developers may attempt to access DOM properties directly, leading to undefined errors.
Fix: Use ref for component methods and state. If DOM access is required, expose a specific ref from within the teleported component or use forwardRef patterns.
7. Multiple Teleports Ordering
Explanation: When multiple <Teleport> components target the same container, content is appended in render order. If the render order changes due to conditional logic, the visual order in the target container may shift unexpectedly.
Fix: Be explicit about render order. If order matters, consider a single teleport with a list of children or a dedicated portal manager component.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Migrating Vue App to React | VuReact Teleport Adapter | Preserves Vue semantics, zero rewrite of portal logic, maintains event bubbling. | Low (Automated) |
| New React Project | Native createPortal | No external dependencies, full control over portal lifecycle, standard React patterns. | Zero |
| Mixed Vue/React Codebase | VuReact Teleport Adapter | Consistency across frameworks, shared runtime behavior, reduced cognitive load. | Medium (Runtime dependency) |
| Complex Portal State Management | Custom Portal Manager | Fine-grained control over mounting, ordering, and state sharing across portals. | High (Custom dev) |
Configuration Template
To enable VuReact's Teleport compilation, ensure your build configuration includes the VuReact compiler plugin and the runtime dependency.
vite.config.ts Example:
import { defineConfig } from 'vite';
import vureact from '@vureact/compiler';
export default defineConfig({
plugins: [
vureact({
// Enable Teleport compilation
features: {
teleport: true,
},
// Optional: Customize runtime import path
runtime: {
core: '@vureact/runtime-core',
},
}),
],
// Ensure target containers are included in HTML
build: {
rollupOptions: {
input: 'src/main.ts',
},
},
});
Runtime Dependency:
npm install @vureact/runtime-core
Quick Start Guide
- Install Dependencies: Add
@vureact/compiler and @vureact/runtime-core to your project.
- Configure Build Tool: Add the VuReact plugin to your Vite or Webpack configuration as shown in the template.
- Run Migration: Execute the VuReact migration command to convert Vue components. The compiler will automatically transform
<Teleport> elements.
- Verify Output: Inspect the compiled React code to ensure
Teleport imports from @vureact/runtime-core are present and props are mapped correctly.
- Test Portals: Run the application and verify that modals, notifications, and overlays render in the correct DOM locations with proper event handling.