withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
46.81k stars 2.49k forks source link

Vercel builds randomly cause all SSR routes to fail #11775

Closed Fryuni closed 2 months ago

Fryuni commented 2 months ago

Astro Info

Astro                    v4.14.2
Node                     v20.15.1
System                   Linux (x64)
Package Manager          pnpm
Output                   hybrid
Adapter                  @astrojs/vercel/serverless
Integrations             @astrojs/starlight
                         @astrojs/tailwind
                         astro-meta-tags

If this issue only occurs in one browser, which browser is a problem?

On the server

Describe the Bug

We generate this apply function that ends up in the entrypoint chunk to set up the Web API globals we use:

function apply() {
  if (!globalThis.crypto) {
    Object.defineProperty(globalThis, "crypto", {
      value: require$$0.webcrypto
    });
  }
  if (!globalThis.File) {
    Object.defineProperty(globalThis, "File", {
      value: buffer.File
    });
  }
}

The SSR manifest goes into its own chunk that has:

function deserializeManifest(serializedManifest) {
  // bunch of stuff
  const key = decodeKey(serializedManifest.key);
  // more stuff
}

const manifest = deserializeManifest(/* the manifest */);

The decodeKey comes from a separate file and is just this:

async function decodeKey(encoded) {
  const bytes = decodeBase64(encoded);
  return crypto.subtle.importKey("raw", bytes, ALGORITHM, true, ["encrypt", "decrypt"]);
}

Neither the manifest chunk nor the chunk defining decodeKey depend on the entrypoint chunk that defines and calls that apply function configuring crypto.

It seems there is some reliance on the ordering of some of the hashes to determine how things get generated that might run the top-level code from the manifest chunk before the top-level code for the entrypoint chunk sets the crypto global. When that happens, calling any SSR route from that build on Vercel results in a 500 status with the following error on the server logs:

Unhandled Rejection: ReferenceError: crypto is not defined
    at decodeKey (file:///var/task/chunks/astro/server_CB4FD5PR.mjs:1561:3)
    at deserializeManifest (file:///var/task/manifest_BB-LzI2u.mjs:323:15)
    at file:///var/task/manifest_BB-LzI2u.mjs:340:18
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:337:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:432:15)
    at async i (/opt/rust/nodejs.js:8:15915)

My guess that this is relying on ordering defined by some hash is because repeatedly building the same code either always works or always breaks, so there doesn't seem to have some randomness. Changing the name of a file (any file) changes the content of the manifest chunk and, therefore, its hash. Testing random names to see the effect it randomly works and stops working.

What's the expected result?

Build should always result in working code

Link to Minimal Reproducible Example

Only reproducible with Vercel prod builds

Participation

matthewp commented 2 months ago

https://github.com/withastro/astro/blob/main/packages/integrations/vercel/src/serverless/entrypoint.ts#L11-L18

applyPolyfills() should be called before the dynamic import. This was happening the Node and Netlify adapters as well and this fixed it. See https://github.com/withastro/adapters/commit/2248bc7edcbe37e4e75f573f88a200c2ba5afbae

Can you submit a PR for this?

matthewp commented 2 months ago

I got it, here: https://github.com/withastro/astro/pull/11783