vike
vike copied to clipboard
Asset Preloading - improve `pageContext._getPageAssets()` DX
Current way of pushing assets to the browser beforer rendering HTML: https://discord.com/channels/815937377888632913/815937377888632916/943509260879405116.
The pageContext._getPageAssets() is an internal API subject to change upon any release; make sure to pin your vite-plugin-ssr version.
We should make it a public API (and rename it to pageContext.getPageAssets()) with improved DX.
Idea:
const pageContextInit = { url, onBeforeAssetInjection }
await renderPage(pageContextInit)
function onBeforeAssetInjection(pageAssets) {
// `pageAssets` contains all the page assets, including these that are not injected by default
pageAssets.forEach(asset => {
if (someCondition) {
asset.inject = false
}
})
pageAssets.filter(asset => asset.inject).forEach(asset => {
res.setHeader("Link", /*...*/)
})
}
Alright, this is the plan so far:
// Defined by vps
type PageAsset = {
href: string, // E.g. `/assets/src/renderer/_default.page.client.tsx.6651e845.js`
type: 'script' | 'style' | 'font' | 'image' | 'video',
mediaType: string, // E.g. `text/javascript`
injectPreloadTag: boolean
}
// Defined by you
function onNewPageAssets(pageAssets: PageAssets) {
pageAssets.forEach(asset => {
if (someCondition) {
asset.injectPreloadTag = false
}
})
pageAssets.filter(asset => asset.type==='style').forEach(asset => {
res.setHeader("Link", /*...*/)
})
})
// The usual server integration
app.get('*', async (req, res, next) => {
const url = req.originalUrl
const pageContextInit = { url, onNewPageAssets }
const pageContext = await renderPage(pageContextInit)
if (!pageContext.httpResponse) return next()
const { body, statusCode, contentType } = pageContext.httpResponse
res.status(statusCode).type(contentType).send(body)
})
Note that vps will call pageContext.onNewPageAssets() more than one time. (Some assets are discovered earlier than others.)
For what it is worth, we are currently using pageContext._getPageAssets().
{
src: '/static/assets/chunk-96ac6e2c.js',
assetType: 'preload',
mediaType: 'text/javascript',
preloadType: 'script'
},
It appears that assetType and preloadType are mixed up. I would expect those values to be switched.
For anyone else, this is how we use these hints:
const earlyHints: string[] = [];
// @see https://github.com/brillout/vite-plugin-ssr/issues/262
// @ts-expect-error Internal API.
const pageAssets = await pageContext._getPageAssets();
// The inverse references to `assetType` and `preloadType` are misnomers in vite-plugin-ssr codebase.
// https://github.com/brillout/vite-plugin-ssr/issues/262#issuecomment-1196616891
for (const pageAsset of pageAssets) {
earlyHints.push(
'<' +
pageAsset.src +
'>; rel="' +
pageAsset.assetType +
'"; as="' +
pageAsset.preloadType +
'"; crossorigin'
);
}
const rawHttpRequest = `HTTP/1.1 103 Early Hints\r\n${earlyHints
.map((earlyHint) => 'Link: ' + earlyHint)
.join('\r\n')}\r\n\r\n`;
if (reply.raw.socket) {
reply.raw.socket.write(rawHttpRequest);
}
if (earlyHints.length) {
void reply.header('Link', earlyHints.join(', '));
}
Thanks for sharing @gajus and, yes, I agree the naming is less than ideal.
https://vite-plugin-ssr.com/preload
Released in 0.4.52.
In case your company is up for it: https://github.com/sponsors/brillout. (Thanks gajus for already sponsoring 💚.)