diff --git a/docs/guide/features.md b/docs/guide/features.md index 60cad69a6ce0be..29878f59fa43b1 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -57,7 +57,7 @@ However, some libraries (e.g. [`vue`](https://github.com/vuejs/core/issues/1228) #### `useDefineForClassFields` -Starting from Vite 2.5.0, the default value will be `true` if the TypeScript target is `ESNext`. It is consistent with the [behavior of `tsc` 4.3.2 and later](https://github.com/microsoft/TypeScript/pull/42663). It is also the standard ECMAScript runtime behavior. +Starting from Vite 2.5.0, the default value will be `true` if the TypeScript target is `ESNext` or `ES2022` or newer. It is consistent with the [behavior of `tsc` 4.3.2 and later](https://github.com/microsoft/TypeScript/pull/42663). It is also the standard ECMAScript runtime behavior. But it may be counter-intuitive for those coming from other programming languages or older versions of TypeScript. You can read more about the transition in the [TypeScript 3.7 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier). diff --git a/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts b/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts index 22884162849a69..7956b35e4668f9 100644 --- a/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts @@ -1,7 +1,6 @@ import { describe, expect, test } from 'vitest' import type { ResolvedConfig, UserConfig } from '../../config' import { - ESBuildTransformResult, resolveEsbuildTranspileOptions, transformWithEsbuild, } from '../../plugins/esbuild' @@ -305,6 +304,81 @@ describe('transformWithEsbuild', () => { `/* @__PURE__ */ ${jsxFactory}(${jsxFragment}, null)`, ) }) + + describe('useDefineForClassFields', async () => { + const transformClassCode = async ( + target: string, + tsconfigCompilerOptions: { + target?: string + useDefineForClassFields?: boolean + }, + ) => { + const result = await transformWithEsbuild( + ` + class foo { + bar = 'bar' + } + `, + 'bar.ts', + { + target, + tsconfigRaw: { compilerOptions: tsconfigCompilerOptions }, + }, + ) + return result?.code + } + + const [ + defineForClassFieldsTrueTransformedCode, + defineForClassFieldsTrueLowerTransformedCode, + defineForClassFieldsFalseTransformedCode, + ] = await Promise.all([ + transformClassCode('esnext', { + useDefineForClassFields: true, + }), + transformClassCode('es2021', { + useDefineForClassFields: true, + }), + transformClassCode('esnext', { + useDefineForClassFields: false, + }), + ]) + + test('target: esnext and tsconfig.target: esnext => true', async () => { + const actual = await transformClassCode('esnext', { + target: 'esnext', + }) + expect(actual).toBe(defineForClassFieldsTrueTransformedCode) + }) + + test('target: es2021 and tsconfig.target: esnext => true', async () => { + const actual = await transformClassCode('es2021', { + target: 'esnext', + }) + expect(actual).toBe(defineForClassFieldsTrueLowerTransformedCode) + }) + + test('target: es2021 and tsconfig.target: es2021 => false', async () => { + const actual = await transformClassCode('es2021', { + target: 'es2021', + }) + expect(actual).toBe(defineForClassFieldsFalseTransformedCode) + }) + + test('target: esnext and tsconfig.target: es2021 => false', async () => { + const actual = await transformClassCode('esnext', { + target: 'es2021', + }) + expect(actual).toBe(defineForClassFieldsFalseTransformedCode) + }) + + test('target: es2022 and tsconfig.target: es2022 => true', async () => { + const actual = await transformClassCode('es2022', { + target: 'es2022', + }) + expect(actual).toBe(defineForClassFieldsTrueTransformedCode) + }) + }) }) /** diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index 0dc57c6328dee5..89e2dab2e54316 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -125,6 +125,23 @@ export async function transformWithEsbuild( ...tsconfigRaw?.compilerOptions, }, } + + const { compilerOptions } = tsconfigRaw + if (compilerOptions) { + // esbuild derives `useDefineForClassFields` from `target` instead of `tsconfig.compilerOptions.target` + // https://github.com/evanw/esbuild/issues/2584 + // but we want `useDefineForClassFields` to be derived from `tsconfig.compilerOptions.target` + if (compilerOptions.useDefineForClassFields === undefined) { + const lowercaseTarget = compilerOptions.target?.toLowerCase() ?? 'es3' + if (lowercaseTarget.startsWith('es')) { + const esVersion = lowercaseTarget.slice(2) + compilerOptions.useDefineForClassFields = + esVersion === 'next' || +esVersion >= 2022 + } else { + compilerOptions.useDefineForClassFields = false + } + } + } } const resolvedOptions = {