Skip to content

feat: enable all native plugins by default #168

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 12, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 157 additions & 8 deletions packages/vite/src/node/__tests__/plugins/define.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest'
import { rolldown } from 'rolldown'
import { definePlugin } from '../../plugins/define'
import { resolveConfig } from '../../config'
import { PartialEnvironment } from '../../baseEnvironment'
Expand All @@ -16,17 +17,48 @@ async function createDefinePluginTransform(
const environment = new PartialEnvironment(ssr ? 'ssr' : 'client', config)

return async (code: string) => {
// @ts-expect-error transform.handler should exist
const result = await instance.transform.handler.call(
{ environment },
code,
'foo.ts',
)
return result?.code || result
if (process.env._VITE_TEST_JS_PLUGIN) {
// @ts-expect-error transform.handler should exist
const result = await instance.transform.handler.call(
{ environment },
code,
'foo.ts',
)
return result?.code || result
} else {
const bundler = await rolldown({
input: 'entry.js',
plugins: [
{
name: 'test',
resolveId(id) {
if (id === 'entry.js') {
return '\0' + id
}
},
load(id) {
if (id === '\0entry.js') {
return code
}
},
},
{
name: 'native:define',
options: (definePlugin(config).options! as any).bind({
environment,
}),
},
],
experimental: {
attachDebugInfo: 'none',
},
})
return (await bundler.generate()).output[0].code
}
}
}

