mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt): remove wrapper from client only components (#6165)
Co-authored-by: Daniel Roe <daniel@roe.dev>
This commit is contained in:
parent
e61f58b0f5
commit
2cdaf8065c
@ -1,4 +1,4 @@
|
||||
import { ref, onMounted, defineComponent, createElementBlock, h } from 'vue'
|
||||
import { ref, onMounted, defineComponent, createElementBlock, h, Fragment } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ClientOnly',
|
||||
@ -19,18 +19,38 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
export function createClientOnly (component) {
|
||||
const { setup, render: _render, template: _template } = component
|
||||
if (_render) {
|
||||
// override the component render (non <script setup> component)
|
||||
component.render = (ctx, ...args) => {
|
||||
return ctx.mounted$
|
||||
? h(Fragment, null, [h(_render(ctx, ...args), ctx.$attrs ?? ctx._.attrs)])
|
||||
: h('div', ctx.$attrs ?? ctx._.attrs)
|
||||
}
|
||||
} else if (_template) {
|
||||
// handle runtime-compiler template
|
||||
component.template = `
|
||||
<template v-if="mounted$">${_template}</template>
|
||||
<template v-else><div></div></template>
|
||||
`
|
||||
}
|
||||
return defineComponent({
|
||||
name: 'ClientOnlyWrapper',
|
||||
inheritAttrs: false,
|
||||
setup (_props, { attrs, slots }) {
|
||||
const mounted = ref(false)
|
||||
onMounted(() => { mounted.value = true })
|
||||
return () => {
|
||||
if (mounted.value) {
|
||||
return h(component, attrs, slots)
|
||||
}
|
||||
return h('div', { class: attrs.class, style: attrs.style })
|
||||
...component,
|
||||
setup (props, ctx) {
|
||||
const mounted$ = ref(false)
|
||||
onMounted(() => { mounted$.value = true })
|
||||
|
||||
return Promise.resolve(setup?.(props, ctx) || {})
|
||||
.then((setupState) => {
|
||||
return typeof setupState !== 'function'
|
||||
? { ...setupState, mounted$ }
|
||||
: () => {
|
||||
return mounted$.value
|
||||
// use Fragment to avoid oldChildren is null issue
|
||||
? h(Fragment, null, [h(setupState(props, ctx), ctx.attrs)])
|
||||
: h('div', ctx.attrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ describe('pages', () => {
|
||||
expect(html).toContain('Composable | template: auto imported from ~/components/template.ts')
|
||||
// should import components
|
||||
expect(html).toContain('This is a custom component with a named export.')
|
||||
// should apply attributes to client-only components
|
||||
expect(html).toContain('<div style="color:red;" class="client-only"></div>')
|
||||
// should register global components automatically
|
||||
expect(html).toContain('global component registered automatically')
|
||||
expect(html).toContain('global component via suffix')
|
||||
|
||||
|
17
test/fixtures/basic/components/ClientWrapped.client.vue
vendored
Normal file
17
test/fixtures/basic/components/ClientWrapped.client.vue
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
function exposedFunc () {
|
||||
console.log('ok')
|
||||
}
|
||||
|
||||
defineExpose({ exposedFunc })
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
|
||||
onMounted(() => { console.log('mounted') })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
client-only component
|
||||
</div>
|
||||
</template>
|
6
test/fixtures/basic/pages/index.vue
vendored
6
test/fixtures/basic/pages/index.vue
vendored
@ -16,6 +16,7 @@
|
||||
<CustomComponent />
|
||||
<component :is="`test${'-'.toString()}global`" />
|
||||
<component :is="`with${'-'.toString()}suffix`" />
|
||||
<ClientWrapped ref="clientRef" style="color: red;" class="client-only" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -31,4 +32,9 @@ useHead({
|
||||
|
||||
const foo = useFoo()
|
||||
const bar = useBar()
|
||||
const clientRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
clientRef.value.exposedFunc()
|
||||
})
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user