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: do less on js side for optimizer #76

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading