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

feat: optimize custom extensions #6801

Merged
merged 8 commits into from Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/vite/src/node/optimizer/esbuildDepPlugin.ts
@@ -1,5 +1,5 @@
import path from 'path'
import type { Loader, Plugin, ImportKind } from 'esbuild'
import type { Plugin, ImportKind } from 'esbuild'
import { KNOWN_ASSET_TYPES } from '../constants'
import type { ResolvedConfig } from '..'
import {
Expand Down Expand Up @@ -40,6 +40,12 @@ export function esbuildDepPlugin(
config: ResolvedConfig,
ssr?: boolean
): Plugin {
const allExternalTypes = config.optimizeDeps.supportedExtensions
? externalTypes.filter(
(type) => !config.optimizeDeps.supportedExtensions?.includes('.' + type)
)
: externalTypes

// default resolver which prefers ESM
const _resolve = config.createResolver({ asSrc: false })

Expand Down Expand Up @@ -74,7 +80,7 @@ export function esbuildDepPlugin(
// externalize assets and commonly known non-js file types
build.onResolve(
{
filter: new RegExp(`\\.(` + externalTypes.join('|') + `)(\\?.*)?$`)
filter: new RegExp(`\\.(` + allExternalTypes.join('|') + `)(\\?.*)?$`)
},
async ({ path: id, importer, kind }) => {
const resolved = await resolve(id, importer, kind)
Expand Down Expand Up @@ -181,10 +187,8 @@ export function esbuildDepPlugin(
}
}

let ext = path.extname(entryFile).slice(1)
if (ext === 'mjs') ext = 'js'
return {
loader: ext as Loader,
loader: 'js',
bluwy marked this conversation as resolved.
Show resolved Hide resolved
contents,
resolveDir: root
}
Expand Down
62 changes: 43 additions & 19 deletions packages/vite/src/node/optimizer/index.ts
Expand Up @@ -79,6 +79,12 @@ export interface DepOptimizationOptions {
* @deprecated use `esbuildOptions.keepNames`
*/
keepNames?: boolean
/**
* List of file extensions that can be optimized. A corresponding esbuild
* plugin must exist to handle the specific extension.
* @experimental
*/
supportedExtensions?: string[]
bluwy marked this conversation as resolved.
Show resolved Hide resolved
}

export interface DepOptimizationMetadata {
Expand Down Expand Up @@ -244,29 +250,47 @@ export async function optimizeDeps(
for (const id in deps) {
const flatId = flattenId(id)
const filePath = (flatIdDeps[flatId] = deps[id])
const entryContent = fs.readFileSync(filePath, 'utf-8')
let exportsData: ExportsData
try {
exportsData = parse(entryContent) as ExportsData
} catch {
debug(
`Unable to parse dependency: ${id}. Trying again with a JSX transform.`
if (
config.optimizeDeps.supportedExtensions?.some((ext) =>
filePath.endsWith(ext)
)
const transformed = await transformWithEsbuild(entryContent, filePath, {
loader: 'jsx'
) {
// For custom supported extensions, build the entry file to transform it into JS,
// and then parse with es-module-lexer. Note that the `bundle` option is not `true`,
// so only the entry file is being transformed.
const result = await build({
...esbuildOptions,
plugins,
entryPoints: [filePath],
write: false,
format: 'esm'
})
// Ensure that optimization won't fail by defaulting '.js' to the JSX parser.
// This is useful for packages such as Gatsby.
esbuildOptions.loader = {
'.js': 'jsx',
...esbuildOptions.loader
exportsData = parse(result.outputFiles[0].text) as ExportsData
} else {
const entryContent = fs.readFileSync(filePath, 'utf-8')
try {
exportsData = parse(entryContent) as ExportsData
} catch {
debug(
`Unable to parse dependency: ${id}. Trying again with a JSX transform.`
)
const transformed = await transformWithEsbuild(entryContent, filePath, {
loader: 'jsx'
})
// Ensure that optimization won't fail by defaulting '.js' to the JSX parser.
// This is useful for packages such as Gatsby.
esbuildOptions.loader = {
'.js': 'jsx',
...esbuildOptions.loader
}
exportsData = parse(transformed.code) as ExportsData
}
exportsData = parse(transformed.code) as ExportsData
}
for (const { ss, se } of exportsData[0]) {
const exp = entryContent.slice(ss, se)
if (/export\s+\*\s+from/.test(exp)) {
exportsData.hasReExports = true
for (const { ss, se } of exportsData[0]) {
const exp = entryContent.slice(ss, se)
if (/export\s+\*\s+from/.test(exp)) {
exportsData.hasReExports = true
}
}
}
idToExports[id] = exportsData
Expand Down
14 changes: 12 additions & 2 deletions packages/vite/src/node/optimizer/scan.ts
Expand Up @@ -181,6 +181,10 @@ function esbuildScanPlugin(
'@vite/env'
]

const canOptimize = (id: string) =>
OPTIMIZABLE_ENTRY_RE.test(id) ||
!!config.optimizeDeps.supportedExtensions?.some((ext) => id.endsWith(ext))

const externalUnlessEntry = ({ path }: { path: string }) => ({
path,
external: !entries.includes(path)
Expand Down Expand Up @@ -218,8 +222,14 @@ function esbuildScanPlugin(

// html types: extract script contents -----------------------------------
build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
const resolved = await resolve(path, 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 (resolved.includes('node_modules') && canOptimize(resolved)) return
bluwy marked this conversation as resolved.
Show resolved Hide resolved
return {
path: await resolve(path, importer),
path: resolved,
namespace: 'html'
}
})
Expand Down Expand Up @@ -340,7 +350,7 @@ function esbuildScanPlugin(
}
if (resolved.includes('node_modules') || include?.includes(id)) {
// dependency or forced included, externalize and stop crawling
if (OPTIMIZABLE_ENTRY_RE.test(resolved)) {
if (canOptimize(resolved)) {
depImports[id] = resolved
}
return externalUnlessEntry({ path: id })
Expand Down