Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
feat(nuxt): remove wrapper from client only components (#6165)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Roe <daniel@roe.dev>
  • Loading branch information
huang-julien and danielroe committed Aug 2, 2022
1 parent e61f58b commit 2cdaf80
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 12 deletions.
44 changes: 32 additions & 12 deletions packages/nuxt/src/app/components/client-only.mjs
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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)
}
})
}
})
}
3 changes: 3 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
17 changes: 17 additions & 0 deletions test/fixtures/basic/components/ClientWrapped.client.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions test/fixtures/basic/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -31,4 +32,9 @@ useHead({
const foo = useFoo()
const bar = useBar()
const clientRef = ref()
onMounted(() => {
clientRef.value.exposedFunc()
})
</script>

0 comments on commit 2cdaf80

Please sign in to comment.