Skip to content

Commit 6f12fd8

Browse files
authored
perf: reduce preload marker markup size (#14550)
1 parent 5bb13aa commit 6f12fd8

File tree

2 files changed

+75
-28
lines changed

2 files changed

+75
-28
lines changed

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

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer'
2626
import { removedPureCssFilesCache } from './css'
2727
import { interopNamedImports } from './importAnalysis'
2828

29+
type FileDep = {
30+
url: string
31+
runtime: boolean
32+
}
33+
2934
/**
3035
* A flag for injected helpers. This flag will be set to `false` if the output
3136
* target is not native es - so that injected helper logic can be conditionally
@@ -450,6 +455,26 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
450455
const s = new MagicString(code)
451456
const rewroteMarkerStartPos = new Set() // position of the leading double quote
452457

458+
const fileDeps: FileDep[] = []
459+
const addFileDep = (
460+
url: string,
461+
runtime: boolean = false,
462+
): number => {
463+
const index = fileDeps.findIndex((dep) => dep.url === url)
464+
if (index === -1) {
465+
return fileDeps.push({ url, runtime }) - 1
466+
} else {
467+
return index
468+
}
469+
}
470+
const getFileDep = (index: number): FileDep => {
471+
const fileDep = fileDeps[index]
472+
if (!fileDep) {
473+
throw new Error(`Cannot find file dep at index ${index}`)
474+
}
475+
return fileDep
476+
}
477+
453478
if (imports.length) {
454479
for (let index = 0; index < imports.length; index++) {
455480
// To handle escape sequences in specifier strings, the .n field will be provided where possible.
@@ -467,7 +492,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
467492
if (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`)
468493
url = rawUrl.slice(1, -1)
469494
}
470-
const deps: Set<string> = new Set()
495+
const deps: Set<number> = new Set()
471496
let hasRemovedPureCssChunk = false
472497

473498
let normalizedFile: string | undefined = undefined
@@ -487,12 +512,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
487512
analyzed.add(filename)
488513
const chunk = bundle[filename] as OutputChunk | undefined
489514
if (chunk) {
490-
deps.add(chunk.fileName)
515+
deps.add(addFileDep(chunk.fileName))
491516
chunk.imports.forEach(addDeps)
492517
// Ensure that the css imported by current chunk is loaded after the dependencies.
493518
// So the style of current chunk won't be overwritten unexpectedly.
494519
chunk.viteMetadata!.importedCss.forEach((file) => {
495-
deps.add(file)
520+
deps.add(addFileDep(file))
496521
})
497522
} else {
498523
const removedPureCssFiles =
@@ -501,7 +526,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
501526
if (chunk) {
502527
if (chunk.viteMetadata!.importedCss.size) {
503528
chunk.viteMetadata!.importedCss.forEach((file) => {
504-
deps.add(file)
529+
deps.add(addFileDep(file))
505530
})
506531
hasRemovedPureCssChunk = true
507532
}
@@ -535,74 +560,96 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
535560
? modulePreload === false
536561
? // CSS deps use the same mechanism as module preloads, so even if disabled,
537562
// we still need to pass these deps to the preload helper in dynamic imports.
538-
[...deps].filter((d) => d.endsWith('.css'))
563+
[...deps].filter((d) =>
564+
getFileDep(d).url.endsWith('.css'),
565+
)
539566
: [...deps]
540567
: []
541568

542-
let renderedDeps: string[]
569+
let renderedDeps: number[]
543570
if (normalizedFile && customModulePreloadPaths) {
544571
const { modulePreload } = config.build
545572
const resolveDependencies = modulePreload
546573
? modulePreload.resolveDependencies
547574
: undefined
548-
let resolvedDeps: string[]
575+
let resolvedDeps: number[]
549576
if (resolveDependencies) {
550577
// We can't let the user remove css deps as these aren't really preloads, they are just using
551578
// the same mechanism as module preloads for this chunk
552-
const cssDeps: string[] = []
553-
const otherDeps: string[] = []
579+
const cssDeps: number[] = []
580+
const otherDeps: number[] = []
554581
for (const dep of depsArray) {
555-
;(dep.endsWith('.css') ? cssDeps : otherDeps).push(dep)
582+
if (getFileDep(dep).url.endsWith('.css')) {
583+
cssDeps.push(dep)
584+
} else {
585+
otherDeps.push(dep)
586+
}
556587
}
557588
resolvedDeps = [
558-
...resolveDependencies(normalizedFile, otherDeps, {
559-
hostId: file,
560-
hostType: 'js',
561-
}),
589+
...resolveDependencies(
590+
normalizedFile,
591+
otherDeps.map((otherDep) => getFileDep(otherDep).url),
592+
{
593+
hostId: file,
594+
hostType: 'js',
595+
},
596+
).map((otherDep) => addFileDep(otherDep)),
562597
...cssDeps,
563598
]
564599
} else {
565600
resolvedDeps = depsArray
566601
}
567602

568-
renderedDeps = resolvedDeps.map((dep: string) => {
603+
renderedDeps = resolvedDeps.map((dep: number) => {
569604
const replacement = toOutputFilePathInJS(
570-
dep,
605+
getFileDep(dep).url,
571606
'asset',
572607
chunk.fileName,
573608
'js',
574609
config,
575610
toRelativePath,
576611
)
577-
const replacementString =
578-
typeof replacement === 'string'
579-
? JSON.stringify(replacement)
580-
: replacement.runtime
581612

582-
return replacementString
613+
if (typeof replacement === 'string') {
614+
return addFileDep(replacement)
615+
}
616+
617+
return addFileDep(replacement.runtime, true)
583618
})
584619
} else {
585620
renderedDeps = depsArray.map((d) =>
586621
// Don't include the assets dir if the default asset file names
587622
// are used, the path will be reconstructed by the import preload helper
588-
JSON.stringify(
589-
optimizeModulePreloadRelativePaths
590-
? toRelativePath(d, file)
591-
: d,
592-
),
623+
optimizeModulePreloadRelativePaths
624+
? addFileDep(toRelativePath(getFileDep(d).url, file))
625+
: d,
593626
)
594627
}
595628

596629
s.update(
597630
markerStartPos,
598631
markerStartPos + preloadMarker.length + 2,
599-
`[${renderedDeps.join(',')}]`,
632+
`__vite__mapDeps([${renderedDeps.join(',')}])`,
600633
)
601634
rewroteMarkerStartPos.add(markerStartPos)
602635
}
603636
}
604637
}
605638

639+
const fileDepsCode = `[${fileDeps
640+
.map((fileDep) =>
641+
fileDep.runtime ? fileDep.url : JSON.stringify(fileDep.url),
642+
)
643+
.join(',')}]`
644+
645+
s.append(`\
646+
function __vite__mapDeps(indexes) {
647+
if (!__vite__mapDeps.viteFileDeps) {
648+
__vite__mapDeps.viteFileDeps = ${fileDepsCode}
649+
}
650+
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
651+
}`)
652+
606653
// there may still be markers due to inlined dynamic imports, remove
607654
// all the markers regardless
608655
let markerStartPos = indexOfMatchInSlice(code, preloadMarkerWithQuote)

playground/js-sourcemap/__tests__/js-sourcemap.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ describe.runIf(isBuild)('build tests', () => {
117117
const map = findAssetFile(/after-preload-dynamic.*\.js\.map/)
118118
expect(formatSourcemapForSnapshot(JSON.parse(map))).toMatchInlineSnapshot(`
119119
{
120-
"mappings": "k2BAAA,OAAO,2BAAuB,EAAC,sEAE/B,QAAQ,IAAI,uBAAuB",
120+
"mappings": "k2BAAA,OAAO,2BAAuB,EAAC,wBAE/B,QAAQ,IAAI,uBAAuB",
121121
"sources": [
122122
"../../after-preload-dynamic.js",
123123
],

0 commit comments

Comments
 (0)