modern client mode behaves differently for including async chunks on initial page load

Version

v2.12.2

Reproduction link

https://github.com/simllll/vue-lazy-hydration/tree/fix/lazy-imports

Steps to reproduce

I just stumbled onto something that looks like a bug, but which is actually kind of a feature request which is possible with this “bug” ;). Sounds weird? Yes, let me explain:

When including async components on a page, they are included in the page assets via script tag immedeitaly on page load. This is actually a problem for things like “lazy-hydration” (https://github.com/maoberlehner/vue-lazy-hydration#advanced) and there is a open PR to solve this (vuejs/vue#9847). While I was looking into this issue, I found out that nuxt does this somehow differently. Even though the VueSSRClientPlugin is used (but some own version of VueSSRServerPLugin..)..I could make the assets load async (and only when needed) in my nuxt project, but as I tried to reproduce this with a little test I came up to the conclusion this is actually a side effect of “modern: client” mode.

When I build my demo app with modern: true:

<script>window.__NUXT__={layout:"default",data:[{}],fetch:[],error:null,serverRendered:true,routePath:"\u002Fintegration"};</script>
<script src="/_nuxt/runtime.js" defer></script>
<script src="/_nuxt/pages/integration.js" defer>
</script><script src="/_nuxt/1.js" defer></script>
<script src="/_nuxt/2.js" defer></script>
<script src="/_nuxt/3.js" defer></script>
<script src="/_nuxt/0.js" defer></script>
<script src="/_nuxt/commons.app.js" defer></script>
<script src="/_nuxt/app.js" defer></script>

The script src includes all my async chunks <script src="/_nuxt/0.js" defer></script>. Similar for modern: false of course, but now with modern: ‘client’, it produces following code:

<script>window.__NUXT__={layout:"default",data:[{}],fetch:[],error:null,serverRendered:true,routePath:"\u002Fintegration"};</script>
<script>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script nomodule src="/_nuxt/legacy-runtime.js" defer></script>
<script type="module" src="/_nuxt/runtime.js" defer></script>
<script nomodule src="/_nuxt/legacy-pages/integration.js" defer></script>
<script type="module" src="/_nuxt/pages/integration.js" defer></script>
<script nomodule src="/_nuxt/legacy-1.js" defer></script>
<script type="module" src="/_nuxt/3.js" defer></script>
<script nomodule src="/_nuxt/legacy-2.js" defer></script>
<script type="module" src="/_nuxt/3.js" defer></script>
<script nomodule src="/_nuxt/legacy-3.js" defer></script>
<script type="module" src="/_nuxt/3.js" defer></script>
<script nomodule src="/_nuxt/legacy-0.js" defer></script>
<script type="module" src="/_nuxt/3.js" defer></script>
<script nomodule src="/_nuxt/legacy-commons.app.js" defer></script>
<script type="module" src="/_nuxt/commons.app.js" defer></script>
<script nomodule src="/_nuxt/legacy-app.js" defer></script>
<script type="module" src="/_nuxt/app.js" defer></script>
  </body>
</html>

Now the html includes some script tags for the chunks, but with a “nomodule” attribute <script nomodule src="/_nuxt/legacy-0.js" defer>. The chunks are not loaded therefore in a “module” supported browser. This is actually what I want to have, therefore super great (y). But unfortunately it still looks kind of a bug, because changing e.g. max chunk size to split up other common chunks, is then broken as the “other” chunks are not loaded in this case.

I also think it should not include “_nuxt/3.js” several times, probably here is the root cause of this bug.

<script type="module" src="/_nuxt/3.js" defer></script>

(is included several times instead of the right asset)

To reproduce this issue, you can check out the git branch https://github.com/simllll/vue-lazy-hydration/tree/fix/lazy-imports (lazy-imports), run npm install in the root and switch to the directory “test/nuxt-integration”. Run “nuxt build” and “nuxt start”. In this setup, the modern mode is set to client and it actually lazy loads the component when they are needed (just scroll down on the demo page and watch the network tab).

What is expected ?

The behavior should be somehow either be

  1. documented
  2. controllable (which would be GREAT also for the non-modern mode)
  3. work the same way as in the modern: true or modern: false way.

I would vote for number 2, or number 1.. number 3 would break my lovely workaround 😉

What is actually happening?

The async chunks are not loaded when modern is set to “client” mode.

This bug report is available on Nuxt community (#c10607)

1 possible answer(s) on “modern client mode behaves differently for including async chunks on initial page load

  1. @simllll Thank you so much for the detailed issue description.

    You’re right, the issue is because of modern and legacy bundle files don’t map very well because code split on asyn components, I’ll try to look into this this weekend, I hope I can figure out a proper way to fix it.

    Thanks for the reporting again.