Skip to content

renatodeleao/teleporte

Repository files navigation

teleporte

Like native Vue‘s built-in <Teleport> component, but different.

Why another vue teleport package

Was trying to refactor some project codebase from portal-vue to built-in Vue‘s <teleport> component and couldn't make it work with TransitionGroup. After a discussion on a Vue-related issue, I realized that it was a limitation due to its implementation.

I decided to keep portal-vue in the project, as I needed the extra features, but there were also some caveats with its implementation and I start to wonder how hard would be to implement a very minimalistic version of a teleport myself.

When should you use this package

This package is the middle-ground between simplicity and power:

  1. Try using built-in Vue‘s <teleport> first. No point in bringing a 3rd party lib into your app when you only need simple teleport capabilities, like sending modals/dropdowns to end of <body>.
  2. Use teleporte if you need good support for in-app dynamic transportation of content, with conditional/deferred rendering of both origin/target; when custom TransitionGroup is required at target level or when provide/inject is heavily used.
  3. Use portal-vue for everything else.

QuirksList

This is not a feature parity conversion, these are the quirks in the other Vue "teleport" implementations that motivated me to build this package in the first place.

Quirks list Vue's <teleport> portal-vue teleporte
Works when target is mounted after teleport origin x⁵ ✔️ ✔️
Teleported content can use provide/inject origin context ✔️ x ✔️
Teleport Target can use <TransitionGroup> x ✔️¹ ✔️¹
use of $parent (TBD) x (TBD)
vue-router view (TBD) x x (will not support)
use of $refs ✔️ ✔️² ✔️³
SSR support ✔️⁴ ✔️⁴ ✔️⁴
Footnotes
  1. Requires usage of target component #default slot bindings and loop over exposed vnodes directly into TransitionGroup default slot
  2. after nextTick (see caveats in docs)
  3. to assert the need for nextTick
  4. Yes with caveats (see SSR Section)
  5. Will be supported without workarounds in 3.5.x minor

Installation & basic usage.

# npm | yarn
pnpm install teleporte

Warning

This package requires vue@^3.2.0 to be installed.

Then Import the components.

<sript setup>
import { TeleportOrigin, TeleportTarget } from 'teleporte'
</script>

<!-- Same API as portal-vue, in fact snippet is from their repo -->
<teleport-origin to="destination">
  <p>This slot content will be rendered wherever the
    <teleport-target> with name 'destination'
    is located.
  </p>
</teleporte>

<teleport-target name="destination">
  <!--
  This component can be located anywhere in your App
  (i.e. right before the </body> tag, good for overlays).
  The slot content of the above teleporte component will be rendered here.
  -->
</teleport-target>

Install as Vue plugin

import { TeleportPlugin } from 'teleporte'
import { createApp } from 'vue'

const app = createApp()
app.use(TeleportPlugin)
// exposes Teleporte and TeleportTarget as global components

Usage with TransitionGroup

<sript setup>
import { Teleporte, TeleporteTarget } from 'teleporte'
</script>

<teleport-origin to="destination">
  <p>This slot content will be rendered wherever the
    <teleport-target> with name 'destination'
    is located.
  </p>
</teleport-origin>

<teleport-target name="destination" #default="teleported">
  <transition-group>
    <component 
      v-for="teleport in teleported"
      :key="teleport.key"
      :is="teleport.component"
    />
  </transition-group>
</teleport-target>

SSR

cross-request-state-pollution

TL;DR we use a singleton pattern for storing teleports state so the module state is preserved on each request which would lead to content duplication. We prevent that within library code.

Hydration mismatches

Since the content will not render on server, you'll get a warning from Vue. The same caveat is present in the other "teleport" implementations so the workarounds are the same:

  1. defer <teleport-origin> mount with a ref at onMounted hook + v-if
  2. Your SSR framework (like nuxt) will probably provide a <client-only> component, so wrap inside it
  3. There are external <client-only> component implementations out there as well or you can roll your own.

See:

Contribute

See the guide at CONTRIBUTING.md

Goals

  1. To be deprecated if Vue‘s <teleport> adds fixes to the current quirks :)
  2. While that does not happen, this project aims to be a just minimalistic enhanced version of <teleport>:
    • bundle size to be kept below around ≈1.2KB gzip
    • No unnecessary features: it should do one thing and do it well, move content between target and origin without quirks.

Credits

  • portal-vue, the main inspiration for this package implementation
  • Vue‘s <teleport>
    quirks for the motivation to do it
  • To all maintainers from the packages in dependencies.

Footnotes

  • Teleporte is the Portuguese word for teleport. I found it curious how similar it was and in my mind the "e" suffix could also mean "enhanced": Teleport enhanced.
  • The original version started as a Vue playground, which I've then translated into this more generic package.