Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9ab8e4c
chore: update lockfile
edison1105 Apr 1, 2025
b0a6ae2
wip: vapor keepalive
edison1105 Apr 8, 2025
61d6f48
wip: save
edison1105 Apr 9, 2025
dccc47c
wip: refactor
edison1105 Apr 9, 2025
1c5d833
wip: refactor
edison1105 Apr 9, 2025
a103715
test: port tests
edison1105 Apr 9, 2025
c6e3fab
test: port tests
edison1105 Apr 10, 2025
e8320b7
test: port tests
edison1105 Apr 10, 2025
032f46f
test: port tests
edison1105 Apr 10, 2025
e012bc5
chore: refactor
edison1105 Apr 10, 2025
aec438b
chore: update
edison1105 Apr 10, 2025
6c4d22a
wip: vdom interop
edison1105 Apr 11, 2025
eeb6ab8
wip: vdom interop
edison1105 Apr 14, 2025
82ad930
Merge branch 'vapor' into edison/feat/vaporKeepAlive
edison1105 Apr 14, 2025
05f197d
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 14, 2025
6efc7ec
wip: vdom interop
edison1105 Apr 14, 2025
c9de3fb
wip: transform KeepAlive to VaporKeepAlive
edison1105 Apr 14, 2025
3278078
wip: e2e tests
edison1105 Apr 15, 2025
90f23e2
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 15, 2025
252f647
chore: update
edison1105 Apr 15, 2025
2604aeb
wip: e2e tests
edison1105 Apr 15, 2025
2134b87
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 15, 2025
f84427b
chore: avoid using process as function name
edison1105 Apr 18, 2025
a9d1053
chore: Merge branch 'vapor' into edison/feat/vaporKeepAlive
edison1105 Jun 20, 2025
a6a886a
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 20, 2025
789d50c
chore: Merge branch 'minor' into edison/feat/vaporKeepAlive
edison1105 Jul 16, 2025
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
87 changes: 87 additions & 0 deletions packages-private/vapor-e2e-test/__tests__/keepalive.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import path from 'node:path'
import {
E2E_TIMEOUT,
setupPuppeteer,
} from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect'
import sirv from 'sirv'
const { page, html, click, value, enterValue } = setupPuppeteer()

describe('vapor keepalive', () => {
let server: any
const port = '8196'
beforeAll(() => {
server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist')))
.listen(port)
process.on('SIGTERM', () => server && server.close())
})

beforeEach(async () => {
const baseUrl = `http://localhost:${port}/keepalive/`
await page().goto(baseUrl)
await page().waitForSelector('#app')
})

afterAll(() => {
server.close()
})

test(
'render vdom component',
async () => {
const testSelector = '.render-vdom-component'
const btnShow = `${testSelector} .btn-show`
const btnToggle = `${testSelector} .btn-toggle`
const container = `${testSelector} > div`
const inputSelector = `${testSelector} input`

let calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['mounted', 'activated'])

expect(await html(container)).toBe('<input type="text">')
expect(await value(inputSelector)).toBe('vdom')

// change input value
await enterValue(inputSelector, 'changed')
expect(await value(inputSelector)).toBe('changed')

// deactivate
await click(btnToggle)
expect(await html(container)).toBe('')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['deactivated'])

// activate
await click(btnToggle)
expect(await html(container)).toBe('<input type="text">')
expect(await value(inputSelector)).toBe('changed')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['activated'])

// unmount keepalive
await click(btnShow)
expect(await html(container)).toBe('')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['deactivated', 'unmounted'])

