diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 3c592cec499de5..c762b6b584cf8f 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -205,8 +205,11 @@ async function prepareEsbuildScanner( const plugin = esbuildScanPlugin(config, container, deps, missing, entries) - const { plugins = [], ...esbuildOptions } = - config.optimizeDeps?.esbuildOptions ?? {} + const { + plugins = [], + tsconfigRaw, + ...esbuildOptions + } = config.optimizeDeps?.esbuildOptions ?? {} return await esbuild.context({ absWorkingDir: process.cwd(), @@ -219,6 +222,16 @@ async function prepareEsbuildScanner( format: 'esm', logLevel: 'silent', plugins: [...plugins, plugin], + tsconfigRaw: + typeof tsconfigRaw === 'string' + ? tsconfigRaw + : { + ...tsconfigRaw, + compilerOptions: { + experimentalDecorators: true, + ...tsconfigRaw?.compilerOptions, + }, + }, ...esbuildOptions, }) } diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index fedb39d9d844a1..128d14f6ce1cd0 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -142,6 +142,14 @@ export async function transformWithEsbuild( compilerOptions.useDefineForClassFields = false } + // esbuild v0.18 only transforms decorators when `experimentalDecorators` is set to `true`. + // To preserve compat with the esbuild breaking change, we set `experimentalDecorators` to + // `true` by default if it's unset. + // TODO: Remove this in Vite 5 + if (compilerOptions.experimentalDecorators === undefined) { + compilerOptions.experimentalDecorators = true + } + // esbuild uses tsconfig fields when both the normal options and tsconfig was set // but we want to prioritize the normal options if (options) { diff --git a/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts b/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts index a5e2dd47d191cd..dd130a65291001 100644 --- a/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts +++ b/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts @@ -67,4 +67,16 @@ describe('transformWithEsbuild', () => { 'import { MainTypeOnlyClass } from "./not-used-type";', ) }) + + test('experimentalDecorators', async () => { + const main = path.resolve(__dirname, '../src/decorator.ts') + const mainContent = fs.readFileSync(main, 'utf-8') + // Should not error when transpiling decorators + // TODO: In Vite 5, this should require setting `tsconfigRaw.experimentalDecorators` + // or via the closest `tsconfig.json` + const result = await transformWithEsbuild(mainContent, main, { + target: 'es2020', + }) + expect(result.code).toContain('__decorateClass') + }) }) diff --git a/playground/tsconfig-json/src/decorator.ts b/playground/tsconfig-json/src/decorator.ts new file mode 100644 index 00000000000000..2dc056ec09c809 --- /dev/null +++ b/playground/tsconfig-json/src/decorator.ts @@ -0,0 +1,11 @@ +function first() { + return function (...args: any[]) {} +} + +export class Foo { + @first() + // @ts-expect-error we intentionally not enable `experimentalDecorators` to test esbuild compat + method(@first test: string) { + return test + } +} diff --git a/playground/tsconfig-json/src/main.ts b/playground/tsconfig-json/src/main.ts index 6ae1fe03b7d023..bec5c6bcc2729d 100644 --- a/playground/tsconfig-json/src/main.ts +++ b/playground/tsconfig-json/src/main.ts @@ -1,6 +1,7 @@ // @ts-nocheck import '../nested/main' import '../nested-with-extends/main' +import './decorator' // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { MainTypeOnlyClass } from './not-used-type'