1- import { createHash } from "node:crypto" ;
2- import fsp from "node:fs/promises" ;
31import { glob } from "tinyglobby" ;
4- import { dirname , join , resolve } from "pathe" ;
2+ import { join , resolve } from "pathe" ;
53import { joinURL } from "ufo" ;
64import type { Nitro } from "nitro/types" ;
75import { isDirectory , writeFile } from "../_utils/fs.ts" ;
@@ -15,11 +13,9 @@ import { isDirectory, writeFile } from "../_utils/fs.ts";
1513// overwrite an existing file with different content, so the file names embed a
1614// content hash.
1715//
18- // They are served from the reserved `/_vercel/immutable/` path, so we relocate
19- // the client build output out of the public `static/` dir into the top-level
20- // `.vercel/output/_vercel/immutable/` dir and emit a
21- // `.vercel/output/immutable.json` manifest mapping each file to a content hash
22- // (derived from its content-addressed file name).
16+ // The client build emits them under the reserved `_vercel/immutable/` base of
17+ // the public `static/` output. Alongside them we emit a
18+ // `.vercel/output/immutable.json` manifest listing each immutable file.
2319
2420const IMMUTABLE_MANIFEST = "immutable.json" ;
2521
@@ -40,22 +36,21 @@ export async function generateImmutableManifest(nitro: Nitro) {
4036 }
4137
4238 // The client build emits content-addressed assets under the reserved
43- // `_vercel/immutable/` base inside the public `static/` output. Relocate them
44- // to the top-level `.vercel/output/_vercel/immutable/` dir (a sibling of
45- // `static/`), from where Vercel serves them at the absolute
46- // `/_vercel/immutable/` path, independent of the site `baseURL`.
47- const srcDir = join ( nitro . options . output . publicDir , IMMUTABLE_DIR ) ;
48- if ( ! ( await isDirectory ( srcDir ) ) ) {
39+ // `_vercel/immutable/` base of the public `static/` output.
40+ const immutableDir = join ( nitro . options . output . publicDir , IMMUTABLE_DIR ) ;
41+ if ( ! ( await isDirectory ( immutableDir ) ) ) {
4942 return ;
5043 }
51- const destDir = resolve ( nitro . options . output . dir , IMMUTABLE_DIR ) ;
52- await fsp . mkdir ( dirname ( destDir ) , { recursive : true } ) ;
53- await fsp . rename ( srcDir , destDir ) ;
5444
55- const files = await glob ( "**" , { cwd : destDir , absolute : false , dot : true } ) ;
45+ const files = await glob ( "**" , { cwd : immutableDir , absolute : false , dot : true } ) ;
46+
47+ // The file names already embed a long, content-addressed hash, so it is valid
48+ // (per Vercel's Build Output API) to leave the manifest value empty and rely on
49+ // the entropy in the file name. `VERCEL_HASH_SALT` is not used: Vite generates
50+ // the file name hashes and they cannot be salted.
5651 const hashes : Record < string , string > = { } ;
5752 for ( const file of files ) {
58- hashes [ joinURL ( "/" , IMMUTABLE_DIR , file ) ] = ""
53+ hashes [ joinURL ( nitro . options . baseURL , IMMUTABLE_DIR , file ) ] = "" ;
5954 }
6055
6156 const manifest : ImmutableManifest = { version : 1 , hashes } ;
@@ -66,4 +61,3 @@ export async function generateImmutableManifest(nitro: Nitro) {
6661
6762 nitro . logger . withTag ( "vercel" ) . info ( `Generated immutable manifest (${ files . length } files).` ) ;
6863}
69-
0 commit comments