|
| 1 | +import { cleanId } from "../utils.ts"; |
| 2 | + |
| 3 | +/** The parts of Vite's `DepsOptimizer` we rely on. */ |
| 4 | +export interface DepsOptimizerLike { |
| 5 | + metadata: { browserHash: string }; |
| 6 | + options: { extensions?: string[] }; |
| 7 | +} |
| 8 | + |
| 9 | +const DEP_VERSION_REG = /[?&]v=/; |
| 10 | +/** Mirrors Vite's `OPTIMIZABLE_ENTRY_RE`, which also excludes `.jsx`/`.tsx`. */ |
| 11 | +const OPTIMIZABLE_REG = /\.[cm]?[jt]s$/; |
| 12 | + |
| 13 | +/** |
| 14 | + * The dependency optimizer only exists on a dev environment, and is absent |
| 15 | + * from the base `Environment` type that plugin hooks are handed. |
| 16 | + */ |
| 17 | +export function depsOptimizerOf( |
| 18 | + environment: unknown, |
| 19 | +): DepsOptimizerLike | undefined { |
| 20 | + return (environment as { depsOptimizer?: DepsOptimizerLike }).depsOptimizer; |
| 21 | +} |
| 22 | + |
| 23 | +/** |
| 24 | + * Mirrors `ensureVersionQuery()` from Vite's `vite:resolve` plugin. |
| 25 | + * |
| 26 | + * That is where Vite attaches the optimizer's `?v=<hash>` to a dependency, |
| 27 | + * but `vite:resolve` never runs for the specifiers we resolve ourselves. Both |
| 28 | + * resolvers have to produce the same url for a given file: the browser keys |
| 29 | + * modules on the url it was told to fetch, so a file reachable under two urls |
| 30 | + * is loaded twice as two independent instances. |
| 31 | + */ |
| 32 | +export function ensureVersionQuery( |
| 33 | + resolved: string, |
| 34 | + depsOptimizer: DepsOptimizerLike, |
| 35 | +): string { |
| 36 | + // Only dependencies are versioned; app source uses `?t=` for HMR instead. |
| 37 | + if (!resolved.includes("node_modules")) return resolved; |
| 38 | + if (DEP_VERSION_REG.test(resolved)) return resolved; |
| 39 | + |
| 40 | + // Empty until the optimizer has finished initialising. |
| 41 | + const { browserHash } = depsOptimizer.metadata; |
| 42 | + if (!browserHash) return resolved; |
| 43 | + |
| 44 | + // Vite only versions what it could pre-bundle, so we must match. |
| 45 | + const file = cleanId(resolved); |
| 46 | + const { extensions } = depsOptimizer.options; |
| 47 | + const optimizable = OPTIMIZABLE_REG.test(file) || |
| 48 | + (extensions?.some((ext) => file.endsWith(ext)) ?? false); |
| 49 | + if (!optimizable) return resolved; |
| 50 | + |
| 51 | + return injectVersionQuery(resolved, browserHash); |
| 52 | +} |
| 53 | + |
| 54 | +function injectVersionQuery(id: string, browserHash: string): string { |
| 55 | + const hashIndex = id.indexOf("#"); |
| 56 | + const fragment = hashIndex >= 0 ? id.slice(hashIndex) : ""; |
| 57 | + const rest = hashIndex >= 0 ? id.slice(0, hashIndex) : id; |
| 58 | + |
| 59 | + const queryIndex = rest.indexOf("?"); |
| 60 | + if (queryIndex >= 0) { |
| 61 | + const pathname = rest.slice(0, queryIndex); |
| 62 | + const query = rest.slice(queryIndex + 1); |
| 63 | + return `${pathname}?v=${browserHash}&${query}${fragment}`; |
| 64 | + } |
| 65 | + |
| 66 | + return `${rest}?v=${browserHash}${fragment}`; |
| 67 | +} |
0 commit comments