vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.84k stars 33.68k forks source link

For standalone components - Need a reliable method to get the main Vue object #2339

Closed paulpflug closed 8 years ago

paulpflug commented 8 years ago

Currently I'm using this.$root.constructor within my component to get to Vue

But when using vue-router, this.$root is a VueComponent, so this doesn't work.
Object.getPrototypeOf(Object.getPrototypeOf(this)) would work, but I don't really want to do that, for obvious reasons :smile:

maybe I can have $Vue refering to the main Vue object?

simplesmiler commented 8 years ago

How about var Vue = require('vue');?

paulpflug commented 8 years ago

I thought I might end up with different versions of Vue. I think I can go with peerDependencies, thank you :smile:

azamat-sharapov commented 8 years ago

If you use webpack, it should just refer to 1 version of script, instead of creating new on each require, no?

paulpflug commented 8 years ago

I think one cannot necessarily assume this. When you add a component as a dependency and it has another vue as dependency then I think it should load that instead.

paulpflug commented 8 years ago

Ok, now it is a real problem. When Vue is statically linked, for example from a cdn, I can't get it with require('vue')

Atop of this, when I have Vue additionally installed as a dep I won't even notice that require('vue') points to another instance and produce wired behavior (e.g Vue.util.defineReactive isn't working)

Furthermore it would allow component developers to get rid of the peerDependecy.

azamat-sharapov commented 8 years ago

When Vue is statically linked, for example from a cdn, I can't get it with require('vue')

Obviously.. It's already on window.Vue in that case.

paulpflug commented 8 years ago

so what would be the reliable way?

if (window.Vue != null)
  Vue = window.Vue
else
  Vue = require("vue")

would still add vue to the webpack bundle without necessity or error when no vue is installed

yyx990803 commented 8 years ago

You have to use different packaging strategies targeting different usage.

When distributing it for npm-based bundler usage (e.g. Webpack, browserify), there's no need to pre-compile it, just let the user import the *.vue file directly.

When distributing for use without build tools (e.g. global Vue from CDN), you need to wrap the whole thing as a plugin so that Vue can be passed via Vue.use() or just assume Vue will be available globally.

paulpflug commented 8 years ago

In that case I would target statical linking for my component, I would have to distribute 4 versions

paulpflug commented 8 years ago

I will use this now:

if @$root.construtor? # is Vue instance
  @Vue = @$root.construtor
else # is Vue-Component instance
  @Vue = Object.getPrototypeOf(Object.getPrototypeOf(@$root)).constructor

But I really would appreciate a cleaner solution

yyx990803 commented 8 years ago

Actually, the next version of vue-router will probably use a different api which uses a plain Vue instance as root. Together with Vuex it will look more like:

const root = new Vue({
  el: '#app',
  store: vuexStoreInstance,
  router: vueRouterInstance,
  components: { App }
})
blake-newman commented 8 years ago

+1 for that api change.

yyx990803 commented 8 years ago

I'm looking at this issue again and am wondering what is the reason that you need reference to the global Vue inside a distributed component - can you explain the use case a bit more?

paulpflug commented 8 years ago

In vue-clusterize I need to access Vue.FragmentFactory, Vue.util.createAnchor and Vue.util.defineReactive to workaround the nested slot problem by basically building an own v-for, this can be avoided with the changes I proposed in #2507.

Actually I can't think of another use case.

yyx990803 commented 8 years ago

Cool, seems like a very specific use case then. Since you already have a working (although hacky) solution, and the need can be eliminated by adding API elsewhere, I'm closing this for now.

paulpflug commented 8 years ago

I found another use case: singleton component. For example an overlay which is used behind dialogs or modals. A singleton pattern allows a way better handling of z-index / click events than dealing with multiple instances.

This is my current implementation:

overlay = null
module.exports = (Vue) ->
  overlay ?= new Vue(require('./overlay-component')) # the overlay attaches itself to the body..
  return overlay

So to activate the overlay the calling component needs a ref. to Vue.

I would prefer a cleaner solution, but I see no easy way of adding singleton components to Vue..

paulpflug commented 8 years ago

I have another convincing use-case for a singleton component: toast

I made a mixin as a workaround getVue

so the usage will look something like this:

mixins: [require("vue-mixins/getVue")]
method:
  makeAToast: ->
      toaster = require("vue-toaster")(@getVue())
      toaster.toast({})

I would love to get around the getVue stuff for the user..

paulpflug commented 8 years ago

I think this should be reconsidered for 2.0 because of vm.$set -> Vue.set and vm.$delete -> Vue.delete

yyx990803 commented 8 years ago

The check you mentioned here actually should work, you just need to treat vue as an external when bundling your component: https://webpack.github.io/docs/library-and-externals.html#applications-and-externals

paulpflug commented 8 years ago

nonono - I will stick with my mixin solution. All others are unreliable and error prone.

My point is, with that API change, others will also have the need for the Vue instance and this will create some issues in the future.. (because all other solutions are unreliable and error prone :smile:)

yyx990803 commented 8 years ago

Why is what I suggested unreliable?

paulpflug commented 8 years ago

imagine this dependency tree:

require("vue") will give different versions of Vue in myProject and in myComponent - this could be very difficult to debug, especially as a maintainer of myComponent when you have no idea of myProject.

Such tree happens regularly when using npm link.. (But I also stumbled upon components which list Vue as a direct dependency)

this can be avoided by adding

resolve:
  alias:
    "vue": path.resolve("./node_modules/vue")

in the webpack config of the project, but I clearly prefer the mixin to that..

simplesmiler commented 8 years ago

@paulpflug I believe if component specifies vue as a peerDependency, then npm (at least 3.x+) will never install Vue under the component, even when there's a version conflict.

paulpflug commented 8 years ago

sure thing, emphasis is on if I have seen components with vue as direct dependency already..

This is very easy to fix - yes - but very difficult to debug, because there is no error / no warning if you accidentally use the wrong vue instance, it just won't work (I speak for Vue.util.defineReactive).

Regarding peerDependency - I often use npm link on my own repositories which have all devDependencies installed, so peerDependency is ignored and require("vue") will always deliver different instances - so I avoid it completely..

This Issue is not severe, not like I can't live without fixing this. It might be a source for annoying mistakes in the future - we will see.

If I could close it again I would :smile: