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 ""