vike icon indicating copy to clipboard operation
vike copied to clipboard

Asset Preloading - improve `pageContext._getPageAssets()` DX

Open brillout opened this issue 3 years ago • 5 comments

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.

brillout avatar Feb 16 '22 14:02 brillout

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", /*...*/)
  })
}

brillout avatar Feb 25 '22 08:02 brillout

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.)

brillout avatar Feb 27 '22 10:02 brillout

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.

gajus avatar Jul 27 '22 11:07 gajus

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(', '));
}

gajus avatar Jul 27 '22 11:07 gajus

Thanks for sharing @gajus and, yes, I agree the naming is less than ideal.

brillout avatar Jul 27 '22 14:07 brillout

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 💚.)

brillout avatar Nov 24 '22 15:11 brillout