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

[✨Feature Request]: Add prefetch option for async chunks #10600

Open
4 tasks done
xsjcTony opened this issue Oct 23, 2022 · 25 comments
Open
4 tasks done

[✨Feature Request]: Add prefetch option for async chunks #10600

xsjcTony opened this issue Oct 23, 2022 · 25 comments

Comments

@xsjcTony
Copy link

Description

English:

Webpack enables developer to prefetch an async chunk using magic comment /* webpackPrefetch: true */ which will make a <link rel="prefetch" src="..."> tag in <head>
Code Splitting | webpack

// LoginButton.jsx
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');

will result in <link rel="prefetch" href="login-modal-chunk.js">

It's nice to have the same feature in Vite, but I currently find no alternative.

I tried adding those <link> tags manually using IntersectionObserver whenever an internal link goes into the user's view that the user is likely to click on, just like what VitePress did. (https://vitejs.dev/ did this)

But the thing is, I'm NOT able to access the hash of the bundled chunk file, hence it's pretty hard to do unless I remove the hash from bundled chunk file (so the filename is predictable) but I don't think it's a good way to do so, as it may result in more problems (naming conflict, etc.).

Thus, I think the only solution is that vite can implement this feature internally.
(maybe an extra feature for something similar to /* webpackPreload: true */ as well which result in <link rel="preload" ...> tag)

I personally think adding prefetch for lazy loading routes is pretty important to UX, but I just can't implement it at the moment.


中文:

Webpack 可以通过 /* webpackPrefetch: true */ 让开发者 prefetch 一些异步模块, 通过 <head> 中的 <link rel="prefetch" src="..."> 实现.
Code Splitting | webpack

// LoginButton.jsx
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');

结果为 <link rel="prefetch" href="login-modal-chunk.js">

我希望 Vite 也有类似的功能, 但是我没有找到相关的内容.

我试着使用 IntersectionObserver 手动添加这些 <link> 标签, 每当有app内部的超链接 (路由跳转之类的) 出现在用户的 viewport 里, 也就是说用户有概率点击这个链接. (就像 VitePress 使用的那样, 使用在了 Vite 的官网上).

但是我无法做到, 因为我没有办法访问打包之后异步模块的 hash, 所以我不知道应该加载的文件名 (确实我也不应该知道, 因为这是在开发中不可预测的). 唯一的解法是在打包时不使用 hash, 但我并不认为这是一个好的选项, 因为他会导致更多的问题 (比如命名冲突).

所以我觉得如果只能让 Vite 开发这个功能 (因为能够获取打包之后的 hash (大概吧?)), 然后让用户通过一些方式使用.
(可能也可以顺便开发一个类似 /* webpackPreload: true */ 的功能, 结果为 <link rel="preload" ...> 标签)

我个人认为针对懒加载路由做 prefetch 是挺重要的, 但是我目前没法实现.


Related: #5818

Suggested solution

Something similar to this

// LoginButton.jsx
import(/* vitePrefetch: true */ './path/to/LoginModal.js');

will result in <link rel="prefetch" href="login-modal-chunk-with-hash.js"> in <head>

Alternative

No response

Additional context

No response

Validations

@bluwy
Copy link
Member

bluwy commented Nov 9, 2022

Currently Vite automatically preloads dynamic imports by default (rel="preload"), which should work better than prefetch in most case. Does this feature currently doesn't work for you? Or if there's a reason you're looking for prefetch specifically?

@xsjcTony
Copy link
Author

xsjcTony commented Nov 9, 2022

I think it doesn't. If you mean modulepreload, it only applies to entry chunks and their direct imports. and preload is very different from prefetch,

where preload is

specifying resources that your page will need very soon, which you want to start loading early in the page lifecycle, before browsers' main rendering machinery kicks in.

and prefetch is

a hint to browsers that the user is likely to need the target resource for future navigations, and therefore the browser can likely improve the user experience by preemptively fetching and caching the resource.

Vite definitely won't prefetch the resources of potential future navigations by users. A use case can be lazying-loading routes, #5818 is a great example.

