question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Alternative to confusing async component loading behavior

See original GitHub issue

The Problem

In Nuxt components v2, components are dynamically imported with async. This change was a nightmare for me because it result in several issues which took the better part of several days to find the cause of.

Hard to Find Bug Resulting from the Problem

Here is the issue I had created on the Nuxt repository the first time I ran into the problem: https://github.com/nuxt/nuxt.js/issues/8981

The solution required me to write extra code in my custom component because I needed to ensure that $el existed before I could initialize a gsap animation. It was very counter-intuitive for me to not be able to assume that $el was populated inside of the mounted() hook, instead having to write a conditional inside the updated() hook to check if the element was populated each time. Honestly not a great solution in my opinion.

Also, this only broke I after I upgraded to v2 of Nuxt components, and unfortunately the migration guide was of no help to me because it didn’t explain that components being automatically made global also meant they were being imported dynamically. I was totally in the dark on that detail.

Another Different Hard to Find Bug Resulting from the Problem

Today I just ran into another problem which I eventually discovered was resulting from async component loading. This time, my styling broke because I relied on the assumption that an id attribute would be present, but it wasn’t due to this same async component loading. Nothing about this behavior was intuitive to me.

Here is a simple Code Sandbox that shows the second problem in action: https://codesandbox.io/s/boring-bassi-et0fb?file=/layouts/default.vue

What I’d Like to See

Documentation for Effects of Async Component Loading

First, we need a clear part of the documentation that explains the downsides of the async component loading so people don’t spend hours trying to debug the source of weird issues. I imagine that 99% of Vue users expect $el to always exist in the mounted() hook, and id attributes to never disappear without a clear cause. Any behavior that changes this makes for a very dangerous default, in my humble opinion.

Official Support for loader: true

In the issue I linked above, @pi0 revealed that we can set loader: true to re-enable v1 behavior. However, he also stated that this is “not recommended nor guaranteed to always work”. I propose that this solution be made officially supported for those who wish to not lose sleep at night.

Somewhere in nuxt.config.js to Exclude Specific Components from Async Loading

Additionally, provide a way to list components in nuxt.config.js that should not be loaded in an async manner.

Why not just import components manually? Random importing certain components here and there in a codebase to force them to not be loaded async is confusing, because there is not immediately any apparent reason why one component is manually imported and another isn’t, unless clear comments are left. Anyone could remove the component import thinking they are just cleaning things up and end up breaking the application. Also, manually importing components defeats part of what I love about Nuxt components, which is cleaner code due to not having to import them manually.

TL;DR / Summary

  • Clearly document the behavior changes when using async components. Even if this is just a link to some existing Vue documentation for what happens when you use async components, it would be extremely helpful because I’ll bet a lot of more serious users are running into issues resulting from async component loading as a default and scratching their heads.
  • Provide an officially supported way to not use async components. I understand why you guys started using them, but I’ve already run into two situations where they make things harder instead of easier, and I’m willing to bet many others have as well.
    • Make using loader: true officially supported, rename it to async: false or something like that so it’s clear what it does, and document how and when to use it.
    • Provide a property with the name staticComponents or excludeAsync something like that where paths to components or maybe even entire directories of components can be specified to be excluded from being loaded dynamically with async.

Bottom line, let’s not force people to stop using the mounted() hook or manually import components (negating the benefit of Nuxt components) to make initial component state consistent in their applications.

Thanks so much for your hard work on Nuxt, and I appreciate your consideration of my suggestion.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:15
  • Comments:14 (6 by maintainers)

github_iconTop GitHub Comments

3reactions
AaronBeaudoincommented, Jun 4, 2021

It’s frustrating that I need to come back here a 3rd time, but here I am. I just wasted an entire day of work trying to find the source of a problem and it ended up being this exact same cause again.

Please see the below CodeSandbox containing a minimal test case and think about how, in a complex project, you would have figured out what the core problem is. I guarantee a new user starting off with Nuxt would just uninstall and move on to React if they ran into an issue like this right off the bat.

https://codesandbox.io/s/relaxed-hofstadter-4jdoy

Simple Explaination of Issue

In the attached example, a minimal index.vue page includes nothing but a <Test> component as it’s root element. The <Test> component itself contains nothing but a <div> whose contents is a <slot>. Back in the index.vue page, the slot contains a <div> with the text contents of the path of an image, and below it is an <img> element displaying the actual image.

In the JS for the index.vue page, the path to the image is an image root property in the data of the page component. It is first set in asyncData() and then updated in the mounted() hook. Now here is where everything goes wrong:

When the page component first renders, the path is to the mountains_01.jpg file, which was set in asyncData(). The text for the path and the image in the template both render correctly at this point. However, after the image is updated to the mountains_02.jpg file in the mounted() hook, only the text for the path actually changes. The image in the <img> tag remains the same, and does not update to show mountains_02.jpg like it should. Check what both of the images look like in the project tree to confirm this. Also, if the issue does not occur at first, try reloading the page in the preview window.

How would one resolve this? I tried over a dozen ideas over the course of an entire day. In the end, the only way to resolve it was to either use loader: true or ensure all components through the tree up to the <img> element were loaded explicitly. The following is just my opinion, but that’s insane. I am seriously considering ditching Nuxt and the Vue ecosystem altogether for Next and React.

3reactions
KaelWDcommented, May 13, 2021

I don’t understand why they all have to be async imports in the first place. The file generated for dev looks like this:

import Vue from 'vue'
import { wrapFunctional } from './utils'

const components = {
  MyComponent: () => import('../../app/components/MyComponent.vue' /* webpackChunkName: "components/MyComponent" */).then(c => wrapFunctional(c.default || c)),
}

for (const name in components) {
  Vue.component(name, components[name])
  Vue.component('Lazy' + name, components[name])
}

But it could just as easily generate static imports for non-lazy components:

import Vue from 'vue'
import { wrapFunctional } from './utils'

import { MyComponent as _MyComponent } from '../../app/components/MyComponent.vue'

const components = {
  MyComponent: _MyComponent,
  LazyMyComponent: () => import('../../app/components/MyComponent.vue' /* webpackChunkName: "components/MyComponent" */).then(c => wrapFunctional(c.default || c)),
}

for (const name in components) {
  Vue.component(name, components[name])
}

Turns out you already have this anyway in .nuxt/components/index.js but it isn’t used in development??? The dev file could literally just be

import * as components from './index'

for (const name in components) {
  Vue.component(name, components[name])
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Async/Await - Best Practices in Asynchronous Programming
This behavior can be confusing, especially considering that stepping through the debugger implies that it's the await that never completes.
Read more >
How To Handle Async Data Loading, Lazy Loading, and Code ...
In this step, you'll use the useEffect Hook to load asynchronous data into a sample application. You'll use the Hook to prevent unnecessary...
Read more >
Svelte 3, async onMount or a valid alternative? - Stack Overflow
I need that async behavior because I need to wait for function lazyLoading() before rendering the Child component. Is there an alternative way ......
Read more >
Coding Better Composables: Async Without Await (5/5)
This tutorial series will serve as your guide on how to craft solid composables that you and your team can rely on.
Read more >
Async Generators as an alternative to State Management
With Async Generators there is no longer need for components state, state management tools, component lifecycle methods, and even the latest ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found