Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async script output #3163

Closed
DylanPiercey opened this issue Apr 26, 2021 · 5 comments
Closed

Async script output #3163

DylanPiercey opened this issue Apr 26, 2021 · 5 comments
Labels

Comments

@DylanPiercey
Copy link
Contributor

DylanPiercey commented Apr 26, 2021

Clear and concise description of the problem

Currently Vite outputs <script type="module"> scripts which are executed in deferred mode. Although this is a lot better than the blocking default of standard <script> tags, it does have an issue with streaming.

Specifically defer does not execute any scripts until DOMContentLoaded has fired. This means that if you are using SSR + streaming (or even if your document is sufficiently large to be parsed by the browser in multiple segments) that no scripts execute until the browser receives the final byte of html.

This is especially bad with streaming + partial hydration, where you are intentionally are sending down sections of interactive html and would like for it to hydrate ASAP. Instead right now in Vite hydration will be blocked until streaming has finished.

Simply adding async to these scripts causes them to download asynchronously without blocking the rest of the document (as the name implies) similar to defer, however it also allows the scripts to execute as soon as they are downloaded.

Adding async blindly to Vite's output scripts as it stands would cause a race condition for common setups though, since they could be relying on the document being complete by the time initialization code runs, for example:

import { hydrate } from "popular-library";
hydrate(document.getElementById("my-app"), ...);

This does not account for the case where the script has downloaded and is ready, but the #my-app content has not yet been sent by the server.

It would be nice if there was some way to preserve the simple behavior of Vite to support the above without much complexity, while also allowing a way to opt into the async style hydration.

Suggested solution

One potential option could be to support a special handling of <script async type="module" src="..."/> in the .html files. We could perhaps use this to indicate that the chunk being loaded is capable of initializing immediately. I think this might be a bit tricky though since technically you can have multiple scripts in your .html file that would get merged into the same chunk and loaded together.

Alternative

One not so great option would be to have a dynamic import in the .html file like:

<script type="module">
  import("./my-actual-entry");
</script>

This is a bit verbose though and currently does not seem to actually work.
A naive compilation of this might also introduce an additional network waterfall, which trades one perf issue with another.

Additional context

I'm not clear on the solution here but wanted to get the discussion started. Happy to create a PR if we can decide on a direction.

@ryansolid
Copy link

ryansolid commented Apr 26, 2021

Yeah I hit this while working Solid plugin a few weeks back as well. We ended up doing an inline plugin like:

{
      name: "html-async",
      enforce: "post",
      transformIndexHtml(html) {
        return html.replace('type="module"', 'type="module" async');
     }
}

But really any library that wants to take advantage of progressive rendering would want this feature so that scripts can start loading and executing before the document is finished streaming in. Without it progressive rendering(streaming) loses its primary benefit.

@patak-dev
Copy link
Member

One potential option could be to support a special handling of <script async type="module" src="..."/> in the .html files. We could perhaps use this to indicate that the chunk being loaded is capable of initializing immediately.

IMO, this looks like a clean solution.

I think this might be a bit tricky though since technically you can have multiple scripts in your .html file that would get merged into the same chunk and loaded together.

If several scripts end up combined in a single one, we could:

  • if all are async, then the chunk is async
  • if there are some that are not async, revert back to defer

I think it would be great to have the PR, so we can later discuss this feat with Evan with the change set on the table. Thanks for the proposal!

@github-actions
Copy link

Hello @DylanPiercey. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!

@patak-dev
Copy link
Member

@DylanPiercey sorry for the delay. We discussed this enhancement with the team and there was a great reception for it. The API as you suggested looks good to avoid the need for non-standard config or markers. And we can use the heuristic I described in my last comment to define if a chunk can be async or it needs to be referred (it can only be async if all the files in the chunk are marked as async modules).
Would you like to work in a PR for this feature or do you prefer to open it to the community to do it?

@DylanPiercey
Copy link
Contributor Author

I’d be happy to work on it but I’ve got several other things on my plate for the moment. Probably won’t be able to put up a PR for a couple weeks.

@github-actions github-actions bot locked and limited conversation to collaborators Oct 1, 2021
aleclarson pushed a commit to aleclarson/vite that referenced this issue Nov 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants