Skip to content

Commit 392a0de

Browse files
authored
fix: browser cache of newly discovered deps (#7378)
1 parent f448593 commit 392a0de

File tree

4 files changed

+148
-26
lines changed

4 files changed

+148
-26
lines changed

packages/vite/src/node/optimizer/index.ts

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import { performance } from 'perf_hooks'
2424
const debug = createDebugger('vite:deps')
2525
const isDebugEnabled = _debug('vite:deps').enabled
2626

27+
const jsExtensionRE = /\.js$/i
28+
const jsMapExtensionRE = /\.js\.map$/i
29+
2730
export type ExportsData = ReturnType<typeof parse> & {
2831
// es-module-lexer has a facade detection but isn't always accurate for our
2932
// use case when the module has default export
@@ -125,7 +128,7 @@ export interface OptimizedDepInfo {
125128
* During optimization, ids can still be resolved to their final location
126129
* but the bundles may not yet be saved to disk
127130
*/
128-
processing: Promise<void>
131+
processing?: Promise<void>
129132
}
130133

131134
export interface DepOptimizationMetadata {
@@ -144,6 +147,10 @@ export interface DepOptimizationMetadata {
144147
* Metadata for each already optimized dependency
145148
*/
146149
optimized: Record<string, OptimizedDepInfo>
150+
/**
151+
* Metadata for non-entry optimized chunks and dynamic imports
152+
*/
153+
chunks: Record<string, OptimizedDepInfo>
147154
/**
148155
* Metadata for each newly discovered dependency after processing
149156
*/
@@ -213,6 +220,7 @@ export async function createOptimizeDepsRun(
213220
hash: mainHash,
214221
browserHash: mainHash,
215222
optimized: {},
223+
chunks: {},
216224
discovered: {}
217225
}
218226

@@ -222,8 +230,7 @@ export async function createOptimizeDepsRun(
222230
const prevDataPath = path.join(depsCacheDir, '_metadata.json')
223231
prevData = parseOptimizedDepsMetadata(
224232
fs.readFileSync(prevDataPath, 'utf-8'),
225-
depsCacheDir,
226-
processing.promise
233+
depsCacheDir
227234
)
228235
} catch (e) {}
229236
// hash is consistent, no need to re-bundle
@@ -490,7 +497,9 @@ export async function createOptimizeDepsRun(
490497
processingCacheDirOutputPath
491498
)
492499
const output =
493-
meta.outputs[path.relative(process.cwd(), optimizedInfo.file)]
500+
meta.outputs[
501+
path.relative(process.cwd(), getProcessingDepPath(id, config))
502+
]
494503
if (output) {
495504
// We only need to hash the output.imports in to check for stability, but adding the hash
496505
// and file path gives us a unique hash that may be useful for other things in the future
@@ -518,6 +527,25 @@ export async function createOptimizeDepsRun(
518527
debug(`optimized deps have altered files: ${alteredFiles}`)
519528
}
520529

530+
for (const o of Object.keys(meta.outputs)) {
531+
if (!o.match(jsMapExtensionRE)) {
532+
const id = path
533+
.relative(processingCacheDirOutputPath, o)
534+
.replace(jsExtensionRE, '')
535+
const file = getOptimizedDepPath(id, config)
536+
if (!findFileInfo(metadata.optimized, file)) {
537+
metadata.chunks[id] = {
538+
file,
539+
src: '',
540+
needsInterop: false,
541+
browserHash:
542+
(!alteredFiles && currentData?.chunks[id]?.browserHash) ||
543+
newBrowserHash
544+
}
545+
}
546+
}
547+
}
548+
521549
if (alteredFiles) {
522550
metadata.browserHash = newBrowserHash
523551
}
@@ -615,19 +643,12 @@ export function depsFromOptimizedDepInfo(
615643
)
616644
}
617645

618-
function getHash(text: string) {
646+
export function getHash(text: string) {
619647
return createHash('sha256').update(text).digest('hex').substring(0, 8)
620648
}
621649

622-
export function getOptimizedBrowserHash(
623-
hash: string,
624-
deps: Record<string, string>,
625-
missing?: Record<string, string>
626-
) {
627-
// update browser hash
628-
return getHash(
629-
hash + JSON.stringify(deps) + (missing ? JSON.stringify(missing) : '')
630-
)
650+
function getOptimizedBrowserHash(hash: string, deps: Record<string, string>) {
651+
return getHash(hash + JSON.stringify(deps))
631652
}
632653

633654
function getCachedDepFilePath(id: string, depsCacheDir: string) {
@@ -642,7 +663,15 @@ export function getDepsCacheDir(config: ResolvedConfig) {
642663
return normalizePath(path.resolve(config.cacheDir, 'deps'))
643664
}
644665

645-
export function getProcessingDepsCacheDir(config: ResolvedConfig) {
666+
function getProcessingDepFilePath(id: string, processingCacheDir: string) {
667+
return normalizePath(path.resolve(processingCacheDir, flattenId(id) + '.js'))
668+
}
669+
670+
function getProcessingDepPath(id: string, config: ResolvedConfig) {
671+
return getProcessingDepFilePath(id, getProcessingDepsCacheDir(config))
672+
}
673+
674+
function getProcessingDepsCacheDir(config: ResolvedConfig) {
646675
return normalizePath(path.resolve(config.cacheDir, 'processing'))
647676
}
648677

@@ -671,8 +700,7 @@ export function createIsOptimizedDepUrl(config: ResolvedConfig) {
671700

672701
function parseOptimizedDepsMetadata(
673702
jsonMetadata: string,
674-
depsCacheDir: string,
675-
processing: Promise<void>
703+
depsCacheDir: string
676704
) {
677705
const metadata = JSON.parse(jsonMetadata, (key: string, value: string) => {
678706
// Paths can be absolute or relative to the deps cache dir where
@@ -682,25 +710,69 @@ function parseOptimizedDepsMetadata(
682710
}
683711
return value
684712
})
713+
const { browserHash } = metadata
685714
for (const o of Object.keys(metadata.optimized)) {
686-
metadata.optimized[o].processing = processing
715+
const depInfo = metadata.optimized[o]
716+
depInfo.browserHash = browserHash
717+
}
718+
metadata.chunks ||= {} // Support missing chunks for back compat
719+
for (const o of Object.keys(metadata.chunks)) {
720+
const depInfo = metadata.chunks[o]
721+
depInfo.src = ''
722+
depInfo.browserHash = browserHash
687723
}
688-
return { ...metadata, discovered: {} }
724+
metadata.discovered = {}
725+
return metadata
689726
}
690727

728+
/**
729+
* Stringify metadata for deps cache. Remove processing promises
730+
* and individual dep info browserHash. Once the cache is reload
731+
* the next time the server start we need to use the global
732+
* browserHash to allow long term caching
733+
*/
691734
function stringifyOptimizedDepsMetadata(
692735
metadata: DepOptimizationMetadata,
693736
depsCacheDir: string
694737
) {
695738
return JSON.stringify(
696739
metadata,
697740
(key: string, value: any) => {
698-
if (key === 'processing' || key === 'discovered') {
741+
if (key === 'discovered' || key === 'processing') {
699742
return
700743
}
701744
if (key === 'file' || key === 'src') {
702745
return normalizePath(path.relative(depsCacheDir, value))
703746
}
747+
if (key === 'optimized') {
748+
// Only remove browserHash for individual dep info
749+
const cleaned: Record<string, object> = {}
750+
for (const dep of Object.keys(value)) {
751+
const { browserHash, ...c } = value[dep]
752+
cleaned[dep] = c
753+
}
754+
return cleaned
755+
}
756+
if (key === 'optimized') {
757+
return Object.keys(value).reduce(
758+
(cleaned: Record<string, object>, dep: string) => {
759+
const { browserHash, ...c } = value[dep]
760+
cleaned[dep] = c
761+
return cleaned
762+
},
763+
{}
764+
)
765+
}
766+
if (key === 'chunks') {
767+
return Object.keys(value).reduce(
768+
(cleaned: Record<string, object>, dep: string) => {
769+
const { browserHash, needsInterop, src, ...c } = value[dep]
770+
cleaned[dep] = c
771+
return cleaned
772+
},
773+
{}
774+
)
775+
}
704776
return value
705777
},
706778
2
@@ -797,7 +869,8 @@ export function optimizeDepInfoFromFile(
797869
): OptimizedDepInfo | undefined {
798870
return (
799871
findFileInfo(metadata.optimized, file) ||
800-
findFileInfo(metadata.discovered, file)
872+
findFileInfo(metadata.discovered, file) ||
873+
findFileInfo(metadata.chunks, file)
801874
)
802875
}
803876

packages/vite/src/node/optimizer/registerMissing.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import colors from 'picocolors'
22
import {
33
createOptimizeDepsRun,
44
getOptimizedDepPath,
5-
getOptimizedBrowserHash,
5+
getHash,
66
depsFromOptimizedDepInfo,
77
newDepOptimizationProcessing
88
} from '.'
@@ -202,6 +202,21 @@ export function createMissingImporterRegisterFn(
202202
})
203203
}
204204

205+
const discoveredTimestamp = Date.now()
206+
207+
function getDiscoveredBrowserHash(
208+
hash: string,
209+
deps: Record<string, string>,
210+
missing: Record<string, string>
211+
) {
212+
return getHash(
213+
hash +
214+
JSON.stringify(deps) +
215+
JSON.stringify(missing) +
216+
discoveredTimestamp
217+
)
218+
}
219+
205220
return function registerMissingImport(
206221
id: string,
207222
resolved: string,
@@ -211,6 +226,10 @@ export function createMissingImporterRegisterFn(
211226
if (optimized) {
212227
return optimized
213228
}
229+
const chunk = metadata.chunks[id]
230+
if (chunk) {
231+
return chunk
232+
}
214233
let missing = metadata.discovered[id]
215234
if (missing) {
216235
// We are already discover this dependency
@@ -225,7 +244,7 @@ export function createMissingImporterRegisterFn(
225244
// the current state of known + missing deps. If its optimizeDeps run
226245
// doesn't alter the bundled files of previous known dependendencies,
227246
// we don't need a full reload and this browserHash will be kept
228-
browserHash: getOptimizedBrowserHash(
247+
browserHash: getDiscoveredBrowserHash(
229248
metadata.hash,
230249
depsFromOptimizedDepInfo(metadata.optimized),
231250
depsFromOptimizedDepInfo(metadata.discovered)

packages/vite/src/node/plugins/importAnalysis.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,11 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
271271
// (e.g. vue blocks), inherit importer's version query
272272
// do not do this for unknown type imports, otherwise the appended
273273
// query can break 3rd party plugin's extension checks.
274-
if ((isRelative || isSelfImport) && !/[\?&]import=?\b/.test(url)) {
274+
if (
275+
(isRelative || isSelfImport) &&
276+
!/[\?&]import=?\b/.test(url) &&
277+
!url.match(DEP_VERSION_RE)
278+
) {
275279
const versionMatch = importer.match(DEP_VERSION_RE)
276280
if (versionMatch) {
277281
url = injectQuery(url, versionMatch[1])

packages/vite/src/node/plugins/resolve.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
SPECIAL_QUERY_RE,
88
DEFAULT_EXTENSIONS,
99
DEFAULT_MAIN_FIELDS,
10-
OPTIMIZABLE_ENTRY_RE
10+
OPTIMIZABLE_ENTRY_RE,
11+
DEP_VERSION_RE
1112
} from '../constants'
1213
import {
1314
isBuiltin,
@@ -29,7 +30,11 @@ import {
2930
isPossibleTsOutput,
3031
getPotentialTsSrcPaths
3132
} from '../utils'
32-
import { createIsOptimizedDepUrl } from '../optimizer'
33+
import {
34+
createIsOptimizedDepUrl,
35+
isOptimizedDepFile,
36+
optimizeDepInfoFromFile
37+
} from '../optimizer'
3338
import type { OptimizedDepInfo } from '../optimizer'
3439
import type { ViteDevServer, SSROptions } from '..'
3540
import type { PartialResolvedId } from 'rollup'
@@ -163,6 +168,22 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
163168
// handle browser field mapping for relative imports
164169

165170
const normalizedFsPath = normalizePath(fsPath)
171+
172+
if (server && isOptimizedDepFile(normalizedFsPath, server!.config)) {
173+
// Optimized files could not yet exist in disk, resolve to the full path
174+
// Inject the current browserHash version if the path doesn't have one
175+
if (!normalizedFsPath.match(DEP_VERSION_RE)) {
176+
const browserHash = optimizeDepInfoFromFile(
177+
server._optimizeDepsMetadata!,
178+
normalizedFsPath
179+
)?.browserHash
180+
if (browserHash) {
181+
return injectQuery(normalizedFsPath, `v=${browserHash}`)
182+
}
183+
}
184+
return normalizedFsPath
185+
}
186+
166187
const pathFromBasedir = normalizedFsPath.slice(basedir.length)
167188
if (pathFromBasedir.startsWith('/node_modules/')) {
168189
// normalize direct imports from node_modules to bare imports, so the
@@ -638,6 +659,11 @@ export function tryOptimizedResolve(
638659
return getOptimizedUrl(isOptimized)
639660
}
640661

662+
const isChunk = depData.chunks[id]
663+
if (isChunk) {
664+
return getOptimizedUrl(isChunk)
665+
}
666+
641667
if (!importer) return
642668

643669
// further check if id is imported by nested dependency

0 commit comments

Comments
 (0)