describe('definePlugin', () => {
describe.skipIf(!process.env._VITE_TEST_JS_PLUGIN)('definePlugin', () => {
test('replaces custom define', async () => {
const transform = await createDefinePluginTransform({
__APP_VERSION__: JSON.stringify('1.0'),
Expand Down Expand Up @@ -146,3 +178,120 @@ describe('definePlugin', () => {
)
})
})

describe.skipIf(process.env._VITE_TEST_JS_PLUGIN)('native definePlugin', () => {
test('replaces custom define', async () => {
const transform = await createDefinePluginTransform({
__APP_VERSION__: JSON.stringify('1.0'),
})
expect(await transform('export const version = __APP_VERSION__;')).toBe(
'const version = "1.0";\n\nexport { version };',
)
expect(await transform('export const version = __APP_VERSION__ ;')).toBe(
'const version = "1.0";\n\nexport { version };',
)
})

test('should not replace if not defined', async () => {
const transform = await createDefinePluginTransform({
__APP_VERSION__: JSON.stringify('1.0'),
})
expect(await transform('export const version = "1.0";')).toBe(
'const version = "1.0";\n\nexport { version };',
)
expect(
await transform('export const version = import.meta.SOMETHING'),
).toBe('const version = import.meta.SOMETHING;\n\nexport { version };')
})

test('replaces import.meta.env.SSR with false', async () => {
const transform = await createDefinePluginTransform()
expect(await transform('export const isSSR = import.meta.env.SSR;')).toBe(
'const isSSR = false;\n\nexport { isSSR };',
)
})

test('preserve import.meta.hot with override', async () => {
// assert that the default behavior is to replace import.meta.hot with undefined
const transform = await createDefinePluginTransform()
expect(await transform('export const hot = import.meta.hot;')).toBe(
'const hot = void 0;\n\nexport { hot };',
)
// assert that we can specify a user define to preserve import.meta.hot
const overrideTransform = await createDefinePluginTransform({
'import.meta.hot': 'import.meta.hot',
})
expect(await overrideTransform('export const hot = import.meta.hot;')).toBe(
'const hot = import.meta.hot;\n\nexport { hot };',
)
})

test('replace import.meta.env.UNKNOWN with undefined', async () => {
const transform = await createDefinePluginTransform()
expect(await transform('export const foo = import.meta.env.UNKNOWN;')).toBe(
'const foo = void 0;\n\nexport { foo };',
)
})

test('leave import.meta.env["UNKNOWN"] to runtime', async () => {
const transform = await createDefinePluginTransform()
expect(
await transform('export const foo = import.meta.env["UNKNOWN"];'),
).toMatch(/const foo = .*\["UNKNOWN"\];\n\nexport \{ foo \};/s)
})

test('preserve import.meta.env.UNKNOWN with override', async () => {
const transform = await createDefinePluginTransform({
'import.meta.env.UNKNOWN': 'import.meta.env.UNKNOWN',
})
expect(await transform('export const foo = import.meta.env.UNKNOWN;')).toBe(
'const foo = import.meta.env.UNKNOWN;\n\nexport { foo };',
)
})

test('replace import.meta.env when it is a invalid json', async () => {
const transform = await createDefinePluginTransform({
'import.meta.env.LEGACY': '__VITE_IS_LEGACY__',
})

expect(
await transform(
'export const isLegacy = import.meta.env.LEGACY;\nimport.meta.env.UNDEFINED && console.log(import.meta.env.UNDEFINED);',
),
).toMatchInlineSnapshot(
`"const isLegacy = __VITE_IS_LEGACY__;\n\nexport { isLegacy };"`,
)
})

test('replace bare import.meta.env', async () => {
const transform = await createDefinePluginTransform()
expect(await transform('export const env = import.meta.env;')).toMatch(
/const env = .*;\n\nexport \{ env \};/s,
)
})

test('already has marker', async () => {
const transform = await createDefinePluginTransform()
expect(
await transform(
'console.log(__vite_import_meta_env__);\nexport const env = import.meta.env;',
),
).toMatch(/console.log\(__vite_import_meta_env__\);\nconst env = .*/)

expect(
await transform(
'console.log(__vite_import_meta_env__, __vite_import_meta_env__1);\n export const env = import.meta.env;',
),
).toMatch(
/console.log\(__vite_import_meta_env__, __vite_import_meta_env__1\);\nconst env = .*/,
)

expect(
await transform(
'console.log(__vite_import_meta_env__);\nexport const env = import.meta.env;\nconsole.log(import.meta.env.UNDEFINED);',
),
).toMatch(
/console.log\(__vite_import_meta_env__\);\nconst env = .*;\nconsole.log\(void 0\);/s,
)
})
})
8 changes: 4 additions & 4 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,14 +505,14 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
],
post: [
...buildImportAnalysisPlugin(config),
...(config.experimental.enableNativePlugin !== true
? [
...(config.nativePluginEnabledLevel >= 1
? []
: [
buildOxcPlugin(),
...(config.build.minify === 'esbuild'
? [buildEsbuildPlugin()]
: []),
]
: []),
]),
terserPlugin(config),
...(!config.isWorker
? [
Expand Down
45 changes: 38 additions & 7 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,14 @@ export interface ExperimentalOptions {
/**
* Enable builtin plugin that written by rust, which is faster than js plugin.
*
* - 'resolver': Enable only the native resolver plugin.
* - 'v1': Enable the first stable set of native plugins (including resolver).
* - true: Enable all native plugins (currently an alias of 'v1', it will map to a newer one in the future versions).
*
* @experimental
* @default 'resolver'
* @default 'v1'
*/
enableNativePlugin?: boolean | 'resolver'
enableNativePlugin?: boolean | 'resolver' | 'v1'
}

export interface LegacyOptions {
Expand Down Expand Up @@ -680,6 +684,8 @@ export interface ResolvedConfig
/** @internal */
safeModulePaths: Set<string>
/** @internal */
nativePluginEnabledLevel: number
/** @internal */
[SYMBOL_RESOLVED_CONFIG]: true
} & PluginHookUtils
> {}
Expand Down Expand Up @@ -746,7 +752,7 @@ export const configDefaults = Object.freeze({
importGlobRestoreExtension: false,
renderBuiltUrl: undefined,
hmrPartialAccept: false,
enableNativePlugin: process.env._VITE_TEST_JS_PLUGIN ? false : 'resolver',
enableNativePlugin: process.env._VITE_TEST_JS_PLUGIN ? false : 'v1',
},
future: {
removePluginHookHandleHotUpdate: undefined,
Expand Down Expand Up @@ -1751,6 +1757,11 @@ export async function resolveConfig(
)
}

const experimental = mergeWithDefaults(
configDefaults.experimental,
config.experimental ?? {},
)

resolved = {
configFile: configFile ? normalizePath(configFile) : undefined,
configFileDependencies: configFileDependencies.map((name) =>
Expand Down Expand Up @@ -1812,10 +1823,7 @@ export async function resolveConfig(
packageCache,
worker: resolvedWorkerOptions,
appType: config.appType ?? 'spa',
experimental: mergeWithDefaults(
configDefaults.experimental,
config.experimental ?? {},
),
experimental,
future:
config.future === 'warn'
? ({
Expand Down Expand Up @@ -1880,6 +1888,9 @@ export async function resolveConfig(
},
),
safeModulePaths: new Set<string>(),
nativePluginEnabledLevel: resolveNativePluginEnabledLevel(
experimental.enableNativePlugin,
),
[SYMBOL_RESOLVED_CONFIG]: true,
}
resolved = {
Expand Down Expand Up @@ -2018,6 +2029,26 @@ assetFileNames isn't equal for every build.rollupOptions.output. A single patter
return resolved
}

function resolveNativePluginEnabledLevel(
enableNativePlugin: Exclude<
ExperimentalOptions['enableNativePlugin'],
undefined
>,
) {
switch (enableNativePlugin) {
case 'resolver':
return 0
case 'v1':
case true:
return 1
case false:
return -1
default:
enableNativePlugin satisfies never
return -1
}
}

/**
* Resolve base url. Note that some users use Vite to build for non-web targets like
* electron or expects to deploy
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function definePlugin(config: ResolvedConfig): Plugin {
return pattern
}

if (config.experimental.enableNativePlugin === true && isBuild) {
if (isBuild && config.nativePluginEnabledLevel >= 1) {
return {
name: 'vite:define',
options(option) {
Expand Down
5 changes: 1 addition & 4 deletions packages/vite/src/node/plugins/dynamicImportVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,7 @@ export function dynamicImportVarsPlugin(config: ResolvedConfig): Plugin {
extensions: [],
})

if (
config.experimental.enableNativePlugin === true &&
config.command === 'build'
) {
if (config.command === 'build' && config.nativePluginEnabledLevel >= 1) {
return perEnvironmentPlugin('native:dynamic-import-vars', (environment) => {
const { include, exclude } =
environment.config.build.dynamicImportVarsOptions
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/importAnalysisBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin[] {
},
}

if (config.experimental.enableNativePlugin === true) {
if (config.nativePluginEnabledLevel >= 1) {
delete plugin.transform
delete plugin.resolveId
delete plugin.load
Expand Down
5 changes: 1 addition & 4 deletions packages/vite/src/node/plugins/importMetaGlob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ interface ParsedGeneralImportGlobOptions extends GeneralImportGlobOptions {
}

export function importGlobPlugin(config: ResolvedConfig): Plugin {
if (
config.experimental.enableNativePlugin === true &&
config.command === 'build'
) {
if (config.command === 'build' && config.nativePluginEnabledLevel >= 1) {
return nativeImportGlobPlugin({
root: config.root,
restoreQueryExtension: config.experimental.importGlobRestoreExtension,
Expand Down
7 changes: 4 additions & 3 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ export async function resolvePlugins(
? await (await import('../build')).resolveBuildPlugins(config)
: { pre: [], post: [] }
const { modulePreload } = config.build
const enableNativePlugin = config.experimental.enableNativePlugin
const enableNativePlugin = config.nativePluginEnabledLevel >= 0
const enableNativePluginV1 = config.nativePluginEnabledLevel >= 1

return [
!isBuild ? optimizedDepsPlugin() : null,
!isWorker ? watchPackageDataPlugin(config.packageCache) : null,
!isBuild ? preAliasPlugin(config) : null,
enableNativePlugin === true &&
isBuild &&
enableNativePluginV1 &&
!config.resolve.alias.some((v) => v.customResolver)
? nativeAliasPlugin({
entries: config.resolve.alias.map((item) => {
Expand Down Expand Up @@ -104,7 +105,7 @@ export async function resolvePlugins(
cssPlugin(config),
esbuildBannerFooterCompatPlugin(config),
config.oxc !== false ? oxcPlugin(config) : null,
jsonPlugin(config.json, isBuild, enableNativePlugin === true),
jsonPlugin(config.json, isBuild, enableNativePluginV1),
wasmHelperPlugin(config),
webWorkerPlugin(config),
assetPlugin(config),
Expand Down
5 changes: 1 addition & 4 deletions packages/vite/src/node/plugins/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
},
}
})
if (
config.build.manifest &&
config.experimental.enableNativePlugin === true
) {
if (config.build.manifest && config.nativePluginEnabledLevel >= 1) {
return perEnvironmentPlugin('native:manifest', (environment) => {
if (!environment.config.build.manifest) return false

Expand Down
5 changes: 1 addition & 4 deletions packages/vite/src/node/plugins/modulePreloadPolyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ export const modulePreloadPolyfillId = 'vite/modulepreload-polyfill'
const resolvedModulePreloadPolyfillId = '\0' + modulePreloadPolyfillId + '.js'

export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin {
if (
config.experimental.enableNativePlugin === true &&
config.command === 'build'
) {
if (config.command === 'build' && config.nativePluginEnabledLevel >= 1) {
return perEnvironmentPlugin(
'native:modulepreload-polyfill',
(environment) => {
Expand Down
Loading
Loading