Skip to content

Commit cf0154f

Browse files
authored
perf: do less on js side for optimizer (#76)
* perf(optimizer): reduce calculation of optimized result * perf(optimizer): call transform on JS side only if necessary
1 parent 55b487d commit cf0154f

File tree

2 files changed

+181
-154
lines changed

2 files changed

+181
-154
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -622,14 +622,20 @@ export function runOptimizeDeps(
622622
return context
623623
.build()
624624
.then((result) => {
625+
const depsForSrc: Record<string, OptimizedDepInfo[]> = {}
626+
for (const dep of Object.values(depsInfo)) {
627+
if (dep.src) {
628+
// One chunk maybe corresponding multiply entry
629+
depsForSrc[dep.src] ||= []
630+
depsForSrc[dep.src].push(dep)
631+
}
632+
}
633+
625634
for (const chunk of result.output) {
626635
if (chunk.type !== 'chunk') continue
627636

628637
if (chunk.isEntry) {
629-
// One chunk maybe corresponding multiply entry
630-
const deps = Object.values(depsInfo).filter(
631-
(d) => d.src === normalizePath(chunk.facadeModuleId!),
632-
)
638+
const deps = depsForSrc[normalizePath(chunk.facadeModuleId!)]
633639
for (const { exportsData, file, id, ...info } of deps) {
634640
addOptimizedDepInfo(metadata, 'optimized', {
635641
id,

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

Lines changed: 171 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ async function prepareRolldownScanner(
293293

294294
const plugins = await asyncFlatten(arraify(pluginsFromConfig))
295295

296-
plugins.push(rolldownScanPlugin(environment, deps, missing, entries))
296+
plugins.push(...rolldownScanPlugin(environment, deps, missing, entries))
297297

298298
async function build() {
299299
await scan({
@@ -350,7 +350,7 @@ function rolldownScanPlugin(
350350
depImports: Record<string, string>,
351351
missing: Record<string, string>,
352352
entries: string[],
353-
): Plugin {
353+
): Plugin[] {
354354
const seen = new Map<string, string | undefined>()
355355
async function resolveId(
356356
id: string,
@@ -533,182 +533,203 @@ function rolldownScanPlugin(
533533

534534
const ASSET_TYPE_RE = new RegExp(`\\.(${KNOWN_ASSET_TYPES.join('|')})$`)
535535

536-
return {
537-
name: 'vite:dep-scan',
538-
async resolveId(id, importer) {
539-
// external urls
540-
if (externalRE.test(id)) {
541-
return {
542-
id,
543-
external: true,
536+
return [
537+
{
538+
name: 'vite:dep-scan:resolve',
539+
async resolveId(id, importer) {
540+
// external urls
541+
if (externalRE.test(id)) {
542+
return {
543+
id,
544+
external: true,
545+
}
544546
}
545-
}
546547

547-
// data urls
548-
if (dataUrlRE.test(id)) {
549-
return {
550-
id,
551-
external: true,
548+
// data urls
549+
if (dataUrlRE.test(id)) {
550+
return {
551+
id,
552+
external: true,
553+
}
552554
}
553-
}
554-
555-
// local scripts (`<script>` in Svelte and `<script setup>` in Vue)
556-
if (virtualModuleRE.test(id)) {
557-
return id
558-
}
559555

560-
// Make sure virtual module importer can be resolve
561-
importer =
562-
importer && virtualModuleRE.test(importer)
563-
? importer.replace(virtualModulePrefix, '')
564-
: importer
565-
566-
// html types: extract script contents -----------------------------------
567-
if (htmlTypesRE.test(id)) {
568-
const resolved = await resolve(id, importer)
569-
if (!resolved) return
570-
// It is possible for the scanner to scan html types in node_modules.
571-
// If we can optimize this html type, skip it so it's handled by the
572-
// bare import resolve, and recorded as optimization dep.
573-
if (
574-
isInNodeModules(resolved) &&
575-
isOptimizable(resolved, optimizeDepsOptions)
576-
)
577-
return
578-
return resolved
579-
}
580-
581-
// bare imports: record and externalize ----------------------------------
582-
// avoid matching windows volume
583-
if (/^[\w@][^:]/.test(id)) {
584-
if (moduleListContains(exclude, id)) {
585-
return externalUnlessEntry({ path: id })
556+
// local scripts (`<script>` in Svelte and `<script setup>` in Vue)
557+
if (virtualModuleRE.test(id)) {
558+
return id
586559
}
587-
if (depImports[id]) {
588-
return externalUnlessEntry({ path: id })
560+
561+
// Make sure virtual module importer can be resolve
562+
importer =
563+
importer && virtualModuleRE.test(importer)
564+
? importer.replace(virtualModulePrefix, '')
565+
: importer
566+
567+
// html types: extract script contents -----------------------------------
568+
if (htmlTypesRE.test(id)) {
569+
const resolved = await resolve(id, importer)
570+
if (!resolved) return
571+
// It is possible for the scanner to scan html types in node_modules.
572+
// If we can optimize this html type, skip it so it's handled by the
573+
// bare import resolve, and recorded as optimization dep.
574+
if (
575+
isInNodeModules(resolved) &&
576+
isOptimizable(resolved, optimizeDepsOptions)
577+
)
578+
return
579+
return resolved
589580
}
590-
const resolved = await resolve(id, importer, {
591-
custom: {
592-
depScan: importer ? { loader: scripts[importer]?.loader } : {},
593-
},
594-
})
595-
if (resolved) {
596-
if (shouldExternalizeDep(resolved, id)) {
581+
582+
// bare imports: record and externalize ----------------------------------
583+
// avoid matching windows volume
584+
if (/^[\w@][^:]/.test(id)) {
585+
if (moduleListContains(exclude, id)) {
597586
return externalUnlessEntry({ path: id })
598587
}
599-
if (isInNodeModules(resolved) || include?.includes(id)) {
600-
// dependency or forced included, externalize and stop crawling
601-
if (isOptimizable(resolved, optimizeDepsOptions)) {
602-
depImports[id] = resolved
603-
}
588+
if (depImports[id]) {
604589
return externalUnlessEntry({ path: id })
605-
} else if (isScannable(resolved, optimizeDepsOptions.extensions)) {
606-
// linked package, keep crawling
607-
return path.resolve(resolved)
590+
}
591+
const resolved = await resolve(id, importer, {
592+
custom: {
593+
depScan: importer ? { loader: scripts[importer]?.loader } : {},
594+
},
595+
})
596+
if (resolved) {
597+
if (shouldExternalizeDep(resolved, id)) {
598+
return externalUnlessEntry({ path: id })
599+
}
600+
if (isInNodeModules(resolved) || include?.includes(id)) {
601+
// dependency or forced included, externalize and stop crawling
602+
if (isOptimizable(resolved, optimizeDepsOptions)) {
603+
depImports[id] = resolved
604+
}
605+
return externalUnlessEntry({ path: id })
606+
} else if (isScannable(resolved, optimizeDepsOptions.extensions)) {
607+
// linked package, keep crawling
608+
return path.resolve(resolved)
609+
} else {
610+
return externalUnlessEntry({ path: id })
611+
}
608612
} else {
609-
return externalUnlessEntry({ path: id })
613+
missing[id] = normalizePath(importer!)
610614
}
611-
} else {
612-
missing[id] = normalizePath(importer!)
613-
}
614-
}
615-
616-
// Externalized file types -----------------------------------------------
617-
// these are done on raw ids using esbuild's native regex filter so it
618-
// should be faster than doing it in the catch-all via js
619-
// they are done after the bare import resolve because a package name
620-
// may end with these extensions
621-
622-
// css
623-
if (CSS_LANGS_RE.test(id)) {
624-
return externalUnlessEntry({ path: id })
625-
}
626-
627-
// json & wasm
628-
if (/\.(?:json|json5|wasm)$/.test(id)) {
629-
return externalUnlessEntry({ path: id })
630-
}
631-
632-
// known asset types
633-
if (ASSET_TYPE_RE.test(id)) {
634-
return externalUnlessEntry({ path: id })
635-
}
636-
637-
// known vite query types: ?worker, ?raw
638-
if (SPECIAL_QUERY_RE.test(id)) {
639-
return {
640-
id,
641-
external: true,
642615
}
643-
}
644616

645-
// catch all -------------------------------------------------------------
617+
// Externalized file types -----------------------------------------------
618+
// these are done on raw ids using esbuild's native regex filter so it
619+
// should be faster than doing it in the catch-all via js
620+
// they are done after the bare import resolve because a package name
621+
// may end with these extensions
646622

647-
// use vite resolver to support urls and omitted extensions
648-
const resolved = await resolve(id, importer, {
649-
custom: {
650-
depScan: importer ? { loader: scripts[importer]?.loader } : {},
651-
},
652-
})
653-
if (resolved) {
654-
if (
655-
shouldExternalizeDep(resolved, id) ||
656-
!isScannable(resolved, optimizeDepsOptions.extensions)
657-
) {
623+
// css
624+
if (CSS_LANGS_RE.test(id)) {
658625
return externalUnlessEntry({ path: id })
659626
}
660-
return path.resolve(cleanUrl(resolved))
661-
}
662627

663-
// resolve failed... probably unsupported type
664-
return externalUnlessEntry({ path: id })
665-
},
666-
async load(id) {
667-
if (virtualModuleRE.test(id)) {
668-
const script = scripts[id.replace(virtualModulePrefix, '')]
669-
return {
670-
code: script.contents,
671-
moduleType: script.loader,
628+
// json & wasm
629+
if (/\.(?:json|json5|wasm)$/.test(id)) {
630+
return externalUnlessEntry({ path: id })
672631
}
673-
}
674632

675-
// extract scripts inside HTML-like files and treat it as a js module
676-
if (htmlTypesRE.test(id)) {
677-
return {
678-
code: await htmlTypeOnLoadCallback(id),
679-
moduleType: 'js',
633+
// known asset types
634+
if (ASSET_TYPE_RE.test(id)) {
635+
return externalUnlessEntry({ path: id })
680636
}
681-
}
682637

683-
// for jsx/tsx, we need to access the content and check for
684-
// presence of import.meta.glob, since it results in import relationships
685-
// but isn't crawled by esbuild.
686-
if (JS_TYPES_RE.test(id)) {
687-
let ext = path.extname(id).slice(1)
688-
if (ext === 'mjs') ext = 'js'
689-
690-
const esbuildConfig = environment.config.esbuild
691-
let contents = await fsp.readFile(id, 'utf-8')
692-
if (ext.endsWith('x') && esbuildConfig && esbuildConfig.jsxInject) {
693-
contents = esbuildConfig.jsxInject + `\n` + contents
638+
// known vite query types: ?worker, ?raw
639+
if (SPECIAL_QUERY_RE.test(id)) {
640+
return {
641+
id,
642+
external: true,
643+
}
694644
}
695645

696-
const loader = ext as 'js' | 'ts' | 'jsx' | 'tsx'
646+
// catch all -------------------------------------------------------------
697647

698-
if (contents.includes('import.meta.glob')) {
699-
return {
700-
moduleType: 'js',
701-
code: await doTransformGlobImport(contents, id, loader),
648+
// use vite resolver to support urls and omitted extensions
649+
const resolved = await resolve(id, importer, {
650+
custom: {
651+
depScan: importer ? { loader: scripts[importer]?.loader } : {},
652+
},
653+
})
654+
if (resolved) {
655+
if (
656+
shouldExternalizeDep(resolved, id) ||
657+
!isScannable(resolved, optimizeDepsOptions.extensions)
658+
) {
659+
return externalUnlessEntry({ path: id })
702660
}
661+
return path.resolve(cleanUrl(resolved))
703662
}
704663

705-
return {
706-
moduleType: loader,
707-
code: contents,
708-
}
709-
}
664+
// resolve failed... probably unsupported type
665+
return externalUnlessEntry({ path: id })
666+
},
710667
},
711-
}
668+
{
669+
name: 'vite:dep-scan:load:html',
670+
load: {
671+
filter: { id: [virtualModuleRE, htmlTypesRE] },
672+
async handler(id) {
673+
if (virtualModuleRE.test(id)) {
674+
const script = scripts[id.replace(virtualModulePrefix, '')]
675+
return {
676+
code: script.contents,
677+
moduleType: script.loader,
678+
}
679+
}
680+
681+
// extract scripts inside HTML-like files and treat it as a js module
682+
if (htmlTypesRE.test(id)) {
683+
return {
684+
code: await htmlTypeOnLoadCallback(id),
685+
moduleType: 'js',
686+
}
687+
}
688+
},
689+
},
690+
},
691+
// for jsx/tsx, we need to access the content and check for
692+
// presence of import.meta.glob, since it results in import relationships
693+
// but isn't crawled by esbuild.
694+
...(environment.config.esbuild && environment.config.esbuild.jsxInject
695+
? [
696+
{
697+
name: 'vite:dep-scan:transform:jsx-inject',
698+
transform: {
699+
filter: {
700+
id: /\.[jt]sx$/,
701+
},
702+
handler(code) {
703+
const esbuildConfig = environment.config.esbuild
704+
if (esbuildConfig && esbuildConfig.jsxInject) {
705+
code = esbuildConfig.jsxInject + `\n` + code
706+
}
707+
return code
708+
},
709+
},
710+
} satisfies Plugin,
711+
]
712+
: []),
713+
{
714+
name: 'vite:dep-scan:transform:js-glob',
715+
transform: {
716+
filter: {
717+
code: 'import.meta.glob',
718+
},
719+
async handler(code, id) {
720+
if (JS_TYPES_RE.test(id)) {
721+
let ext = path.extname(id).slice(1)
722+
if (ext === 'mjs') ext = 'js'
723+
const loader = ext as 'js' | 'ts' | 'jsx' | 'tsx'
724+
return {
725+
moduleType: 'js',
726+
code: await doTransformGlobImport(code, id, loader),
727+
}
728+
}
729+
},
730+
},
731+
},
732+
]
712733
}
713734

714735
/**

0 commit comments

Comments
 (0)