Also, if you go to Vite | Next Generation Frontend Tooling, and you will fine those in the <head> element:
image
The technical that VitePress uses is adding a prefetch <link> tag dynamically when IntersectionObserver finds a new link that appears in the user's viewport, hence the resources are prefetched.

I'm not sure and have not tried yet to use the same strategy to get the build file name of chunks as VitePress did (which is stored in window.__VP_HASH_MAP__), but I mean it's way too complex to implement that. It would be good if vite provides a native feature that I can specify which lazy loaded route is going to be prefetched during runtime.

@fr0stf0x

This comment was marked as spam.

@bepan
Copy link

bepan commented Jan 28, 2023

Is this feature be implemented?

As @xsjcTony said, prefetch is important because it start to load chunk files only after the page completely loads. It means that start to load the file on browser idle time and not in parallel with the main chunks.

@enjoy-wind
Copy link

@xsjcTony
🧚‍♀️,你是否可以在项目中基于requestIdleCallback和IntersectionObserver及动态import函数,封装prefetch函数,来完成对应需求

@xsjcTony
Copy link
Author

@enjoy-wind Yes I can, and I've already achieved it. The problem is how can I get the hash map, like, I've no idea which js file to prefetch. Is there any way to get over it?

@enjoy-wind
Copy link

enjoy-wind commented Feb 21, 2023

1.定义一个usePrefetch.js

export default (componentName) => {
    const element = useCurrentElement()
    const io = new IntersectionObserver(
        entries => {
            const {isVisible} = entries[0]
            let delay = 500;
            if (!isVisible) {
                delay = 2000
            }
            setTimeout(() => {
                window.requestIdleCallback(() => {
                    requestComponent(componentName)
                    io.disconnect()
                })
            }, delay)

        },
        {
            threshold: [1]
        }
    );
    watchOnce(element, (value) => {
        const target = value.nextElementSibling || value
        io.observe(target);
    })
}

2.定义一个useLoadAsyncComponents.js

//todo期待通过语法糖支持动态参数变量导入
const requestComponent = async (key) => {
  if (key === 'SFMonacoEditor') {
    return import('~/components/special/SMonacoEditor/form/SFMonacoEditor.js')
  }
  if (key === 'BFUpload') {
    return import('~/components/base/BUpload/form/BFUpload')
  }
}

3.使用
usePrefetch('SFMonacoEditor')

@xsjcTony

@xsjcTony
Copy link
Author

@enjoy-wind Yeah it seems like a valid way, appreciate that.

But I may be misleading in my descriptions, what I actually want is to achieve the same thing VitePress did, like, globally fetch every link comes into the viewport.

So basically I still need the hashmap, since it doesn't make sense to use the custom hook everywhere in my code (like whenever there's a new link, I need to use it in the corresponding component)

@enjoy-wind
Copy link

@xsjcTony 是否可以加个微信,我们一起看看怎么解决

@agualis
Copy link

agualis commented Mar 29, 2023

@xsjcTony @enjoy-wind Did you finally implement this solution?

@anderskiaer
Copy link

So basically I still need the hashmap

We use a hack which appears to work (it assumes some internal workings of vite I guess 🙈 ). In order to get the hashed assets of e.g. a dynamic import () => import("./pages/some_page") we utilize that e.g.

(() => import("./pages/some_page")).toString()

gives us a string

()=>vt((()=>import("./index-fcec6b38.js")),["assets/index-fcec6b38.js","assets/index.esm-d8722078.js"])

when running the built application, which can be parsed to get the assets corresponding to the dynamic import (and then prefetch them when suitable).

However - we would very much like to remove this hack and get the hash-map through an official vite supported method.

@enjoy-wind
Copy link

@xsjcTony @enjoy-wind Did you finally implement this solution?
@agualis

通过import.meta.glob获取类似组件的混淆后的hash,然后通过正则命中Key,完成动态导入.

const formComponent = import.meta.glob('~/components/*/*/form/*.(jsx|js|vue)')

const getReg = (key) =>
  new RegExp(`src/components/[^/]*/[^/]*/form/${key}.(jsx|js|vue)$`)

