diff --git a/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts b/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts
index 94a3dc24665101..33cd73fa18b67c 100644
--- a/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts
+++ b/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts
@@ -1,4 +1,4 @@
-import { getColor } from '../../testUtils'
+import { getColor, isBuild } from '../../testUtils'
test('default + named imports from cjs dep (react)', async () => {
expect(await page.textContent('.cjs button')).toBe('count is 0')
@@ -61,3 +61,9 @@ test('dep w/ non-js files handled via plugin', async () => {
test('vue + vuex', async () => {
expect(await page.textContent('.vue')).toMatch(`[success]`)
})
+
+test('esbuild-plugin', async () => {
+ expect(await page.textContent('.esbuild-plugin')).toMatch(
+ isBuild ? `Hello from a package` : `Hello from an esbuild plugin`
+ )
+})
diff --git a/packages/playground/optimize-deps/dep-esbuild-plugin-transform/index.js b/packages/playground/optimize-deps/dep-esbuild-plugin-transform/index.js
new file mode 100644
index 00000000000000..eeec9ced7ab0d1
--- /dev/null
+++ b/packages/playground/optimize-deps/dep-esbuild-plugin-transform/index.js
@@ -0,0 +1,3 @@
+// will be replaced by an esbuild plugin
+
+export const hello = () => `Hello from a package`
diff --git a/packages/playground/optimize-deps/dep-esbuild-plugin-transform/package.json b/packages/playground/optimize-deps/dep-esbuild-plugin-transform/package.json
new file mode 100644
index 00000000000000..883bc5176e91c0
--- /dev/null
+++ b/packages/playground/optimize-deps/dep-esbuild-plugin-transform/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "dep-esbuild-plugin-transform",
+ "version": "0.0.0",
+ "main": "index.js"
+}
diff --git a/packages/playground/optimize-deps/index.html b/packages/playground/optimize-deps/index.html
index b45bdd61de446f..1bf9be5ca2d2e2 100644
--- a/packages/playground/optimize-deps/index.html
+++ b/packages/playground/optimize-deps/index.html
@@ -40,6 +40,9 @@
Dep w/ special file format supported via plugins
Vue & Vuex
+Dep with changes from esbuild plugin
+This should show a greeting:
+
diff --git a/packages/playground/optimize-deps/package.json b/packages/playground/optimize-deps/package.json
index db3a24ee39b068..a9644619d74994 100644
--- a/packages/playground/optimize-deps/package.json
+++ b/packages/playground/optimize-deps/package.json
@@ -14,6 +14,7 @@
"dep-cjs-named-only": "link:./dep-cjs-named-only",
"dep-linked": "link:./dep-linked",
"dep-linked-include": "link:./dep-linked-include",
+ "dep-esbuild-plugin-transform": "link:./dep-esbuild-plugin-transform",
"phoenix": "^1.5.7",
"react": "^17.0.1",
"react-dom": "^17.0.1",
diff --git a/packages/playground/optimize-deps/vite.config.js b/packages/playground/optimize-deps/vite.config.js
index 162d9d0fdc0102..ff9f95c92f808c 100644
--- a/packages/playground/optimize-deps/vite.config.js
+++ b/packages/playground/optimize-deps/vite.config.js
@@ -9,8 +9,27 @@ module.exports = {
},
optimizeDeps: {
- include: ['dep-linked-include'],
- plugins: [vue()]
+ include: [
+ 'dep-linked-include',
+ // required since it isn't in node_modules and is ignored by the optimizer otherwise
+ 'dep-esbuild-plugin-transform'
+ ],
+ esbuildOptions: {
+ plugins: [
+ {
+ name: 'replace-a-file',
+ setup(build) {
+ build.onLoad(
+ { filter: /dep-esbuild-plugin-transform(\\|\/)index\.js$/ },
+ () => ({
+ contents: `export const hello = () => 'Hello from an esbuild plugin'`,
+ loader: 'js'
+ })
+ )
+ }
+ }
+ ]
+ }
},
build: {
diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts
index 1e2dc6fce8124e..450e74aeee4bb5 100644
--- a/packages/vite/src/node/__tests__/config.spec.ts
+++ b/packages/vite/src/node/__tests__/config.spec.ts
@@ -1,4 +1,5 @@
-import { mergeConfig, UserConfigExport } from '../config'
+import { InlineConfig } from '..'
+import { mergeConfig, resolveConfig, UserConfigExport } from '../config'
describe('mergeConfig', () => {
test('handles configs with different alias schemas', () => {
@@ -92,3 +93,49 @@ describe('mergeConfig', () => {
expect(mergeConfig(baseConfig, newConfig)).toEqual(mergedConfig)
})
})
+
+describe('resolveConfig', () => {
+ beforeAll(() => {
+ // silence deprecation warning
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
+ })
+
+ afterAll(() => {
+ jest.clearAllMocks()
+ })
+
+ test('copies optimizeDeps.keepNames to esbuildOptions.keepNames', async () => {
+ const config: InlineConfig = {
+ optimizeDeps: {
+ keepNames: false
+ }
+ }
+
+ expect(await resolveConfig(config, 'serve')).toMatchObject({
+ optimizeDeps: {
+ esbuildOptions: {
+ keepNames: false
+ }
+ }
+ })
+ })
+
+ test('uses esbuildOptions.keepNames if set', async () => {
+ const config: InlineConfig = {
+ optimizeDeps: {
+ keepNames: true,
+ esbuildOptions: {
+ keepNames: false
+ }
+ }
+ }
+
+ expect(await resolveConfig(config, 'serve')).toMatchObject({
+ optimizeDeps: {
+ esbuildOptions: {
+ keepNames: false
+ }
+ }
+ })
+ })
+})
diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts
index b486429895fe6f..10f8919952d04a 100644
--- a/packages/vite/src/node/config.ts
+++ b/packages/vite/src/node/config.ts
@@ -173,7 +173,10 @@ export interface InlineConfig extends UserConfig {
}
export type ResolvedConfig = Readonly<
- Omit & {
+ Omit<
+ UserConfig,
+ 'plugins' | 'alias' | 'dedupe' | 'assetsInclude' | 'optimizeDeps'
+ > & {
configFile: string | undefined
configFileDependencies: string[]
inlineConfig: InlineConfig
@@ -193,6 +196,7 @@ export type ResolvedConfig = Readonly<
assetsInclude: (file: string) => boolean
logger: Logger
createResolver: (options?: Partial) => ResolveFn
+ optimizeDeps: Omit
}
>
@@ -381,10 +385,17 @@ export async function resolveConfig(
return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)
},
logger,
- createResolver
+ createResolver,
+ optimizeDeps: {
+ ...config.optimizeDeps,
+ esbuildOptions: {
+ keepNames: config.optimizeDeps?.keepNames,
+ ...config.optimizeDeps?.esbuildOptions
+ }
+ }
}
- ;(resolved as any).plugins = await resolvePlugins(
+ ;(resolved.plugins as Plugin[]) = await resolvePlugins(
resolved,
prePlugins,
normalPlugins,
@@ -466,6 +477,24 @@ export async function resolveConfig(
}
})
+ if (config.optimizeDeps?.keepNames) {
+ logDeprecationWarning(
+ 'optimizeDeps.keepNames',
+ 'Use "optimizeDeps.esbuildOptions.keepNames" instead.'
+ )
+ }
+ Object.defineProperty(resolved.optimizeDeps, 'keepNames', {
+ enumerable: false,
+ get() {
+ logDeprecationWarning(
+ 'optimizeDeps.keepNames',
+ 'Use "optimizeDeps.esbuildOptions.keepNames" instead.',
+ new Error()
+ )
+ return resolved.optimizeDeps.esbuildOptions?.keepNames
+ }
+ })
+
return resolved
}
diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts
index b9efde3986980f..0d054ba5387978 100644
--- a/packages/vite/src/node/optimizer/index.ts
+++ b/packages/vite/src/node/optimizer/index.ts
@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import chalk from 'chalk'
import { createHash } from 'crypto'
-import { build } from 'esbuild'
+import { build, BuildOptions as EsbuildBuildOptions } from 'esbuild'
import { ResolvedConfig } from '../config'
import {
createDebugger,
@@ -47,9 +47,32 @@ export interface DepOptimizationOptions {
*/
exclude?: string[]
/**
- * The bundler sometimes needs to rename symbols to avoid collisions.
- * Set this to `true` to keep the `name` property on functions and classes.
- * https://esbuild.github.io/api/#keep-names
+ * Options to pass to esbuild during the dep scanning and optimization
+ *
+ * Certain options are omitted since changing them would not be compatible
+ * with Vite's dep optimization.
+ *
+ * - `external` is also omitted, use Vite's `optimizeDeps.exclude` option
+ * - `plugins` are merged with Vite's dep plugin
+ * - `keepNames` takes precedence over the deprecated `optimizeDeps.keepNames`
+ *
+ * https://esbuild.github.io/api
+ */
+ esbuildOptions?: Omit<
+ EsbuildBuildOptions,
+ | 'bundle'
+ | 'entryPoints'
+ | 'external'
+ | 'write'
+ | 'watch'
+ | 'outdir'
+ | 'outfile'
+ | 'outbase'
+ | 'outExtension'
+ | 'metafile'
+ >
+ /**
+ * @deprecated use `esbuildOptions.keepNames`
*/
keepNames?: boolean
}
@@ -233,10 +256,12 @@ export async function optimizeDeps(
const start = Date.now()
+ const { plugins = [], ...esbuildOptions } =
+ config.optimizeDeps?.esbuildOptions ?? {}
+
const result = await build({
entryPoints: Object.keys(flatIdDeps),
bundle: true,
- keepNames: config.optimizeDeps?.keepNames,
format: 'esm',
external: config.optimizeDeps?.exclude,
logLevel: 'error',
@@ -246,7 +271,11 @@ export async function optimizeDeps(
treeShaking: 'ignore-annotations',
metafile: true,
define,
- plugins: [esbuildDepPlugin(flatIdDeps, flatIdToExports, config)]
+ plugins: [
+ ...plugins,
+ esbuildDepPlugin(flatIdDeps, flatIdToExports, config)
+ ],
+ ...esbuildOptions
})
const meta = result.metafile!
diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts
index c5cd9d19aa12bb..3383e88058c3a0 100644
--- a/packages/vite/src/node/optimizer/scan.ts
+++ b/packages/vite/src/node/optimizer/scan.ts
@@ -89,6 +89,9 @@ export async function scanImports(
const container = await createPluginContainer(config)
const plugin = esbuildScanPlugin(config, container, deps, missing, entries)
+ const { plugins = [], ...esbuildOptions } =
+ config.optimizeDeps?.esbuildOptions ?? {}
+
await Promise.all(
entries.map((entry) =>
build({
@@ -97,7 +100,8 @@ export async function scanImports(
bundle: true,
format: 'esm',
logLevel: 'error',
- plugins: [plugin]
+ plugins: [...plugins, plugin],
+ ...esbuildOptions
})
)
)
diff --git a/yarn.lock b/yarn.lock
index 5b58daa8cff9f5..252621bb86d0a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2707,6 +2707,10 @@ delegate@^3.1.2:
version "0.0.0"
uid ""
+"dep-esbuild-plugin-transform@link:./packages/playground/optimize-deps/dep-esbuild-plugin-transform":
+ version "0.0.0"
+ uid ""
+
"dep-import-type@link:./packages/playground/ssr-vue/dep-import-type":
version "0.0.0"
uid ""