Skip to content

Commit b6a1a90

Browse files
committed
feat: add contentAddressedStatic to Vite plugin config
Port the contentAddressedStatic option to FreshViteConfig so it works with the modern App/Vite path (not just the legacy Builder API). Static files matching the glob patterns are marked immutable in the snapshot, enabling content-hash caching that survives deploys.
1 parent 840ebc5 commit b6a1a90

4 files changed

Lines changed: 38 additions & 5 deletions

File tree

docs/latest/concepts/static-files.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,22 @@ that should use their **content hash** as the cache-bust key instead of the
135135
build ID. The URL only changes when the file content changes — surviving deploys
136136
unchanged.
137137

138-
```ts dev.ts
139-
import { Builder } from "fresh/dev";
138+
```ts vite.config.ts
139+
import { defineConfig } from "vite";
140+
import { fresh } from "@fresh/plugin-vite";
140141

141-
const builder = new Builder({
142-
contentAddressedStatic: ["**/*.wasm", "**/*.bin"],
142+
export default defineConfig({
143+
plugins: [
144+
fresh({
145+
contentAddressedStatic: ["**/*.wasm", "**/*.bin"],
146+
}),
147+
],
143148
});
144149
```
145150

151+
> [info]: If you're using the [Builder](/docs/advanced/builder) API, the same
152+
> option is available on the `Builder` constructor.
153+
146154
With this config, `asset("/module.wasm")` produces a URL like
147155
`/module.wasm?__frsh_c=<content-hash>` instead of
148156
`/module.wasm?__frsh_c=<build-id>`. The middleware serves it with a one-year

packages/plugin-vite/src/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] {
6666
islandSpecifiers: new Map(),
6767
namer: new UniqueNamer(),
6868
checkImports: config?.checkImports ?? [],
69+
contentAddressedStatic: config?.contentAddressedStatic ?? [],
6970
};
7071

7172
fConfig.checkImports.push((id, env) => {

packages/plugin-vite/src/plugins/server_snapshot.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
type ResolvedFreshViteConfig,
2222
} from "../utils.ts";
2323
import * as path from "@std/path";
24+
import { globToRegExp } from "@std/path";
2425
import { getBuildId } from "./build_id.ts";
2526

2627
export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
@@ -276,6 +277,12 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
276277

277278
// Walk all static directories. First directory wins for
278279
// duplicate pathnames.
280+
const caRegexps = options.contentAddressedStatic.map((p) =>
281+
globToRegExp(p, { extended: true, globstar: true })
282+
);
283+
const isContentAddressed = (pathname: string) =>
284+
caRegexps.some((re) => re.test(pathname));
285+
279286
const seenStaticPaths = new Set<string>();
280287
for (const dir of options.staticDir) {
281288
if (!(await fsAdapter.isDirectory(dir))) continue;
@@ -312,6 +319,7 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] {
312319
filePath,
313320
hash: null,
314321
pathname: relative,
322+
immutable: isContentAddressed(relative) || undefined,
315323
});
316324

317325
if (path.basename(relative) === "index.html") {

packages/plugin-vite/src/utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,30 @@ export interface FreshViteConfig {
6969
* are not imported in Islands running in the browser.
7070
*/
7171
checkImports?: ImportCheck[];
72+
/**
73+
* Glob patterns for static files that should use content-hash
74+
* caching instead of build ID. Matching files get immutable cache
75+
* headers and their `asset()` URLs only change when the file
76+
* content changes — surviving deploys unchanged.
77+
*
78+
* Useful for large assets like WASM binaries, fonts, or media
79+
* that rarely change between deploys.
80+
*
81+
* @example ["**\/*.wasm", "**\/*.bin"]
82+
*/
83+
contentAddressedStatic?: string[];
7284
}
7385

7486
export type ResolvedFreshViteConfig =
7587
& Required<
76-
Omit<FreshViteConfig, "islandSpecifiers" | "staticDir">
88+
Omit<
89+
FreshViteConfig,
90+
"islandSpecifiers" | "staticDir" | "contentAddressedStatic"
91+
>
7792
>
7893
& {
7994
staticDir: string[];
8095
islandSpecifiers: Map<string, string>;
8196
namer: UniqueNamer;
97+
contentAddressedStatic: string[];
8298
};

0 commit comments

Comments
 (0)