Skip to content

Commit

Permalink
perf: do less on js side for optimizer (#76)
Browse files Browse the repository at this point in the history
* perf(optimizer): reduce calculation of optimized result
* perf(optimizer): call transform on JS side only if necessary
  • Loading branch information
sapphi-red authored Dec 10, 2024
1 parent 55b487d commit cf0154f
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 154 deletions.
14 changes: 10 additions & 4 deletions packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,20 @@ export function runOptimizeDeps(
return context
.build()
.then((result) => {
const depsForSrc: Record<string, OptimizedDepInfo[]> = {}
for (const dep of Object.values(depsInfo)) {
if (dep.src) {
// One chunk maybe corresponding multiply entry
depsForSrc[dep.src] ||= []
depsForSrc[dep.src].push(dep)
}
}

for (const chunk of result.output) {
if (chunk.type !== 'chunk') continue

if (chunk.isEntry) {
// One chunk maybe corresponding multiply entry
const deps = Object.values(depsInfo).filter(
(d) => d.src === normalizePath(chunk.facadeModuleId!),
)
const deps = depsForSrc[normalizePath(chunk.facadeModuleId!)]
for (const { exportsData, file, id, ...info } of deps) {
addOptimizedDepInfo(metadata, 'optimized', {
id,
Expand Down
321 changes: 171 additions & 150 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ async function prepareRolldownScanner(

const plugins = await asyncFlatten(arraify(pluginsFromConfig))

plugins.push(rolldownScanPlugin(environment, deps, missing, entries))
plugins.push(...rolldownScanPlugin(environment, deps, missing, entries))

async function build() {
await scan({
Expand Down Expand Up @@ -350,7 +350,7 @@ function rolldownScanPlugin(
depImports: Record<string, string>,
missing: Record<string, string>,
entries: string[],
): Plugin {
): Plugin[] {
const seen = new Map<string, string | undefined>()
async function resolveId(
id: string,
Expand Down Expand Up @@ -533,182 +533,203 @@ function rolldownScanPlugin(

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

return {
name: 'vite:dep-scan',
async resolveId(id, importer) {
// external urls
if (externalRE.test(id)) {
return {
id,
external: true,
return [
{
name: 'vite:dep-scan:resolve',
async resolveId(id, importer) {
// external urls
if (externalRE.test(id)) {
return {
id,
external: true,
}
}
}

// data urls
if (dataUrlRE.test(id)) {
return {
id,
external: true,
// data urls
if (dataUrlRE.test(id)) {
return {
id,
external: true,
}
}
}

// local scripts (`<script>` in Svelte and `<script setup>` in Vue)
if (virtualModuleRE.test(id)) {
return id
}

// Make sure virtual module importer can be resolve
importer =
importer && virtualModuleRE.test(importer)
? importer.replace(virtualModulePrefix, '')
: importer

// html types: extract script contents -----------------------------------
if (htmlTypesRE.test(id)) {
const resolved = await resolve(id, importer)
if (!resolved) return
// It is possible for the scanner to scan html types in node_modules.
// If we can optimize this html type, skip it so it's handled by the
// bare import resolve, and recorded as optimization dep.
if (
isInNodeModules(resolved) &&
isOptimizable(resolved, optimizeDepsOptions)
)
return
return resolved
}

// bare imports: record and externalize ----------------------------------
// avoid matching windows volume
if (/^[\w@][^:]/.test(id)) {
if (moduleListContains(exclude, id)) {
return externalUnlessEntry({ path: id })
// local scripts (`<script>` in Svelte and `<script setup>` in Vue)
if (virtualModuleRE.test(id)) {
return id
}
if (depImports[id]) {
return externalUnlessEntry({ path: id })

// Make sure virtual module importer can be resolve
importer =
importer && virtualModuleRE.test(importer)
? importer.replace(virtualModulePrefix, '')
: importer

// html types: extract script contents -----------------------------------
if (htmlTypesRE.test(id)) {
const resolved = await resolve(id, importer)
if (!resolved) return
// It is possible for the scanner to scan html types in node_modules.
// If we can optimize this html type, skip it so it's handled by the
// bare import resolve, and recorded as optimization dep.
if (
isInNodeModules(resolved) &&
isOptimizable(resolved, optimizeDepsOptions)
)
return
return resolved
}
const resolved = await resolve(id, importer, {
custom: {
depScan: importer ? { loader: scripts[importer]?.loader } : {},
},
})
if (resolved) {
if (shouldExternalizeDep(resolved, id)) {

// bare imports: record and externalize ----------------------------------
// avoid matching windows volume
if (/^[\w@][^:]/.test(id)) {
if (moduleListContains(exclude, id)) {
return externalUnlessEntry({ path: id })
}
if (isInNodeModules(resolved) || include?.includes(id)) {
// dependency or forced included, externalize and stop crawling
if (isOptimizable(resolved, optimizeDepsOptions)) {
depImports[id] = resolved
}
if (depImports[id]) {
return externalUnlessEntry({ path: id })
} else if (isScannable(resolved, optimizeDepsOptions.extensions)) {
// linked package, keep crawling
return path.resolve(resolved)
}
const resolved = await resolve(id, importer, {
custom: {
depScan: importer ? { loader: scripts[importer]?.loader } : {},
},
})
if (resolved) {
if (shouldExternalizeDep(resolved, id)) {
return externalUnlessEntry({ path: id })
}
if (isInNodeModules(resolved) || include?.includes(id)) {
// dependency or forced included, externalize and stop crawling
if (isOptimizable(resolved, optimizeDepsOptions)) {
depImports[id] = resolved
}
return externalUnlessEntry({ path: id })
} else if (isScannable(resolved, optimizeDepsOptions.extensions)) {
// linked package, keep crawling
return path.resolve(resolved)
} else {
return externalUnlessEntry({ path: id })
}
} else {
return externalUnlessEntry({ path: id })
missing[id] = normalizePath(importer!)
}
} else {
missing[id] = normalizePath(importer!)
}
}

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

// css
if (CSS_LANGS_RE.test(id)) {
return externalUnlessEntry({ path: id })
}

// json & wasm
if (/\.(?:json|json5|wasm)$/.test(id)) {
return externalUnlessEntry({ path: id })
}

// known asset types
if (ASSET_TYPE_RE.test(id)) {
return externalUnlessEntry({ path: id })
}

// known vite query types: ?worker, ?raw
if (SPECIAL_QUERY_RE.test(id)) {
return {
id,
external: true,
}
}

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

// use vite resolver to support urls and omitted extensions
const resolved = await resolve(id, importer, {
custom: {
depScan: importer ? { loader: scripts[importer]?.loader } : {},
},
})
if (resolved) {
if (
shouldExternalizeDep(resolved, id) ||
!isScannable(resolved, optimizeDepsOptions.extensions)
) {
// css
if (CSS_LANGS_RE.test(id)) {
return externalUnlessEntry({ path: id })
}
return path.resolve(cleanUrl(resolved))
}

// resolve failed... probably unsupported type
return externalUnlessEntry({ path: id })
},
async load(id) {
if (virtualModuleRE.test(id)) {
const script = scripts[id.replace(virtualModulePrefix, '')]
return {
code: script.contents,
moduleType: script.loader,
// json & wasm
if (/\.(?:json|json5|wasm)$/.test(id)) {
return externalUnlessEntry({ path: id })
}
}

// extract scripts inside HTML-like files and treat it as a js module
if (htmlTypesRE.test(id)) {
return {
code: await htmlTypeOnLoadCallback(id),
moduleType: 'js',
// known asset types
if (ASSET_TYPE_RE.test(id)) {
return externalUnlessEntry({ path: id })
}
}

// for jsx/tsx, we need to access the content and check for
// presence of import.meta.glob, since it results in import relationships
// but isn't crawled by esbuild.
if (JS_TYPES_RE.test(id)) {
let ext = path.extname(id).slice(1)
if (ext === 'mjs') ext = 'js'

const esbuildConfig = environment.config.esbuild
let contents = await fsp.readFile(id, 'utf-8')
if (ext.endsWith('x') && esbuildConfig && esbuildConfig.jsxInject) {
contents = esbuildConfig.jsxInject + `\n` + contents
// known vite query types: ?worker, ?raw
if (SPECIAL_QUERY_RE.test(id)) {
return {
id,
external: true,
}
}

const loader = ext as 'js' | 'ts' | 'jsx' | 'tsx'
// catch all -------------------------------------------------------------

if (contents.includes('import.meta.glob')) {
return {
moduleType: 'js',
code: await doTransformGlobImport(contents, id, loader),
// use vite resolver to support urls and omitted extensions
const resolved = await resolve(id, importer, {
custom: {
depScan: importer ? { loader: scripts[importer]?.loader } : {},
},
})
if (resolved) {
if (
shouldExternalizeDep(resolved, id) ||
!isScannable(resolved, optimizeDepsOptions.extensions)
) {
return externalUnlessEntry({ path: id })
}
return path.resolve(cleanUrl(resolved))
}

return {
moduleType: loader,
code: contents,
}
}
// resolve failed... probably unsupported type
return externalUnlessEntry({ path: id })
},
},
}
{
name: 'vite:dep-scan:load:html',
load: {
filter: { id: [virtualModuleRE, htmlTypesRE] },
async handler(id) {
if (virtualModuleRE.test(id)) {
const script = scripts[id.replace(virtualModulePrefix, '')]
return {
code: script.contents,
moduleType: script.loader,
}
}

// extract scripts inside HTML-like files and treat it as a js module
if (htmlTypesRE.test(id)) {
return {
code: await htmlTypeOnLoadCallback(id),
moduleType: 'js',
}
}
},
},
},
// for jsx/tsx, we need to access the content and check for
// presence of import.meta.glob, since it results in import relationships
// but isn't crawled by esbuild.
...(environment.config.esbuild && environment.config.esbuild.jsxInject
? [
{
name: 'vite:dep-scan:transform:jsx-inject',
transform: {
filter: {
id: /\.[jt]sx$/,
},
handler(code) {
const esbuildConfig = environment.config.esbuild
if (esbuildConfig && esbuildConfig.jsxInject) {
code = esbuildConfig.jsxInject + `\n` + code
}
return code
},
},
} satisfies Plugin,
]
: []),
{
name: 'vite:dep-scan:transform:js-glob',
transform: {
filter: {
code: 'import.meta.glob',
},
async handler(code, id) {
if (JS_TYPES_RE.test(id)) {
let ext = path.extname(id).slice(1)
if (ext === 'mjs') ext = 'js'
const loader = ext as 'js' | 'ts' | 'jsx' | 'tsx'
return {
moduleType: 'js',
code: await doTransformGlobImport(code, id, loader),
}
}
},
},
},
]
}

/**
Expand Down

0 comments on commit cf0154f

Please sign in to comment.