// mount keepalive
await click(btnShow)
expect(await html(container)).toBe('<input type="text">')
expect(await value(inputSelector)).toBe('vdom')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['mounted', 'activated'])
},
E2E_TIMEOUT,
)
})
97 changes: 80 additions & 17 deletions packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,31 @@ import {
import connect from 'connect'
import sirv from 'sirv'

describe('vdom / vapor interop', () => {
const { page, click, text, enterValue } = setupPuppeteer()

let server: any
const port = '8193'
beforeAll(() => {
server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist')))
.listen(port)
process.on('SIGTERM', () => server && server.close())
})
const { page, click, html, value, text, enterValue } = setupPuppeteer()

let server: any
const port = '8193'
beforeAll(() => {
server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist')))
.listen(port)
process.on('SIGTERM', () => server && server.close())
})

afterAll(() => {
server.close()
})
afterAll(() => {
server.close()
})

beforeEach(async () => {
const baseUrl = `http://localhost:${port}/interop/`
await page().goto(baseUrl)
await page().waitForSelector('#app')
})

describe('vdom / vapor interop', () => {
test(
'should work',
async () => {
const baseUrl = `http://localhost:${port}/interop/`
await page().goto(baseUrl)

expect(await text('.vapor > h2')).toContain('Vapor component in VDOM')

expect(await text('.vapor-prop')).toContain('hello')
Expand Down Expand Up @@ -81,4 +84,64 @@ describe('vdom / vapor interop', () => {
},
E2E_TIMEOUT,
)

describe('keepalive', () => {
test(
'render vapor component',
async () => {
const testSelector = '.render-vapor-component'
const btnShow = `${testSelector} .btn-show`
const btnToggle = `${testSelector} .btn-toggle`
const container = `${testSelector} > div`
const inputSelector = `${testSelector} input`

let calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['mounted', 'activated'])

expect(await html(container)).toBe('<input type="text">')
expect(await value(inputSelector)).toBe('vapor')

// change input value
await enterValue(inputSelector, 'changed')
expect(await value(inputSelector)).toBe('changed')

// deactivate
await click(btnToggle)
expect(await html(container)).toBe('<!---->')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['deactivated'])

// activate
await click(btnToggle)
expect(await html(container)).toBe('<input type="text">')
expect(await value(inputSelector)).toBe('changed')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['activated'])

// unmount keepalive
await click(btnShow)
expect(await html(container)).toBe('<!---->')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['deactivated', 'unmounted'])

// mount keepalive
await click(btnShow)
expect(await html(container)).toBe('<input type="text">')
expect(await value(inputSelector)).toBe('vapor')
calls = await page().evaluate(() => {
return (window as any).getCalls()
})
expect(calls).toStrictEqual(['mounted', 'activated'])
},
E2E_TIMEOUT,
)
})
})
1 change: 1 addition & 0 deletions packages-private/vapor-e2e-test/index.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<a href="/interop/">VDOM / Vapor interop</a>
<a href="/todomvc/">Vapor TodoMVC</a>
<a href="/keepalive/">Vapor KeepAlive</a>
23 changes: 23 additions & 0 deletions packages-private/vapor-e2e-test/interop/App.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
<script setup lang="ts">
import { ref } from 'vue'
import VaporComp from './VaporComp.vue'
import SimpleVaporComp from './components/SimpleVaporComp.vue'

const msg = ref('hello')
const passSlot = ref(true)

;(window as any).calls = []
;(window as any).getCalls = () => {
const ret = (window as any).calls.slice()
;(window as any).calls = []
return ret
}

const show = ref(true)
const toggle = ref(true)
</script>

<template>
Expand All @@ -19,4 +30,16 @@ const passSlot = ref(true)

<template #test v-if="passSlot">A test slot</template>
</VaporComp>

<!-- keepalive -->
<div class="render-vapor-component">
<button class="btn-show" @click="show = !show">show</button>
<button class="btn-toggle" @click="toggle = !toggle">toggle</button>
<div>
<KeepAlive v-if="show">
<SimpleVaporComp v-if="toggle" />
</KeepAlive>
</div>
</div>
<!-- keepalive end -->
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script vapor>
import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
const msg = ref('vapor')

onMounted(() => {
window.calls.push('mounted')
})
onActivated(() => {
window.calls.push('activated')
})
onDeactivated(() => {
window.calls.push('deactivated')
})
onUnmounted(() => {
window.calls.push('unmounted')
})
</script>
<template>
<input type="text" v-model="msg" />
</template>
26 changes: 26 additions & 0 deletions packages-private/vapor-e2e-test/keepalive/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script vapor>
import { ref } from 'vue'
import VdomComp from './components/VdomComp.vue'

window.calls = []
window.getCalls = () => {
const ret = window.calls.slice()
window.calls = []
return ret
}

const show = ref(true)
const toggle = ref(true)
</script>

<template>
<div class="render-vdom-component">
<button class="btn-show" @click="show = !show">show</button>
<button class="btn-toggle" @click="toggle = !toggle">toggle</button>
<div>
<KeepAlive v-if="show">
<VdomComp v-if="toggle"></VdomComp>
</KeepAlive>
</div>
</div>
</template>
20 changes: 20 additions & 0 deletions packages-private/vapor-e2e-test/keepalive/components/VdomComp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script setup>
import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
const msg = ref('vdom')

onMounted(() => {
window.calls.push('mounted')
})
onActivated(() => {
window.calls.push('activated')
})
onDeactivated(() => {
window.calls.push('deactivated')
})
onUnmounted(() => {
window.calls.push('unmounted')
})
</script>
<template>
<input type="text" v-model="msg" />
</template>
2 changes: 2 additions & 0 deletions packages-private/vapor-e2e-test/keepalive/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script type="module" src="./main.ts"></script>
<div id="app"></div>
4 changes: 4 additions & 0 deletions packages-private/vapor-e2e-test/keepalive/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createVaporApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'

createVaporApp(App).use(vaporInteropPlugin).mount('#app')
1 change: 1 addition & 0 deletions packages-private/vapor-e2e-test/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default defineConfig({
input: {
interop: resolve(import.meta.dirname, 'interop/index.html'),
todomvc: resolve(import.meta.dirname, 'todomvc/index.html'),
keepalive: resolve(import.meta.dirname, 'keepalive/index.html'),
},
},
},
Expand Down
10 changes: 9 additions & 1 deletion packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { genEventHandler } from './event'
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
import { genBlock } from './block'
import { genModelHandler } from './vModel'
import { isBuiltInComponent } from '../utils'

export function genCreateComponent(
operation: CreateComponentIRNode,
Expand Down Expand Up @@ -92,8 +93,15 @@ export function genCreateComponent(
} else if (operation.asset) {
return toValidAssetId(operation.tag, 'component')
} else {
const { tag } = operation
const builtInTag = isBuiltInComponent(tag)
if (builtInTag) {
// @ts-expect-error
helper(builtInTag)
return `_${builtInTag}`
}
return genExpression(
extend(createSimpleExpression(operation.tag, false), { ast: null }),
extend(createSimpleExpression(tag, false), { ast: null }),
context,
)
}
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
type VaporDirectiveNode,
} from '../ir'
import { EMPTY_EXPRESSION } from './utils'
import { findProp } from '../utils'
import { findProp, isBuiltInComponent } from '../utils'

export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(
// the leading comma is intentional so empty string "" is also included
Expand Down Expand Up @@ -122,6 +122,12 @@ function transformComponentElement(
asset = false
}

const builtInTag = isBuiltInComponent(tag)
if (builtInTag) {
tag = builtInTag
asset = false
}

const dotIndex = tag.indexOf('.')
if (dotIndex > 0) {
const ns = resolveSetupReference(tag.slice(0, dotIndex), context)
Expand Down
11 changes: 11 additions & 0 deletions packages/compiler-vapor/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,14 @@ export function getLiteralExpressionValue(
}
return exp.isStatic ? exp.content : null
}

export function isKeepAliveTag(tag: string): boolean {
tag = tag.toLowerCase()
return tag === 'keepalive' || tag === 'vaporkeepalive'
}

export function isBuiltInComponent(tag: string): string | undefined {
if (isKeepAliveTag(tag)) {
return 'VaporKeepAlive'
}
}
Loading