Alternative to confusing async component loading behavior
See original GitHub issueThe 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 toasync: 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
orexcludeAsync
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.
- Make using
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:
- Created 2 years ago
- Reactions:15
- Comments:14 (6 by maintainers)
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 theindex.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 animage
root property in the data of the page component. It is first set inasyncData()
and then updated in themounted()
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 inasyncData()
. The text for the path and the image in the template both render correctly at this point. However, after the image is updated to themountains_02.jpg
file in themounted()
hook, only the text for the path actually changes. The image in the<img>
tag remains the same, and does not update to showmountains_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.I don’t understand why they all have to be async imports in the first place. The file generated for dev looks like this:
But it could just as easily generate static imports for non-lazy components:
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