-
-
Notifications
You must be signed in to change notification settings - Fork 876
presets(vercel): support immutable static files #4432
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
Open
pi0x
wants to merge
31
commits into
main
Choose a base branch
from
feat/vercel-immutable
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
b4cfac8
feat: initial work on vercel immedutable
pi0 4e44a93
rename config to buildAssetsDir
pi0 d0e3a6c
chore: apply automated updates
autofix-ci[bot] adaadc5
change gate
pi0 4472a85
update docs
pi0 73c4b09
support VERCEL_IMMUTABLE_ASSETS env
pi0 624311a
avoid extra hash by default
pi0 c4e8ded
no need to read file
pi0 d2f3916
simplify manifrst creation
pi0 8271343
respect salt
pi0 f2ee1f8
use long vite hashes
pi0 18569bb
Merge branch 'main' into feat/vercel-immutable
pi0 4dcc7d2
rm double slash
pi0 a23d80b
use filename
pi0 c619bd1
simplify manifest creation
pi0 ee4409d
chore: apply automated updates
autofix-ci[bot] 43b5fa8
update docs
pi0 e5e3730
rename options
pi0 3a583f1
use framework name in dir
pi0 8964eca
add basic behavior test
pi0 ced17b8
use salt in test
pi0 eb09b4b
update test
pi0 02ee991
fix: match longer asset hash in ssr
RihanArfan eb30da5
docs: typo
RihanArfan 6d61d11
Merge branch 'main' into feat/vercel-immutable
RihanArfan 835f671
Merge branch 'main' into feat/vercel-immutable
RihanArfan 009f541
refactor: use useLongerAssetHashes with ssr
RihanArfan eb4f0d1
refactor: make immutable opt in
RihanArfan d60f636
fix: apply useLongerAssetHashes to ssr env rather than all non-nitro …
RihanArfan e6b0e71
fix: set immutable assetsDir on all server envs without forcing entry…
RihanArfan 06a2e65
fix(vercel): guard immutable static files and normalize buildAssetsDir
pi0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| import { glob } from "tinyglobby"; | ||
| import { resolve } from "pathe"; | ||
| import { joinURL, withLeadingSlash } from "ufo"; | ||
| import { provider } from "std-env"; | ||
| import type { Nitro } from "nitro/types"; | ||
| import { writeFile } from "../_utils/fs.ts"; | ||
|
|
||
| // Immutable Static Files | ||
| // | ||
| // https://vercel.com/docs/build-output-api/primitives | ||
| // | ||
| // Immutable static files are content-addressed and shared across deployments which improves cross-deployment | ||
| // caching. | ||
| // | ||
| // Newer deployments must never overwrite an existing file with different content, so the file names embed a content hash. | ||
| // | ||
| // Alongside `.vercel/output/static`, we emit a `.vercel/output/immutable.json` | ||
| // manifest mapping each immutable static file URL to its full content hash. The | ||
| // spec allows using the file path itself as the "full content hash" when the | ||
| // name already carries enough entropy, which is what we do: emitted file names | ||
| // embed a 16 character content hash (see `useLongerAssetHashes`). | ||
|
|
||
| const IMMUTABLE_MANIFEST = "immutable.json"; | ||
|
|
||
| interface ImmutableManifest { | ||
| version: 1; | ||
| hashes: Record<string, string>; | ||
| } | ||
|
|
||
| // Opt-in guard for immutable static files, run from `build:before`. | ||
| // | ||
| // Immutable static files are opt-in via `vercel.immutableStaticFiles` (defaulting | ||
| // to the `NITRO_VERCEL_IMMUTABLE_STATIC_FILES_ENABLED` env var). When running on | ||
| // Vercel, the platform advertises support through `VERCEL_IMMUTABLE_STATIC_FILES_ENABLED`. | ||
| // If the feature is enabled but the current deployment does not support it, or the | ||
| // app is served from a non-root `baseURL`, an immutable deploy would fail or silently | ||
| // not apply, so we disable it and warn instead of producing broken output. | ||
| export function setupImmutableStaticFiles(nitro: Nitro) { | ||
| if (!nitro.options.vercel?.immutableStaticFiles) { | ||
| return; | ||
| } | ||
|
|
||
| if (provider === "vercel" && !process.env.VERCEL_IMMUTABLE_STATIC_FILES_ENABLED) { | ||
| nitro.options.vercel.immutableStaticFiles = false; | ||
| nitro.logger | ||
| .withTag("vercel") | ||
| .warn( | ||
| "Immutable static files are enabled but not supported by the current Vercel deployment (`VERCEL_IMMUTABLE_STATIC_FILES_ENABLED` is not set). Skipping immutable static files output." | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| // `publicDir` is nested under `baseURL`, so a non-root base would emit assets | ||
| // to `/<base>/_vercel/immutable/...` instead of the reserved `/_vercel/immutable/` | ||
| // namespace. Vercel would not treat those as immutable, so opt out instead. | ||
| if (nitro.options.baseURL !== "/") { | ||
| nitro.options.vercel.immutableStaticFiles = false; | ||
| nitro.logger | ||
| .withTag("vercel") | ||
| .warn( | ||
| `Immutable static files are not supported with a non-root \`baseURL\` (\`${nitro.options.baseURL}\`), since they must be served from the reserved \`/_vercel/immutable/\` path. Skipping immutable static files output.` | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| nitro.options.buildAssetsDir = immutableDir(nitro); | ||
| } | ||
|
|
||
| export async function generateImmutableManifest(nitro: Nitro) { | ||
| // Skip unless immutable static files are enabled (`vercel.immutableStaticFiles`). | ||
| if (!nitro.options.vercel?.immutableStaticFiles) { | ||
| return; | ||
| } | ||
|
|
||
| const publicDir = nitro.options.output.publicDir; | ||
| const files = await glob(`${nitro.options.buildAssetsDir}/**`, { | ||
| cwd: publicDir, | ||
| absolute: false, | ||
| dot: true, | ||
| }); | ||
|
|
||
| const manifest: ImmutableManifest = { | ||
| version: 1, | ||
| hashes: Object.fromEntries( | ||
| files.map((file) => { | ||
| const pathname = withLeadingSlash(file); | ||
| const url = joinURL(nitro.options.baseURL, pathname); | ||
| return [url, url]; | ||
| }) | ||
| ), | ||
| }; | ||
|
|
||
| await writeFile( | ||
| resolve(nitro.options.output.dir, IMMUTABLE_MANIFEST), | ||
| JSON.stringify(manifest, null, 2) | ||
| ); | ||
|
|
||
| const logger = nitro.logger.withTag("vercel"); | ||
| if (files.length === 0) { | ||
| // Emitting no immutable files almost always means the client build did not | ||
| // pick up `buildAssetsDir` (only the Nitro + Vite integration wires it up | ||
| // automatically). Warn instead of silently writing an empty manifest. | ||
| logger.warn( | ||
| `Immutable static files are enabled but no assets were emitted under \`${nitro.options.buildAssetsDir}\`. Make sure your client build uses \`nitro.options.buildAssetsDir\` as its assets directory.` | ||
| ); | ||
| } else { | ||
| logger.info(`Generated immutable manifest (${files.length} files).`); | ||
| } | ||
| } | ||
|
|
||
| // Reserved Vercel namespace under which immutable static files are served. | ||
| // Used as the client `buildAssetsDir` so content-addressed assets are emitted | ||
| // and referenced here. The path is namespaced by an optional hash salt and the | ||
| // framework name to avoid cross-framework collisions. | ||
| export function immutableDir(nitro: Nitro) { | ||
| return normalizeBuildAssetsDir( | ||
| joinURL( | ||
| "_vercel/immutable", | ||
| process.env.VERCEL_HASH_SALT || "", | ||
| nitro.options.framework.name || "" | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| // Normalize a build assets directory into a bare relative path, stripping | ||
| // leading, trailing and duplicate slashes as well as `.` / `..` segments (a | ||
| // hand-set `VERCEL_HASH_SALT` must not escape the reserved namespace). | ||
| // Mirrors the same normalization applied to user config in `resolveURLOptions`. | ||
| function normalizeBuildAssetsDir(dir: string): string { | ||
| return dir | ||
| .split("/") | ||
| .filter((segment) => segment && segment !== "." && segment !== "..") | ||
| .join("/"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.