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.89k stars 33.69k forks source link

vnode reference to original component (reopened) #9034

Open LukasBombach opened 5 years ago

LukasBombach commented 5 years ago

What problem does this feature solve?

Because we have to have lots of ads on our site we cannot hydrate our SSR'd page with VUE completely as the ads would break VUE. Also most of our site ist static and it would be stupid to ship our entire app for only some parts of our page being interactive.

Instead we partially hydrate the page (with our own plugin https://github.com/spring-media/vue-plugin-moisturizer). For this we need to map SSR'd HTML to the corresponding VUE components, so we need to put some data-attribute in the HTML match them.

We use the name setting of a component for this, so if I have component like this:

{
  name: 'my-component'
}

the html would read

<div data-hydration-name="my-component">...</div>

but this does not work for 3rd party components. Anything we get without that ID we cannot hydrate. It would be great if we could find the component from a component's instance / vnode.

There is a cid property, but this is not the same on the server and on the client, because we have a different number and oder of components on the client and server so we cannot use this.

There is a similiar issue for this here: https://github.com/vuejs/vue/issues/7213 but one of your members suggested to create a new issue because the old one would just not show up under your pile of issues.

What does the proposed API look like?

this.$vnode.cuuid // hash based on the filename and path
LinusBorg commented 5 years ago

Hi Lukas,

Nice idea you have going on with your moisturizer plugin!

Unfortunately, I think your proposal isn't really feasable:

this.$vnode.cuuid // hash based on the filename and path

The reasoning is simple:

  1. That id would have to be generated at build time (where we can actually know file paths through nodejs), not runtime (so even if possible it would be a job for vue-loader, or an additional loader, not vue-core).
  2. 3rd party components can and regularly do come bundled in one file (e.g. /dist/3rdPartyComponentCollection.umd.js).
  3. So if you have a 3rd party lib that exports a bunch of components, you possibly would end up with duplicate cuuid values.

That means that even if we can make it work, you will still end up not being able to identify 3rd party components reliably as soon as more than one comes bundled in a single file.

Alternative Approaches

Have you thought about defining a name for the 3rd party components that you use yourself?

Since this code seems to indicate that you know and control the collection of components you intend to "moisturize" this way, that seems like a viable path to take.

There's nothing wrong with doing this:

import ThirdPartyComp1 from 'thirdparty-collection'

export default {
   ...ThirdPartyComp1,
   name: 'YourDesiredName'
})

// or even just:

ThirdPartyComp1.name = 'YourDesiredName'
export default ThirdPartyComp1

The above example expects ThirdPartyComp1 to be defined as an options object, but a similar thing could be done to a component that's distributed as a constructor already.

And extending this thought you could even use something other than the name property (you can set any custom options on the options object, so you could add a moisturizeId to every component that you want to), which means you can leave the name untouched.

LukasBombach commented 5 years ago

Ah ok, I understand that this cannot be done by filename / path. We could attach names to each component manually, that would probably work, thank you! But that also means overhead for the developer. Particularly we are loading SVGs with the vue-svg-loader and it'd be quite annoying to give a name to each SVG. Still, this can be a fallback-scenario for us.

I am currently seeing into ways to fingerprint components, like this:

function fingerprint(component) {
  if (component.name) return md5(component.name); // Components with a name
  if (component.render) return md5(component.render.toString()); // Functional components
};

What do you think about that and do you see other attributes that can be used to fingerprint a component?

LukasBombach commented 5 years ago

Bonus question: Functional components have a render function. Compiled components have a render function. How do I get the render function for a component that has not been compiled yet. And how can I compile it to get the render function?

LukasBombach commented 5 years ago

Bonus question 2: I found this in the debugger on the $vnode:

{
  "ssrContext": {
    "_registeredComponents": [
      "592f84f2", "cb0b8b00", "5ece1878", /* ... */
    ]
  }
}

Seems to me like a mal op components that the SSR module knows about. Can this be made use of?