vueuse / vue-demi

🎩 Creates Universal Library for Vue 2 & 3
MIT License
2.99k stars 158 forks source link

Vue3 lib not working in Vue2 projects #152

Open jclaessens97 opened 2 years ago

jclaessens97 commented 2 years ago

First of all, I saw a couple of issues that involved more or less the same issue, but I tought it would be useful to group everything into one issue, so we could hopefully try to solve it.

Related issues:

Problem

I'm currently building a private reusable package for a real-world application. This application runs on nuxt2 (currently nuxt-bridge), and we're planning to migrate to nuxt3 when all the packages we need are there. The idea was to build that reusable package in vue3, and use vue-demi to make it possible to be used in our nuxt-bridge app.

At first I just tried building the library and use it in a separate vue 3 app. With Vite it was really fast & easy to make it work 🎉

However when I tried importing it in our nuxt-bridge app I came across several issues:

1) When externalising vue dependency in vite, I got a list of warnings that some functions are not exported by vue. This is what the most issues are about.

2) When I don't externalise vue in vite.config.js, I just get an empty component. In my repro I don't get any warning. I just see an anonymous component in my dev tools. In my production nuxt-app, I see the following error: no template or render function is defined. The component seemed to be imported correctly, it just doesn't render any HTML.

3) I saw in other issues that the solution might be to externalise vue-demi as well, but that doesn't seem to make any difference for this.

4) also tried using npx vue-demi-fix && npx vue-demi-switch 2 commands, but these also doesn't seem to be changing a whole lot.

I also saw that in #117 , @koooge researched into the issue a bit and saw that it might be due to a breaking change in @vue/core.

That's when I decided to build a repro with a vue2 app (@vue/cli), vue3 app (vite) and a simple component in a separate lib.

Repro

https://github.com/jclaessens97/vue-demi-vue2-broken-repro

I hope we can look into this issue and try to come up with a solution to be able to make vue3 components compatible for vue2, until everything is vue3 ready.

jclaessens97 commented 2 years ago

@antfu do you maybe have any pointers on where the issue may be? Don't want to ping you unnecessarily but I'm kinda lost at this point 😅

jclaessens97 commented 2 years ago

@LinusBorg answered me on Discord saying the following:

image

As said, might be useful to have in the README.md to avoid future issues about this topic 😄 I'm coming back on it on Tuesday.

KaygNas commented 2 years ago

@LinusBorg answered me on Discord saying the following:

image

As said, might be useful to have in the README.md to avoid future issues about this topic 😄 I'm coming back on it on Tuesday.

It might not be correct that "vue-demi is not for vue2/3 component", you can use render function to make the component work for both vue2/3.

import {h,defineComponent} from 'vue-demi'

export default defineComponent({
  //...
  render(){
    return h('div', 'hello')
  }
})

In your situation, your component is a sfc, and sfc need compiler to complie to js, where vue-demi doesn't involve in, also there are gap between compiler of vue2/3, that's the reason why the lib you built don't work in vue2.

My work around is build two dist separately for vue2/3, here is the template repo, hope it will help.

GUGIG commented 2 years ago

you can use render function to make the component work for both vue2/3.

I spent some time with vue-demi to make a tiny vue component library and managed to make it compatiblie with both vue 2 and 3 by manually fixing the build result, which is not ideal.

It seems like when building the library to production via vite(rollup), the bundler is replacing "vue-demi" that I wrote in my component file(.ts) with "vue", and removing isVue2 conditional statements, which, to me, is not understandable.

Initially I wrote my component library w/ render function(h) like you mentioned @KaygNas , and compiled it with vite(rollup). But in the build result files, vue 3 APIs were being imported from "vue" instead of "vue-demi", which caused the incompatibility when being used in vue 2 app.

I manually fixed the build files and managed to make it compatible w/ vue2 & 3 1) replaced "vue" with "vue-demi" 2) used "isVue2" to check if the end user(app) is vue 2 or vue 3, and wrote two render functions

// /lib/my-component.es.js
import { defineComponent, ref, isVue2, /*...*/ } from "vue-demi"
//...
const vm = getCurrentInstance();
const h$1 = h.bind(vm);

if(isVue2) {
  return h$1(/*compatible w/ vue2 syntax*/)
} else {
  return h$1(/*compatible w/ vue3 syntax*/)
}

When I write "vue-demi" inside my component directly (not in the build file), vite replaces it with "vue" during build..

And when I try to write the conditional statement directly inside my component(not in the build file), vite makes them go away and only leaves vue3-relative codes inside the resulting build file(this is probably because I initially setup my library project with vue 3).

