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

Feature request: Async Directives #4982

Closed FranckFreiburger closed 7 years ago

FranckFreiburger commented 7 years ago

Like Components, Directives can help split the application into smaller pieces and reusable functionality. In some situations Directives may become big and it would be convenient to load them when they actually needs to be used, exactly how Async Component does.

posva commented 7 years ago

It's true that it may be intuitive to have async directives as well. Do you have a link to that big directive? I'm curious about what kind of directives are so big that they need to be split up

FranckFreiburger commented 7 years ago

I am about to write an "editable" directive that make an element's content editable.

chogarcia commented 7 years ago

I am pretty sure that you know this already, but you want to develop something else?

amirrustam commented 7 years ago

Regardless of the size the directive (personally I only have small ones), anything to do more code-splitting would be nice. 😁

LinusBorg commented 7 years ago

If you need an interim solution, I think you can do something like this

// your-directive.js
export default {
  bind() {
    const args  = arguments
    require.ensure([], require => {
      var bind = require('./your-directive-module.js').bind
      bind.apply(null, args)
    }, 'your-directive-bundle')
  },
  unbind() {
    const args  = arguments
    require.ensure([], function (require) {
      var unbind = require('./your-directive-module.js').unbind
      unbind.apply(null, args)
    }, 'your-directive-bundle') // make sure all are added to the same bundle
  },
  // repeat for other hook methods.
}
// your-directive-module.js
//  here you put your big code that you want to have split out.
export bind(el, binding, newVnode, oldVnode) {

}
export unbind(el, binding, newVnode, oldVnode) {

}

then you can import your-diective.js in any component, and the 'real' code should be lazy-loaded and run when bind() is executed for the first time...

// in some component
import YourDirective from './your-directive.js'
export default {
  directives: {
    'your-directive': YourDirective
  }
  // other component code
}

Disclaimer this is untested.

githoniel commented 7 years ago

i need this too~~~~register async directives

githoniel commented 7 years ago

i have a directives for input,it will render a keyboard under it,and the keyboard component support number/alphabet/symbols input, i think it is big enough

LinusBorg commented 7 years ago

This feature request has been open for quite some time now, and hasn't gotten much traction so far. I also haven't come across a wish for this functionality in other places (forum, SO, chat...) so I'm not sure if there is a large enough use case for this.

In my personal experience, directives tend to be rather small ever since their functionality and scope was reduced in Vue 2.0. I therefore tend to close this request, but we can re-open if enough people express a need for this.

pqt commented 7 years ago

Just want to chime in that this would have been a great addition for where I'm currently at, but the solution provided above may be a good option as well. Haven't tried the approach yet.

gbaldeck commented 6 years ago

I think this needs to be re-opened. Especially since PWA's are the future and incremental loading of your code is a must.

LinusBorg commented 6 years ago

Especially since PWA's are the future and incremental loading of your code is a must.

That sounds nice and catchy. But I still haven't seen any important usecases for directives that are so big that they codesplitting them into their own chunk would be useful. They are surely not meant to be that big.

If you have big dependencies that you import inside of your deirective, those could of course be codesplit with import() quite easily today.

So I don't see a reason for adding code to the codebase for something nobody provided an acutal usecase for.

"It would be nice" it not a good enough reason.

gbaldeck commented 6 years ago

I have a situation right now where I need it. I am writing a wrapper for Vue in Kotlin and I have the shell in JS and then the rest of the application in Kotlin. Most of the app will be written using the Kotlin wrapper and be in its own chunk that is loaded after the shell. Honestly if I was using JS only I would do it this way too.

LinusBorg commented 6 years ago

Sounds exciting, but I don't understand the usecase for asynchronous directives - I don't know Kotlin, but I hope that's not necessary to understand it.

What do you do in directives that's so big the should be codesplit?

gbaldeck commented 6 years ago

It's not that the directives are so big. It's that their definition will be included in the app chunk along with Kotlin which is loaded after the shell. While the directive declaration will be in the shell chunk because that is where the Vue instance is instantiated. So the declaration and definition will be in two different chunks.

The shell will be downloaded quickly, show the basic UI and a spinner or two, then when the rest of the app is loaded the spinners disappear and show the content as well as the rest of the UI that makes the app fully functional.

In the shell chunk I'm able to asynchronously load the VueComponents defined in the app chunk by creating a communication layer between the two chunks and following the instructions for asynchronous Components here: https://vuejs.org/v2/guide/components.html#Async-Components.

I already have that working with Components. What I'm looking for is the same type of asynchronous loading for directives so that I can include them in my app chunk, which is the only place they will be used anyway.

I actually may have thought of a solution already since directives don't need a render function. I'll be testing it out today but it would be nice if Vue had a built in way of doing. And if my idea doesn't work then I feel that Vue definitely needs a built in way of doing it.

Edit:

So my idea did work. I create a plain empty js object in the shell, pass it to the directive definition (Vue.directive) which is also in the shell, then pass it to my communicator that communicates between the shell and app chunks.

When the app chunk finishes loading it creates its own directive definition and passes it to the communicator. The communicator matches the two up based on the directive name and concatenates the directive definition object in the app chunk on the empty object from the shell chunk, which has already been registered using Vue.directive in the shell.

And there we go, asynchronous directive definition loading. Everything works as it should.

Here's my github repo where I have it working: https://github.com/gbaldeck/vue.kt

f3oall commented 5 years ago

Hey guys,

I think async directives can be useful when you're building very small SPAs, where time to interactive is very important.

For example, I made order form not long ago. In my case source code size in parsed state is about 97KB. And I used v-tooltip library for showing tooltips (56kb parsed size). This library gives you an opportunity to add 'v-tooltip' directives.

So as you can see the library takes almost same size as my source code. It would be great if I could make it as separate chunk and load asynchronously. But since Vue doesn't support async load of directives I can't do that.

PS. I'm sure that I could find more lightweight alternative for the library. But it's not cancel that sometimes you need to place directives in separate chunks.

Kolobok12309 commented 3 years ago

Small helper, for use async load directives

const DIRECTIVE_HOOKS = ['bind', 'inserted', 'update', 'componentUpdated', 'unbind'];

const lazyDirective = (directivePromiseFactory) => {
  let promise;

  return DIRECTIVE_HOOKS.reduce((acc, hook) => {
    acc[hook] = async (...args) => {
      if (!promise) promise = directivePromiseFactory()

      let directive = await promise;

      if (directive.default) directive = directive.default;
      if (!directive[hook]) return;

      return directive[hook].apply(null, args);
    };

    return acc;
  }, {});
};

And usage with v-tooltip

Vue.directive('tooltip', lazyDirective(() => import('v-tooltip').then(({ VTooltip }) => VTooltip)));