File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -135,14 +135,22 @@ that should use their **content hash** as the cache-bust key instead of the
135135build ID. The URL only changes when the file content changes — surviving deploys
136136unchanged.
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+
146154With 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
Original file line number Diff line number Diff 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 ) => {
Original file line number Diff line number Diff line change @@ -21,6 +21,7 @@ import {
2121 type ResolvedFreshViteConfig ,
2222} from "../utils.ts" ;
2323import * as path from "@std/path" ;
24+ import { globToRegExp } from "@std/path" ;
2425import { getBuildId } from "./build_id.ts" ;
2526
2627export 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" ) {
Original file line number Diff line number Diff 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
7486export 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 } ;
You can’t perform that action at this time.
0 commit comments