The simple example(use-mouse) vue-demi gives us seems like only utilizes tsc when building? which seems to be the reason why it works because the tsc doesn't try to replace "vue-demi".

If there is a way to stop replacing "vue-demi" with "vue" and removing conditional statements such as the one i wrote above, i think this problem would be solved.

KaygNas commented 2 years ago

you can use render function to make the component work for both vue2/3.

I spent some time with vue-demi to make a tiny vue component library and managed to make it compatiblie with both vue 2 and 3 by manually fixing the build result, which is not ideal.

It seems like when building the library to production via vite(rollup), the bundler is replacing "vue-demi" that I wrote in my component file(.ts) with "vue", and removing isVue2 conditional statements, which, to me, is not understandable.

Initially I wrote my component library w/ render function(h) like you mentioned @KaygNas , and compiled it with vite(rollup). But in the build result files, vue 3 APIs were being imported from "vue" instead of "vue-demi", which caused the incompatibility when being used in vue 2 app.

I manually fixed the build files and managed to make it compatible w/ vue2 & 3

  1. replaced "vue" with "vue-demi"
  2. used "isVue2" to check if the end user(app) is vue 2 or vue 3, and wrote two render functions
// /lib/my-component.es.js
import { defineComponent, ref, isVue2, /*...*/ } from "vue-demi"
//...
const vm = getCurrentInstance();
const h$1 = h.bind(vm);

if(isVue2) {
  return h$1(/*compatible w/ vue2 syntax*/)
} else {
  return h$1(/*compatible w/ vue3 syntax*/)
}

When I write "vue-demi" inside my component directly (not in the build file), vite replaces it with "vue" during build..

And when I try to write the conditional statement directly inside my component(not in the build file), vite makes them go away and only leaves vue3-relative codes inside the resulting build file(this is probably because I initially setup my library project with vue 3).

The simple example(use-mouse) vue-demi gives us seems like only utilizes tsc when building? which seems to be the reason why it works because the tsc doesn't try to replace "vue-demi".

If there is a way to stop replacing "vue-demi" with "vue" and removing conditional statements such as the one i wrote above, i think this problem would be solved.

Do you tell rollup that vue-demi is an external? Try it then rollup might keep the vue-demi import statement untouched. Check the docs here.

GUGIG commented 2 years ago

Do you tell rollup that vue-demi is an external? Try it then rollup might keep the vue-demi import statement untouched. Check the docs here.

This works woderfully! My lack of understanding of how rollup handles external dependencies seems to be the cause of the problem i encountered, apparently 🤦‍♂️

After adding vue-demi as an external in rollup option, everything works as I initially expected! I confirmed that my library works in both vue 2(2.6.14) & vue 3 apps.

I'll try to publish my library package to public npm registry & github maybe in this weekends to share further details of what I've done & leave a link to it's repo in this comment.

To summarize what I've done w/ @KaygNas 's advise

  1. use render function instead of SFC or jsx to write compatible render logics for both vue 2 & vue 3. -> there're some breaking changes in render function's syntax between vue 2 & 3. so either you should a) use isVue2 or isVue3 to write two separate render functions for each compatible versions, b) mix two syntax like below:
    
    // vue 2
    h('div',
    { class: 'box', attrs: { id: 123 } },
    'text inside div tag'
    )

// vue 3 h('div', { class: 'box', id: 123 }, // flattened 'text inside div tag' )

// how i did it.. doesn't seem ideal though but it surprisingly works h('div', { class: 'box', id: 123, attrs: { id: 123 } }, // added attrs property from vue 2 syntax 'text inside div tag' )

look [here](https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#vnode-props-format) to see the breaking change
2. externalize 'vue-demi' when bundling via vite(rollup).
```javascript
// vite.config.js
//...
rollupOptions: {
  external: ['vue-demi'],
  // ...
}
// ...

But this begs another question that is there any reason why there's no mention in the doc about externalizing vue-demi in vite(or rollup) config file in the first place, since it seems like it's rollup's default behavior not to include 'vue-demi' namespace in build result if vue-demi is not specified as the external dependency. May be it's just a common knowledge that I should've known 🤷‍♂️ Or may be it is too certain-buildtool-specific to have a place in the doc? But it would be nice to see a simple section to inform in regards to this.

GUGIG commented 2 years ago

seems like there's already a better solution to the render function's compatibility issue.. check this link

GUGIG commented 2 years ago

I published a public repo & its npm package. repo link It's a demo package to showcase how I managed to make a vue component library compatible with both vue 2 & 3.

GUGIG commented 2 years ago

forgot to actually externalizing "vue-demi" in the public package🤦‍♂️ now it should work as intended