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

feat(nuxt): remove wrapper from client only components #6165

Merged
merged 23 commits into from
Aug 2, 2022

Conversation

huang-julien
Copy link
Member

@huang-julien huang-julien commented Jul 26, 2022

πŸ”— Linked issue

resolve nuxt/nuxt#14310

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

Before, the createClientOnly returned a new component wrapping the component with ".client" in its name. So when we were setting a ref on <HelloWorld ref="helloworld" /> the ref assigned was on the <ClientOnly /> component but not on <HelloWorld />.

- App
  - ClientOnly (has the ref)
    - HelloWorld.client

This PR make createClientOnly() modifying the component instead of wrapping it. The setup hook return an additionnal mounted ref() . It also override the render() function to conditionally render the component if mounted value is set to true

- App
  - HelloWorld.client (has the ref)

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@netlify
Copy link

netlify bot commented Jul 26, 2022

βœ… Deploy Preview for nuxt3-docs canceled.

Name Link
πŸ”¨ Latest commit 4d400c7
πŸ” Latest deploy log https://app.netlify.com/sites/nuxt3-docs/deploys/62e931287218c50009d5a6ae

@huang-julien huang-julien changed the title fix(nuxt): make client only redefine the component instead of wrappin… fix(nuxt): make createClientOnly redefine the component instead of wrappin… Jul 26, 2022
@huang-julien huang-julien marked this pull request as draft July 26, 2022 21:57
@huang-julien
Copy link
Member Author

huang-julien commented Jul 26, 2022

@danielroe it should be good now, i've tested on a component with a <script setup> and on a component with a normal <script>.

<template>
  <div>
    hellow world
    <button @click="add">
      {{ count }}
    </button>
  </div>
</template>

<script setup lang="ts">
const count = ref(0)

const add = () => count.value++

defineExpose({ add, count })
</script>
<template>
  <div>
    hellow world
    <button @click="add">
      {{ count }}
    </button>
  </div>
</template>

<script  lang="ts">

export default {
  setup () {
    const count = ref(0)

    const add = () => count.value++
    return {
      count, add
    }
  }
}
</script>

@huang-julien huang-julien marked this pull request as ready for review July 26, 2022 23:58
Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice fix ❀️

I've made a couple of minor changes - feel free to check and let me know if you agree.

@danielroe danielroe added bug Something isn't working πŸ”¨ p3-minor-bug Priority 3: a bug in an edge case that only affects very specific usage labels Jul 27, 2022
@danielroe danielroe requested a review from pi0 July 27, 2022 10:53
@huang-julien
Copy link
Member Author

@danielroe only the setup state was missing, which was the "res". That mean we don't even have to use a collision-resistant name

@danielroe danielroe self-requested a review July 27, 2022 22:29
@danielroe
Copy link
Member

danielroe commented Jul 28, 2022

Would you take a look at stateful use, and test on both build + dev mode? It's not quite there yet, and I wonder if we may have to migrate back to using a render function in some cases.

Modify the example component ClientWrapped.client.vue to:

<script setup lang="ts">
function exposedFunc () {
  console.log('ok')
}

const state = ref('component')
defineExpose({ exposedFunc })

await new Promise(resolve => setTimeout(resolve, 300))

onMounted(() => { console.log('mounted') })
</script>

<template>
  <div>
    <!-- this can be commented in/out to trigger different compile approaches -->
    client-only {{ state }}
  </div>
</template>

See SFC playground.

@huang-julien huang-julien marked this pull request as draft July 28, 2022 11:05
@huang-julien
Copy link
Member Author

huang-julien commented Jul 28, 2022

i could fix the build-dev issue, i set this PR to draft to do more testing. There might be some refactor that can be done on the fix. I think moving render() in to the setup() at d3fbb3c is still a better idea πŸ˜ƒ

- must use a Fragment to render the build component to avoid "f is null".
@huang-julien huang-julien marked this pull request as draft July 28, 2022 22:04
@huang-julien
Copy link
Member Author

huang-julien commented Jul 28, 2022

yes, it needs a fix. I've finally understood when does the component have the render function. Only <script setup> SFC are compiled into setup only component. So we need to keep the render function if the the original component have one

@huang-julien
Copy link
Member Author

huang-julien commented Jul 28, 2022

checks

  • modify the component to conditionally render if mounted
  • keep the context/props/setup intact
  • keep original component structure (name, render?, setup)
  • make component ref accessible
  • attrs inheritance before/after mount

done on

  • SFC script setup
  • SFC script
  • .ts/js component (with runtime compiler)
  • developement
  • build

@huang-julien huang-julien marked this pull request as ready for review July 28, 2022 23:36
@danielroe
Copy link
Member

danielroe commented Jul 29, 2022

Currently if users pass class/style attributes the following error is shown:

Extraneous non-props attributes (style, class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

Would you explain more about the oldChildren null issue you unearthed?

@huang-julien
Copy link
Member Author

huang-julien commented Jul 29, 2022

Currently if users pass class/style attributes the following error is shown:

Extraneous non-props attributes (style, class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

Would you explain more about the oldChildren null issue you unearthed?

This was happening only in build, not sure why but at the setup render function, in some components, if we don't wrap the component render here with a Fragment, the component is not being re-rendered when mounted. And in some cases there's a oldChildren is null error triggered by vue in build and dev. I'll try to reproduce it.

@danielroe
Copy link
Member

I'm sure you're looking into it, but just to clarify - we're currently getting attrs applied before mount, but not afterwards.

@huang-julien
Copy link
Member Author

huang-julien commented Jul 29, 2022

here is the reproduction

- set back attribute inheritance for template (runtime-compiler)
- force null on Fragment props
- use h() to render the functions with attrs inheritance
@huang-julien
Copy link
Member Author

I'm sure you're looking into it, but just to clarify - we're currently getting attrs applied before mount, but not afterwards.

yes, just fixed. Sorry for taking so much of your time for a simple PR πŸ™ 😒

Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great - looks good in my testing! Not a simple PR by any means. Well done.

@huang-julien
Copy link
Member Author

huang-julien commented Jul 30, 2022

Currently if users pass class/style attributes the following error is shown:

Extraneous non-props attributes (style, class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

Would you explain more about the oldChildren null issue you unearthed?

This was happening only in build, not sure why but at the setup render function, in some components, if we don't wrap the component render here with a Fragment, the component is not being re-rendered when mounted. And in some cases there's a oldChildren is null error triggered by vue in build and dev. I'll try to reproduce it.

This is likely a Vue issue stackblitz, so using Fragment seems to be good to avoid this

@pi0 pi0 changed the title fix(nuxt): make createClientOnly redefine the component instead of wrappin… feat(nuxt): remove wrapper from client only components Aug 2, 2022
Copy link
Member

@pi0 pi0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

@pi0 pi0 merged commit 2cdaf80 into nuxt:main Aug 2, 2022
@ffxsam
Copy link

ffxsam commented Aug 2, 2022

Thank you for this! @huang-julien @danielroe @pi0

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
3.x bug Something isn't working πŸ”¨ p3-minor-bug Priority 3: a bug in an edge case that only affects very specific usage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

defineExpose doesn't work with client-only SFCs
4 participants