const requestComponent = async (key) => {
  const reg = getReg(key)
  const k = Object.keys(formComponent).find((k) => reg.test(k))
  return formComponent[k]()
}

@DavidRNogueira
Copy link

Any updates on this? I would love to convert from Webpack to Vite but without this, it will be tough.

@vitorbertolucci
Copy link

I found this blogpost that suggests a workaround: https://medium.com/@kiranv07/how-to-prefetch-javascript-bundle-in-a-vite-js-react-app-d38de7fe34fc

But for what I can tell it will just load the splitted bundles in parallel, so the benefits of prefetching are lost. I think this is a very important feature for vite to have, I'm looking forward to see if it will be implemented :).

@xsjcTony
Copy link
Author

Yeah exactly as you said, it's just downloading packages in parallel, even if the link is not in user's viewport that user is not likely going to click it

@niltonxp2
Copy link

niltonxp2 commented Dec 22, 2023

I found this article with an approuche that worked for me!

Insted of this

const Home = React.lazy(() => import(/* webpackPrefetch: true */ "./Home"));

Do this

const homePromise = import("./Home");
const Home = React.lazy(() => homePromise);

Thx [Kiran V](Kiran V)

@nicooprat
Copy link

Here our approach for Vue: blog post & gist

We created a Vue plugin that monkey patch the router-link component:

  • do nothing if user has the saveData setting enabled
  • only load when the link enters the viewport
  • use requestIdleCallback and a queue to prevent freezing the browser

Concerning the OP issue with hashmap, we bypass it by actually loading the page file (the component property of the route, which is just a promise we can call whenever we want); which is a bit too much, but good enough in our case.

It could certainly be improved though!

@xsjcTony xsjcTony changed the title Add prefetch option for async chunks [✨Feature Request]: Add prefetch option for async chunks Dec 31, 2023
@dreambo8563
Copy link

dreambo8563 commented Jan 16, 2024

I create a package to append assets into index.html as prefetch Link.
https://github.com/dreambo8563/vite-plugin-bundle-prefetch
@bepan @xsjcTony @DavidRNogueira @agualis have a try with it , I think it can meet most of Scenario.
I test it on vite@4xxx.

@shinyruo-nmsl
Copy link

if we can get the generated html file during build, then use the regex to match the link we need and add prefetch to this link, can it works ? i haven't tried, this is just a idea

@daviareias
Copy link

daviareias commented Aug 30, 2024

I found this article with an approuche that worked for me!

Insted of this

const Home = React.lazy(() => import(/* webpackPrefetch: true */ "./Home"));

Do this

const homePromise = import("./Home");
const Home = React.lazy(() => homePromise);

Thx [Kiran V](Kiran V)

It seems that this method will still block other components in the same file

The only way I found around this is using something like this to load the component only after the page is loaded, but im not sure if it's a good solution.

Also in this example I assume the component will be needed as soon as the page loads and everything else is loaded.

Example

const fetchComponentOnLoad = new Promise((resolve, _reject) => {
  window.addEventListener("load", () => resolve(import("./components/MyComponent")));
});
const MyComponent = lazy(() => fetchComponentOnLoad);

@cszhjh
Copy link

cszhjh commented Sep 5, 2024

hi~我也需要这个功能, 为此我写了一个Vite Plugin, 它将会分析 import() 中的 /* vitePrefetch: true */, 并将其注入到 index.html
https://github.com/cszhjh/vite-plugin-magic-preloader

@gloompiq
Copy link

gloompiq commented Sep 5, 2024

Is there any development on this? We also need to be able to load packages in parallel and as soon as possible. /* vitePreload: true */ would be the best solution.

@wille
Copy link

wille commented Sep 6, 2024

I built vite-preload which will let you inject and Link headers dynamically based on which dynamic imports was rendered.

With this plugin, there is no need to manually do something, any dynamically imported react component that gets rendered will be preloaded.

@gethari
Copy link

gethari commented Sep 19, 2024

any solution to use it in lit + vite ? The solutions above are for react / vue ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests