vuejs / vue-component-compiler

Compile a single file Vue component into a CommonJS module.
MIT License
342 stars 52 forks source link

Compatibility for more Async / await patterns #55

Closed ch-lukas closed 6 years ago

ch-lukas commented 6 years ago

Would allow you to dynamically imports all your modules.

<template>
  <div class="your html code"></div>  
</template>

<!-- Option 1 -->
<script>
(async () => {
  const { exportName } = await import('importName');

  export default {
    // component details
  };
})();
</script>

<!-- Option 2 -->
<script>
export default (async () => {
  const { exportName } = await import('importName');

  return {
    // component details
  };
})();
</script>
znck commented 6 years ago

What purpose does this API solve for you?

I may be missing something, but I feel you can still import modules asynchronously.


const something = import('something')

export default {
  async beforeCreate () {
     const { exportName } = await something

     this.exportName = exportName
  }
}
ch-lukas commented 6 years ago

For me it helps with simplicity/readability.

Your example, which I also use in my code, is a 2 step process. 1 - store promise import in a temp const and then 2 - when promise is ready update a reactive data value.

Now image you have 10 imports per component = 20 steps of code. IMHO that's a lot of unnecessary fluff.

ch-lukas commented 6 years ago

Any news please?

yyx990803 commented 6 years ago

This doesn't make sense. The exports from a vue file must be synchronous. I'm not convinced by the simplicity/readability argument either, on the contrary I think it makes things more complicated then it should be.

ch-lukas commented 6 years ago

Async / await is the future and makes things a lot less complicated and allows for dynamic loading. At the moment there are a lot of tedious complicated work arounds to support dynamic loading (e.g. like the one above in this comments chain). You can also for example add async to your methods, but this only works for click events and not methods that are needed during rendering. It makes sense, but it up to you to take the good advice.

ch-lukas commented 6 years ago

@yyx990803 - two questions for you?

BTW, I really do like Vue and what you have created and really just have a different view on this one and only point.

  1. How to migrate from actively importing everything (e.g. using import { app } from "something") to lazy / just-in-time dynamic imports ? (to help with bundle size and loading speed)
  2. Adopt an object oriented approach to programming exports ? E.g. move all requirements and imports into the export.

Can this be done with Vuejs?

yyx990803 commented 6 years ago

I'm sorry but what @znck showed looks way cleaner and easier to understand to me than the examples you provided. Components are values, not modules. Async happens inside a component's lifecycle hooks, not where it is defined.

chrisnicola commented 6 years ago

@ch-luke if you want to handle dynamic imports why not load them as @yyx990803 is suggesting, in the lifecycle hooks?

Is that not the purpose of dynamic imports with something like webpack? To do code splitting and lazy load code as needed? I don't see what the point of using a top-level await for a dynamic import. It just means that module will be loaded no matter what, in which case why not just load it synchronously?

ch-lukas commented 6 years ago

@chrisnicola / @znck / @yyx990803 , have you tried the above code snippet in a project before?

It actually doesn't work. Dealing with promises await, by design, does not pause execution so the parser will continue with the rest of the functions / lifecycle stages etc and might get to the point where you used the this.exportName before it has been resolved. Hence it is important to give initial values like in the updated example below:

export default {
  async beforeCreate () {
     this.exportName = () => undefined
     const { exportName } = await import('something')

     this.exportName = exportName
  }
}

But this example still doesn't always work like I am finding out when using it in modals (components methods are firing quicker than the imports can be resolved). Currently, I am having to define the imports in the parent and then calling them from the child component this.$parent.exportName - not really ideal.

How can we constructively take this forward ?

chrisnicola commented 6 years ago

Yes, I understand how async/await and promises work. However, I may not fully understand what you are trying to achieve though.

So to clarify, are you suggesting that VueJS have a way to have a lifecycle hook await something before rendering. In order words, if beforeCreate is async (i.e., returns a promise) it should detect the promise and await it before continuing with the render cycle.

@yyx990803 that's an interesting idea and a problem that I have run into, though I'll admit it isn't particularly hard to work around, but just having some component loading state associated with the promise or other solution depending on the context.

I wonder if lifecycle hooks that worked similar to router guards (https://router.vuejs.org/en/advanced/navigation-guards.html) would be useful?

export default {
  // lifecycle hook that expects a promise

  async awaitCreate() {
     this.exportName = () => undefined
     const { exportName } = await import('something')
     this.exportName = exportName
  }

  // or alternatively passes an explicit continue function argument

  async awaitCreate(continue) {
     this.exportName = () => undefined
     const { exportName } = await import('something')
     this.exportName = exportName
     continue() 
  }
}
yyx990803 commented 6 years ago

@ch-luke the snippet assumes you have exportsName declared as a reactive property in data. That's common practice if you want to trigger updates by setting a value async.

ch-lukas commented 6 years ago

@yyx990803, thanks for your response. Yes took that into account, but the snippet really does not work.

I do see something here. As you mentioned , components are synchronise and introducing async to any part of it will alter the load sequence . You are suggesting using reactivity as a solution, but by design that is a post-fix band aid approach and believe there is a better way.

So I am suggesting we use async/await to keep components synchronise and ensure the load order of the lifecycle stages . E.g.


// About to load the component
if (hasAsync(comp)) {
  // Load stages in order
  await loadMethods()
  await loadBeforeCreated()
  await loadCreated()

  ...

  // Shouldn't we also introduce a asyncMethods tag for non load critical
  // code such as button clicks. This will be lazy loaded and not waited for
  loadAsyncMethods()
} else {
  // Load component normally
}
afwn90cj93201nixr2e1re commented 5 years ago

Is there anyway to make it?