Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"pack": "pnpm build",
"play": "pnpm --filter @vue/devtools-playground-basic dev",
"play:reactivity-graph": "pnpm --filter @vue/devtools-playground-reactivity-graph dev",
"play:vapor": "pnpm --filter @vue/devtools-playground-vapor dev",
"postinstall": "simple-git-hooks",
"release": "bumpp -r",
"release-extension": "pnpm build && node scripts/release-extension.mjs && pnpm zip",
Expand Down Expand Up @@ -74,6 +75,7 @@
"vite-plus": "catalog:",
"vitest": "catalog:",
"vue": "catalog:",
"vue-3.6": "catalog:",
"vue-tsc": "catalog:"
},
"simple-git-hooks": {
Expand Down
3 changes: 3 additions & 0 deletions packages/kit/src/runtime/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ComponentTreeNodeTag,
ComponentTreeNodeSnapshot,
} from '../protocol/messages'
import { isVaporApp } from './vapor'
import type { AppId, AppRef, ComponentId, InstanceRef, RuntimeEvent, VueTypeMap } from './types'

export interface ComponentRecord {
Expand Down Expand Up @@ -40,6 +41,7 @@ export interface AppRecord {
app: AppRef
vueTypes?: VueTypeMap
rootInstance?: InstanceRef
vapor?: boolean
components: Map<ComponentId, ComponentRecord>
createdAt: number
updatedAt: number
Expand Down Expand Up @@ -160,6 +162,7 @@ export class RuntimeRegistry {
app,
vueTypes,
rootInstance,
vapor: isVaporApp(app) || undefined,
components: new Map(),
createdAt: time,
updatedAt: time,
Expand Down
63 changes: 63 additions & 0 deletions packages/kit/src/runtime/vapor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Vue Vapor (3.6+) support. Everything here is duck-typed: the kit must keep
// working with Vue 3.5, so it never imports `@vue/runtime-vapor`.

// Fragment kind bits stored on `fragment.__vf` (runtime-vapor 3.6.0-rc.5).
export const VF = {
FRAGMENT: 1,
DYNAMIC: 2,
SLOT: 4,
FOR: 8,
FOR_BLOCK: 16,
TELEPORT: 32,
INTEROP: 64,
SLOT_OUTLET: 128,
} as const

export interface VaporFragmentLike {
__vf: number
nodes: unknown
anchor?: Node
}

export interface VaporInteropFragmentLike extends VaporFragmentLike {
vnode?: object
}

export interface VaporTeleportFragmentLike extends VaporFragmentLike {
target?: Node | null
placeholder?: Node
}

export function isVaporInstance(value: unknown): boolean {
return isObject(value) && (value as { vapor?: unknown }).vapor === true
}

export function isVaporApp(app: unknown): boolean {
return isObject(app) && (app as { vapor?: unknown }).vapor === true
}

export function isVaporFragment(value: unknown): value is VaporFragmentLike {
return (
isObject(value) && typeof (value as { __vf?: unknown }).__vf === 'number' && 'nodes' in value
)
}

export function isInteropFragment(value: unknown): value is VaporInteropFragmentLike {
return hasFragmentBit(value, VF.INTEROP)
}

export function isTeleportFragment(value: unknown): value is VaporTeleportFragmentLike {
return hasFragmentBit(value, VF.TELEPORT)
}

export function isForFragment(value: unknown): value is VaporFragmentLike {
return hasFragmentBit(value, VF.FOR)
}

function hasFragmentBit(value: unknown, bit: number): boolean {
return isVaporFragment(value) && (value.__vf & bit) !== 0
}

function isObject(value: unknown): value is object {
return value != null && typeof value === 'object'
}
217 changes: 217 additions & 0 deletions plans/vapor-support.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions playground/vapor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark" />
<title>Vapor Playground</title>
<style>
html,
body {
background: #111;
}
</style>
</head>
<body class="min-h-screen bg-secondary color-base">
<main class="grid gap-6 p-6 lg:grid-cols-2">
<section>
<h2 class="section-title">createVaporApp</h2>
<div id="vapor-app"></div>
</section>
<section>
<h2 class="section-title">createApp + vaporInteropPlugin</h2>
<div id="interop-app"></div>
</section>
</main>
<div id="teleport-target" class="fixed right-4 bottom-4"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions playground/vapor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@vue/devtools-playground-vapor",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite dev --host 127.0.0.1 --port 3003",
"preview": "vite preview --host 127.0.0.1 --port 3003"
},
"dependencies": {
"@antfu/design": "catalog:",
"@antfu/utils": "^9.3.0",
"@unocss/reset": "catalog:",
"@unocss/transformer-directives": "^66.10.5",
"vue": "3.6.0-rc.5"
},
"devDependencies": {
"@vitejs/devtools": "catalog:",
"@vitejs/plugin-vue": "catalog:",
"typescript": "catalog:",
"unocss": "catalog:",
"vite": "catalog:",
"vite-plugin-vue-devtools": "workspace:*"
}
}
34 changes: 34 additions & 0 deletions playground/vapor/src/VaporApp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script setup vapor lang="ts">
import { ref } from 'vue'
import ConditionalPanel from './components/ConditionalPanel.vue'
import CounterCard from './components/CounterCard.vue'
import FragmentRoot from './components/FragmentRoot.vue'
import KeepAliveTabs from './components/KeepAliveTabs.vue'
import SlotFrame from './components/SlotFrame.vue'
import TeleportNotice from './components/TeleportNotice.vue'
import TextOnly from './components/TextOnly.vue'
import TodoList from './components/TodoList.vue'

const total = ref(0)
</script>

<template>
<div>
<p class="mb-3 text-xs color-muted">Total clicks: {{ total }}</p>
<CounterCard label="Primary counter" :step="1" @change="total++" />
<CounterCard label="Step counter" :step="5" @change="total++" />
<TodoList />
<ConditionalPanel />
<SlotFrame title="Slot frame">
<template #default>
<CounterCard label="Counter in slot" :step="2" @change="total++" />
</template>
<template #footer>
<TextOnly />
</template>
</SlotFrame>
<FragmentRoot />
<KeepAliveTabs />
<TeleportNotice />
</div>
</template>
19 changes: 19 additions & 0 deletions playground/vapor/src/components/ConditionalPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup vapor lang="ts">
import { ref } from 'vue'
import CounterCard from './CounterCard.vue'

const visible = ref(true)
</script>

<template>
<div class="card">
<h3 class="card-title">v-if mount / unmount</h3>
<div class="flex items-center gap-2">
<button class="btn-action-sm" @click="visible = !visible">
{{ visible ? 'Hide' : 'Show' }}
</button>
</div>
<CounterCard v-if="visible" label="Conditional counter" :step="1" />
<p v-else class="text-xs color-muted">Counter unmounted</p>
</div>
</template>
31 changes: 31 additions & 0 deletions playground/vapor/src/components/CounterCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup vapor lang="ts">
import { computed, ref } from 'vue'

const props = defineProps<{
label: string
step: number
}>()

const emit = defineEmits<{
change: [value: number]
}>()

const count = ref(0)
const doubled = computed(() => count.value * 2)

function increment() {
count.value += props.step
emit('change', count.value)
}
</script>

<template>
<div class="card">
<h3 class="card-title">{{ label }}</h3>
<div class="flex items-center gap-2">
<button class="btn-action-sm" @click="increment">+{{ step }}</button>
<span>count: {{ count }}</span>
<span class="text-xs color-muted">doubled: {{ doubled }}</span>
</div>
</div>
</template>
11 changes: 11 additions & 0 deletions playground/vapor/src/components/FragmentRoot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup vapor lang="ts">
import { ref } from 'vue'

const label = ref('multi-root component')
</script>

<template>
<h3 class="card-title">Fragment root</h3>
<p class="text-xs color-muted">This {{ label }} has no single root element.</p>
<span class="mb-3 badge-active">fragment</span>
</template>
21 changes: 21 additions & 0 deletions playground/vapor/src/components/KeepAliveTabs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup vapor lang="ts">
import { shallowRef } from 'vue'
import TabAlpha from './TabAlpha.vue'
import TabBeta from './TabBeta.vue'

const tabs = { alpha: TabAlpha, beta: TabBeta }
const current = shallowRef<keyof typeof tabs>('alpha')
</script>

<template>
<div class="card">
<h3 class="card-title">KeepAlive + dynamic component</h3>
<div class="flex items-center gap-2">
<button class="btn-action-sm" @click="current = 'alpha'">Alpha</button>
<button class="btn-action-sm" @click="current = 'beta'">Beta</button>
</div>
<KeepAlive>
<component :is="tabs[current]" />
</KeepAlive>
</div>
</template>
15 changes: 15 additions & 0 deletions playground/vapor/src/components/SlotFrame.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script setup vapor lang="ts">
defineProps<{
title: string
}>()
</script>

<template>
<div class="card">
<h3 class="card-title">{{ title }}</h3>
<slot />
<div class="text-xs color-muted">
<slot name="footer" />
</div>
</div>
</template>
16 changes: 16 additions & 0 deletions playground/vapor/src/components/TabAlpha.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup vapor lang="ts">
import { ref } from 'vue'

const typed = ref('')
</script>

<template>
<div class="flex items-center gap-2">
<span class="badge-active">alpha</span>
<input
v-model="typed"
class="w-64 px-2 py-1 border border-base rounded bg-transparent text-sm outline-none focus:border-active"
placeholder="State survives tab switches"
/>
</div>
</template>
12 changes: 12 additions & 0 deletions playground/vapor/src/components/TabBeta.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup vapor lang="ts">
import { ref } from 'vue'

const visits = ref(0)
</script>

<template>
<div class="flex items-center gap-2">
<span class="badge-active">beta</span>
<button class="btn-action-sm" @click="visits++">Clicked {{ visits }} times</button>
</div>
</template>
19 changes: 19 additions & 0 deletions playground/vapor/src/components/TeleportNotice.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup vapor lang="ts">
import { ref } from 'vue'

const open = ref(false)
</script>

<template>
<div class="card">
<h3 class="card-title">Teleport</h3>
<div class="flex items-center gap-2">
<button class="btn-action-sm" @click="open = !open">
{{ open ? 'Close' : 'Open' }} notice
</button>
</div>
<Teleport to="#teleport-target">
<div v-if="open" class="card mb-0 shadow-lg">Teleported from a Vapor component</div>
</Teleport>
</div>
</template>
3 changes: 3 additions & 0 deletions playground/vapor/src/components/TextOnly.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script setup vapor lang="ts"></script>

<template>Text-only root component</template>
18 changes: 18 additions & 0 deletions playground/vapor/src/components/TodoItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup vapor lang="ts">
defineProps<{
todo: { id: number; text: string; done: boolean }
}>()

defineEmits<{
toggle: []
remove: []
}>()
</script>

<template>
<div class="flex items-center gap-2">
<input type="checkbox" :checked="todo.done" @change="$emit('toggle')" />
<span>{{ todo.text }}</span>
<button class="btn-action-sm" @click="$emit('remove')">x</button>
</div>
</template>
Loading
Loading