Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: reduce preload marker markup size #14550

Merged
merged 15 commits into from
Oct 10, 2023
100 changes: 73 additions & 27 deletions packages/vite/src/node/plugins/importAnalysisBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer'
import { removedPureCssFilesCache } from './css'
import { interopNamedImports } from './importAnalysis'

type FileDep = {
url: string
runtime: boolean
}

/**
* A flag for injected helpers. This flag will be set to `false` if the output
* target is not native es - so that injected helper logic can be conditionally
Expand Down Expand Up @@ -450,6 +455,26 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
const s = new MagicString(code)
const rewroteMarkerStartPos = new Set() // position of the leading double quote

const fileDeps: FileDep[] = []
const addFileDep = (
url: string,
runtime: boolean = false,
): number => {
const index = fileDeps.findIndex((dep) => dep.url === url)
if (index === -1) {
return fileDeps.push({ url, runtime }) - 1
} else {
return index
}
}
const getFileDep = (index: number): FileDep => {
const fileDep = fileDeps[index]
if (!fileDep) {
throw new Error(`Cannot find file dep at index ${index}`)
}
return fileDep
}

if (imports.length) {
for (let index = 0; index < imports.length; index++) {
// To handle escape sequences in specifier strings, the .n field will be provided where possible.
Expand All @@ -467,7 +492,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
if (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`)
url = rawUrl.slice(1, -1)
}
const deps: Set<string> = new Set()
const deps: Set<number> = new Set()
let hasRemovedPureCssChunk = false

let normalizedFile: string | undefined = undefined
Expand All @@ -487,12 +512,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
analyzed.add(filename)
const chunk = bundle[filename] as OutputChunk | undefined
if (chunk) {
deps.add(chunk.fileName)
deps.add(addFileDep(chunk.fileName))
chunk.imports.forEach(addDeps)
// Ensure that the css imported by current chunk is loaded after the dependencies.
// So the style of current chunk won't be overwritten unexpectedly.
chunk.viteMetadata!.importedCss.forEach((file) => {
deps.add(file)
deps.add(addFileDep(file))
})
} else {
const removedPureCssFiles =
Expand All @@ -501,7 +526,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
if (chunk) {
if (chunk.viteMetadata!.importedCss.size) {
chunk.viteMetadata!.importedCss.forEach((file) => {
deps.add(file)
deps.add(addFileDep(file))
})
hasRemovedPureCssChunk = true
}
Expand Down Expand Up @@ -535,74 +560,95 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
? modulePreload === false
? // CSS deps use the same mechanism as module preloads, so even if disabled,
// we still need to pass these deps to the preload helper in dynamic imports.
[...deps].filter((d) => d.endsWith('.css'))
[...deps].filter((d) =>
getFileDep(d).url.endsWith('.css'),
)
: [...deps]
: []

let renderedDeps: string[]
let renderedDeps: number[]
if (normalizedFile && customModulePreloadPaths) {
const { modulePreload } = config.build
const resolveDependencies = modulePreload
? modulePreload.resolveDependencies
: undefined
let resolvedDeps: string[]
let resolvedDeps: number[]
if (resolveDependencies) {
// We can't let the user remove css deps as these aren't really preloads, they are just using
// the same mechanism as module preloads for this chunk
const cssDeps: string[] = []
const otherDeps: string[] = []
const cssDeps: number[] = []
const otherDeps: number[] = []
for (const dep of depsArray) {
;(dep.endsWith('.css') ? cssDeps : otherDeps).push(dep)
;(getFileDep(dep).url.endsWith('.css')
gajus marked this conversation as resolved.
Show resolved Hide resolved
? cssDeps
: otherDeps
).push(dep)
}
resolvedDeps = [
...resolveDependencies(normalizedFile, otherDeps, {
hostId: file,
hostType: 'js',
}),
...resolveDependencies(
normalizedFile,
otherDeps.map((otherDep) => getFileDep(otherDep).url),
{
hostId: file,
hostType: 'js',
},
).map((otherDep) => addFileDep(otherDep)),
gajus marked this conversation as resolved.
Show resolved Hide resolved
...cssDeps,
]
} else {
resolvedDeps = depsArray
}

renderedDeps = resolvedDeps.map((dep: string) => {
renderedDeps = resolvedDeps.map((dep: number) => {
const replacement = toOutputFilePathInJS(
dep,
getFileDep(dep).url,
'asset',
chunk.fileName,
'js',
config,
toRelativePath,
)
const replacementString =
typeof replacement === 'string'
? JSON.stringify(replacement)
: replacement.runtime

return replacementString
if (typeof replacement === 'string') {
return addFileDep(replacement)
}

return addFileDep(replacement.runtime, true)
})
} else {
renderedDeps = depsArray.map((d) =>
// Don't include the assets dir if the default asset file names
// are used, the path will be reconstructed by the import preload helper
JSON.stringify(
optimizeModulePreloadRelativePaths
? toRelativePath(d, file)
: d,
),
optimizeModulePreloadRelativePaths
? addFileDep(toRelativePath(getFileDep(d).url, file))
: d,
)
}

s.update(
markerStartPos,
markerStartPos + preloadMarker.length + 2,
`[${renderedDeps.join(',')}]`,
`__viteMapDep([${renderedDeps.join(',')}])`,
)
rewroteMarkerStartPos.add(markerStartPos)
}
}
}

const fileDepsCode = `[${fileDeps
.map((fileDep) =>
fileDep.runtime ? fileDep.url : JSON.stringify(fileDep.url),
)
.join(',')}]`

s.append(`\
function __viteMapDep(indexes) {
gajus marked this conversation as resolved.
Show resolved Hide resolved
if (!__viteMapDep.viteFileDeps) {
__viteMapDep.viteFileDeps = ${fileDepsCode}
}
return indexes.map((i) => __viteMapDep.viteFileDeps[i])
}`)

// there may still be markers due to inlined dynamic imports, remove
// all the markers regardless
let markerStartPos = indexOfMatchInSlice(code, preloadMarkerWithQuote)
Expand Down
2 changes: 1 addition & 1 deletion playground/js-sourcemap/__tests__/js-sourcemap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe.runIf(isBuild)('build tests', () => {
const map = findAssetFile(/after-preload-dynamic.*\.js\.map/)
expect(formatSourcemapForSnapshot(JSON.parse(map))).toMatchInlineSnapshot(`
{
"mappings": "k2BAAA,OAAO,2BAAuB,EAAC,sEAE/B,QAAQ,IAAI,uBAAuB",
"mappings": "k2BAAA,OAAO,2BAAuB,EAAC,qBAE/B,QAAQ,IAAI,uBAAuB",
"sources": [
"../../after-preload-dynamic.js",
],
Expand Down