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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(esbuild)!: remove esbuild 0.17 -> 0.18 compat #14804

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 25 additions & 0 deletions docs/guide/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Some configuration fields under `compilerOptions` in `tsconfig.json` require spe

#### `isolatedModules`

- [TypeScript documentation](https://www.typescriptlang.org/tsconfig#isolatedModules)

Should be set to `true`.

It is because `esbuild` only performs transpilation without type information, it doesn't support certain features like const enum and implicit type-only imports.
Expand All @@ -67,8 +69,12 @@ However, some libraries (e.g. [`vue`](https://github.com/vuejs/core/issues/1228)

#### `useDefineForClassFields`

- [TypeScript documentation](https://www.typescriptlang.org/tsconfig#useDefineForClassFields)

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.

Other TypeScript targets will default to `false`.

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).

Expand All @@ -78,13 +84,32 @@ Most libraries expect `"useDefineForClassFields": true`, such as [MobX](https://

But a few libraries haven't transitioned to this new default yet, including [`lit-element`](https://github.com/lit/lit-element/issues/1030). Please explicitly set `useDefineForClassFields` to `false` in these cases.

#### `target`

- [TypeScript documentation](https://www.typescriptlang.org/tsconfig#target)

Vite does not transpile TypeScript with the configured `target` value by default, following the same behaviour as `esbuild`.

The [`esbuild.target`](/config/shared-options.html#esbuild) option can be used instead, which defaults to `esnext` for minimal transpilation. In builds, the [`build.target`](/config/build-options.html#build-target) option takes higher priority and can also be set if needed.

::: warning `useDefineForClassFields`
If `target` is not `ESNext` or `ES2022` or newer, or if there's no `tsconfig.json` file, `useDefineForClassFields` will default to `false` which can be problematic with the default `esbuild.target` value of `esnext`. It may transpile to [static initialization blocks](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks#browser_compatibility) which may not be supported in your browser.

As such, it is recommended to set `target` to `ESNext` or `ES2022` or newer, or set `useDefineForClassFields` to `true` explicitly when configuring `tsconfig.json`.
:::

#### Other Compiler Options Affecting the Build Result

- [`extends`](https://www.typescriptlang.org/tsconfig#extends)
- [`importsNotUsedAsValues`](https://www.typescriptlang.org/tsconfig#importsNotUsedAsValues)
- [`preserveValueImports`](https://www.typescriptlang.org/tsconfig#preserveValueImports)
- [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax)
- [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
- [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
- [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)
- [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)
- [`experimentalDecorators`](https://www.typescriptlang.org/tsconfig#experimentalDecorators)
- [`alwaysStrict`](https://www.typescriptlang.org/tsconfig#alwaysStrict)

If migrating your codebase to `"isolatedModules": true` is an insurmountable effort, you may be able to get around it with a third-party plugin such as [rollup-plugin-friendly-type-imports](https://www.npmjs.com/package/rollup-plugin-friendly-type-imports). However, this approach is not officially supported by Vite.

Expand Down
25 changes: 25 additions & 0 deletions docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ CLI shortcuts, like `r` to restart the dev server, now require an additional `En

This change prevents Vite from swallowing and controlling OS-specific shortcuts, allowing better compatibility when combining the Vite dev server with other processes, and avoids the [previous caveats](https://github.com/vitejs/vite/pull/14342).

### Update `experimentalDecorators` and `useDefineForClassFields` TypeScript behaviour

Vite 5 uses esbuild 0.19 and removes the compatibility layer for esbuild 0.18, which changes how `experimentalDecorators` and `useDefineForClassFields` are handled.

- **`experimentalDecorators` is not enabled by default**

You need to set `compilerOptions.experimentalDecorators` to `true` in `tsconfig.json` to use decorators.

- **`useDefineForClassFields` defaults depend on the TypeScript `target` value**

If `target` is not `ESNext` or `ES2022` or newer, or if there's no `tsconfig.json` file, `useDefineForClassFields` will default to `false` which can be problematic with the default `esbuild.target` value of `esnext`. It may transpile to [static initialization blocks](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks#browser_compatibility) which may not be supported in your browser.

As such, it is recommended to set `target` to `ESNext` or `ES2022` or newer, or set `useDefineForClassFields` to `true` explicitly when configuring `tsconfig.json`.

```jsonc
{
"compilerOptions": {
// Set true if you use decorators
"experimentalDecorators": true,
// Set true if you see parsing errors in your browser
"useDefineForClassFields": true
}
}
```

### Remove `--https` flag and `https: true`

`--https` flag sets `https: true`. This config was meant to be used together with the automatic https certification generation feature which [was dropped in Vite 3](https://v3.vitejs.dev/guide/migration.html#automatic-https-certificate-generation). This config no longer makes sense as it will make Vite start a HTTPS server without a certificate.
Expand Down
15 changes: 0 additions & 15 deletions packages/vite/src/node/__tests__/plugins/esbuild.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,21 +383,6 @@ describe('transformWithEsbuild', () => {
const actual = await transformClassCode('es2022', {})
expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
})

test('useDefineForClassFields: false and static property should not be transpile to static block', async () => {
const result = await transformWithEsbuild(
`
class foo {
static bar = 'bar'
}
`,
'bar.ts',
{
target: 'esnext',
},
)
expect(result?.code).not.toContain('static {')
})
})
})

Expand Down
12 changes: 3 additions & 9 deletions packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
import { transformWithEsbuild } from '../plugins/esbuild'
import { ESBUILD_MODULES_TARGET } from '../constants'
import { esbuildCjsExternalPlugin, esbuildDepPlugin } from './esbuildDepPlugin'
import { resolveTsconfigRaw, scanImports } from './scan'
import { scanImports } from './scan'
import { createOptimizeDepsIncludeResolver, expandGlobIds } from './resolve'
export {
initDepsOptimizer,
Expand Down Expand Up @@ -730,12 +730,8 @@ async function prepareEsbuildOptimizerRun(

const optimizeDeps = getDepOptimizationConfig(config, ssr)

const {
plugins: pluginsFromConfig = [],
tsconfig,
tsconfigRaw,
...esbuildOptions
} = optimizeDeps?.esbuildOptions ?? {}
const { plugins: pluginsFromConfig = [], ...esbuildOptions } =
optimizeDeps?.esbuildOptions ?? {}

await Promise.all(
Object.keys(depsInfo).map(async (id) => {
Expand Down Expand Up @@ -826,8 +822,6 @@ async function prepareEsbuildOptimizerRun(
metafile: true,
plugins,
charset: 'utf8',
tsconfig,
tsconfigRaw: resolveTsconfigRaw(tsconfig, tsconfigRaw),
...esbuildOptions,
supported: {
'dynamic-import': true,
Expand Down
30 changes: 2 additions & 28 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { performance } from 'node:perf_hooks'
import glob from 'fast-glob'
import type {
BuildContext,
BuildOptions,
Loader,
OnLoadArgs,
OnLoadResult,
Expand Down Expand Up @@ -214,12 +213,8 @@ async function prepareEsbuildScanner(

const plugin = esbuildScanPlugin(config, container, deps, missing, entries)

const {
plugins = [],
tsconfig,
tsconfigRaw,
...esbuildOptions
} = config.optimizeDeps?.esbuildOptions ?? {}
const { plugins = [], ...esbuildOptions } =
config.optimizeDeps?.esbuildOptions ?? {}

return await esbuild.context({
absWorkingDir: process.cwd(),
Expand All @@ -232,8 +227,6 @@ async function prepareEsbuildScanner(
format: 'esm',
logLevel: 'silent',
plugins: [...plugins, plugin],
tsconfig,
tsconfigRaw: resolveTsconfigRaw(tsconfig, tsconfigRaw),
...esbuildOptions,
})
}
Expand Down Expand Up @@ -684,22 +677,3 @@ function isScannable(id: string, extensions: string[] | undefined): boolean {
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 and check https://github.com/vitejs/vite/pull/13805#issuecomment-1633612320
export function resolveTsconfigRaw(
tsconfig: string | undefined,
tsconfigRaw: BuildOptions['tsconfigRaw'],
): BuildOptions['tsconfigRaw'] {
return tsconfig || typeof tsconfigRaw === 'string'
? tsconfigRaw
: {
...tsconfigRaw,
compilerOptions: {
experimentalDecorators: true,
...tsconfigRaw?.compilerOptions,
},
}
}
30 changes: 6 additions & 24 deletions packages/vite/src/node/plugins/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export async function transformWithEsbuild(
}

let tsconfigRaw = options?.tsconfigRaw
const fallbackSupported: Record<string, boolean> = {}

// if options provide tsconfigRaw in string, it takes highest precedence
if (typeof tsconfigRaw !== 'string') {
Expand Down Expand Up @@ -140,23 +139,6 @@ 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
}

// Compat with esbuild 0.17 where static properties are transpiled to
// static blocks when `useDefineForClassFields` is false. Its support
// is not great yet, so temporarily disable it for now.
// TODO: Remove this in Vite 5, don't pass hardcoded `esnext` target
// to `transformWithEsbuild` in the esbuild plugin.
if (compilerOptions.useDefineForClassFields !== true) {
fallbackSupported['class-static-blocks'] = false
}

// esbuild uses tsconfig fields when both the normal options and tsconfig was set
// but we want to prioritize the normal options
if (options) {
Expand All @@ -179,10 +161,6 @@ export async function transformWithEsbuild(
...options,
loader,
tsconfigRaw,
supported: {
...fallbackSupported,
...options?.supported,
},
}

// Some projects in the ecosystem are calling this function with an ESBuildOptions
Expand Down Expand Up @@ -220,9 +198,13 @@ export async function transformWithEsbuild(
if (e.errors) {
e.frame = ''
e.errors.forEach((m: Message) => {
if (m.text === 'Experimental decorators are not currently enabled') {
if (
m.text === 'Experimental decorators are not currently enabled' ||
m.text ===
'Parameter decorators only work when experimental decorators are enabled'
) {
m.text +=
'. Vite 4.4+ now uses esbuild 0.18 and you need to enable them by adding "experimentalDecorators": true in your "tsconfig.json" file.'
'. Vite 5 now uses esbuild 0.18 and you need to enable them by adding "experimentalDecorators": true in your "tsconfig.json" file.'
}
e.frame += `\n` + prettifyMessage(m, code)
})
Expand Down
5 changes: 2 additions & 3 deletions playground/tsconfig-json/__tests__/tsconfig-json.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,8 @@ describe('transformWithEsbuild', () => {
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`
// Should not error when transpiling decorators as nearest tsconfig.json
// has "experimentalDecorators": true
const result = await transformWithEsbuild(mainContent, main, {
target: 'es2020',
})
Expand Down
3 changes: 2 additions & 1 deletion playground/tsconfig-json/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"noImplicitReturns": true,

"useDefineForClassFields": true,
"importsNotUsedAsValues": "preserve"
"importsNotUsedAsValues": "preserve",
"experimentalDecorators": true
},
"include": ["./src"]
}