diff --git a/packages/vite/src/node/__tests__/plugins/define.spec.ts b/packages/vite/src/node/__tests__/plugins/define.spec.ts index 9686523a28e6e3..a8f309859c5158 100644 --- a/packages/vite/src/node/__tests__/plugins/define.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/define.spec.ts @@ -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' @@ -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'), @@ -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, + ) + }) +}) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 89663f41950640..7ec357df418429 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -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 ? [ diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 1ce1ad14ffa909..ae11ab2001aef7 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -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 { @@ -680,6 +684,8 @@ export interface ResolvedConfig /** @internal */ safeModulePaths: Set /** @internal */ + nativePluginEnabledLevel: number + /** @internal */ [SYMBOL_RESOLVED_CONFIG]: true } & PluginHookUtils > {} @@ -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, @@ -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) => @@ -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' ? ({ @@ -1880,6 +1888,9 @@ export async function resolveConfig( }, ), safeModulePaths: new Set(), + nativePluginEnabledLevel: resolveNativePluginEnabledLevel( + experimental.enableNativePlugin, + ), [SYMBOL_RESOLVED_CONFIG]: true, } resolved = { @@ -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 diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index ab50ab301b8f13..5cc527c060a391 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -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) { diff --git a/packages/vite/src/node/plugins/dynamicImportVars.ts b/packages/vite/src/node/plugins/dynamicImportVars.ts index 80bac61c24eb7e..3c233db53c4ec3 100644 --- a/packages/vite/src/node/plugins/dynamicImportVars.ts +++ b/packages/vite/src/node/plugins/dynamicImportVars.ts @@ -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 diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 1f11ac861879bc..2107de4c5f5f07 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -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 diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts index fba17e71a1ef81..5ffb84e361df48 100644 --- a/packages/vite/src/node/plugins/importMetaGlob.ts +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -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, diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index ba6a01b90098b6..a0cd0d2d80f5e2 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -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) => { @@ -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), diff --git a/packages/vite/src/node/plugins/manifest.ts b/packages/vite/src/node/plugins/manifest.ts index ea76b24ab11a79..320ceebb4d1157 100644 --- a/packages/vite/src/node/plugins/manifest.ts +++ b/packages/vite/src/node/plugins/manifest.ts @@ -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 diff --git a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts index 72339e714b38a4..8c1cc3b8c55be5 100644 --- a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts +++ b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts @@ -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) => { diff --git a/packages/vite/src/node/plugins/oxc.ts b/packages/vite/src/node/plugins/oxc.ts index 131c4e2ff42637..e3018b8aceaf93 100644 --- a/packages/vite/src/node/plugins/oxc.ts +++ b/packages/vite/src/node/plugins/oxc.ts @@ -277,10 +277,7 @@ function resolveTsconfigTarget(target: string | undefined): number | 'next' { } export function oxcPlugin(config: ResolvedConfig): Plugin { - if ( - config.experimental.enableNativePlugin === true && - config.command === 'build' - ) { + if (config.command === 'build' && config.nativePluginEnabledLevel >= 1) { return perEnvironmentPlugin('native:transform', (environment) => { const { jsxInject, diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index 5a59558a97f78e..8c80132c6b4f92 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -28,7 +28,7 @@ type LogEntry = { const COMPRESSIBLE_ASSETS_RE = /\.(?:html|json|svg|txt|xml|xhtml|wasm)$/ export function buildReporterPlugin(config: ResolvedConfig): Plugin { - if (config.experimental.enableNativePlugin === true) { + if (config.nativePluginEnabledLevel >= 1) { return perEnvironmentPlugin('native:reporter', (env) => { const tty = process.stdout.isTTY && !process.env.CI const shouldLogInfo = diff --git a/packages/vite/src/node/plugins/wasm.ts b/packages/vite/src/node/plugins/wasm.ts index cccb6e2dd423a1..1b3288b8df8f2c 100644 --- a/packages/vite/src/node/plugins/wasm.ts +++ b/packages/vite/src/node/plugins/wasm.ts @@ -54,10 +54,7 @@ const wasmHelper = async (opts = {}, url: string) => { const wasmHelperCode = wasmHelper.toString() export const wasmHelperPlugin = (config: ResolvedConfig): Plugin => { - if ( - config.experimental.enableNativePlugin === true && - config.command === 'build' - ) { + if (config.command === 'build' && config.nativePluginEnabledLevel >= 1) { return nativeWasmHelperPlugin({ decodedBase: config.decodedBase, }) @@ -92,7 +89,7 @@ export const wasmHelperPlugin = (config: ResolvedConfig): Plugin => { } export const wasmFallbackPlugin = (config: ResolvedConfig): Plugin => { - if (config.experimental.enableNativePlugin === true) { + if (config.nativePluginEnabledLevel >= 1) { return nativeWasmFallbackPlugin() } diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 3b61aa829e638d..390700ed31ca80 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -234,10 +234,7 @@ export async function workerFileToUrl( } export function webWorkerPostPlugin(config: ResolvedConfig): Plugin { - if ( - config.experimental.enableNativePlugin === true && - config.command === 'build' - ) { + if (config.command === 'build' && config.nativePluginEnabledLevel >= 1) { return perEnvironmentPlugin( 'native:web-worker-post-plugin', (environment) => { diff --git a/playground/glob-import/__tests__/glob-import.spec.ts b/playground/glob-import/__tests__/glob-import.spec.ts index c29e3d03e58a75..a33a6998473b54 100644 --- a/playground/glob-import/__tests__/glob-import.spec.ts +++ b/playground/glob-import/__tests__/glob-import.spec.ts @@ -88,12 +88,15 @@ const baseRawResult = { } test('should work', async () => { - await expect - .poll(async () => JSON.parse(await page.textContent('.result'))) - .toStrictEqual(allResult) - await expect - .poll(async () => JSON.parse(await page.textContent('.result-eager'))) - .toStrictEqual(allResult) + // TODO: extglobs are not supported yet: https://github.com/vitejs/rolldown-vite/issues/365 + if (process.env._VITE_TEST_JS_PLUGIN) { + await expect + .poll(async () => JSON.parse(await page.textContent('.result'))) + .toStrictEqual(allResult) + await expect + .poll(async () => JSON.parse(await page.textContent('.result-eager'))) + .toStrictEqual(allResult) + } await expect .poll(async () => JSON.parse(await page.textContent('.result-node_modules')), diff --git a/playground/js-sourcemap/__tests__/js-sourcemap.spec.ts b/playground/js-sourcemap/__tests__/js-sourcemap.spec.ts index 43e67da1bc45a0..c842e687525446 100644 --- a/playground/js-sourcemap/__tests__/js-sourcemap.spec.ts +++ b/playground/js-sourcemap/__tests__/js-sourcemap.spec.ts @@ -140,11 +140,11 @@ describe.runIf(isBuild)('build tests', () => { test('sourcemap is correct when preload information is injected', async () => { const map = findAssetFile(/after-preload-dynamic-[-\w]{8}\.js\.map/) - expect(formatSourcemapForSnapshot(JSON.parse(map))).toMatchInlineSnapshot(` + let mapSnapshot = ` { "debugId": "00000000-0000-0000-0000-000000000000", "ignoreList": [], - "mappings": ";grCAAA,OAAO,6BAAuB,wBAE9B,QAAQ,IAAI,wBAAuB", + "mappings": ";grCAAA,OAAO,qDAEP,QAAQ,IAAI,wBAAwB", "sources": [ "../../after-preload-dynamic.js", ], @@ -156,7 +156,16 @@ describe.runIf(isBuild)('build tests', () => { ], "version": 3, } - `) + ` + if (process.env._VITE_TEST_JS_PLUGIN) { + mapSnapshot = mapSnapshot.replace( + ';grCAAA,OAAO,qDAEP,QAAQ,IAAI,wBAAwB', + ';grCAAA,OAAO,6BAAuB,wBAE9B,QAAQ,IAAI,wBAAuB', + ) + } + expect(formatSourcemapForSnapshot(JSON.parse(map))).toMatchInlineSnapshot( + mapSnapshot, + ) // verify sourcemap comment is preserved at the last line const js = findAssetFile(/after-preload-dynamic-[-\w]{8}\.js$/) expect(js).toMatch( diff --git a/playground/minify/vite.config.js b/playground/minify/vite.config.js index 69b96c12569ea7..a23f2bc8147c0a 100644 --- a/playground/minify/vite.config.js +++ b/playground/minify/vite.config.js @@ -8,5 +8,10 @@ export default defineConfig({ build: { minify: 'esbuild', cssMinify: 'esbuild', + rollupOptions: { + output: { + legalComments: 'none', + }, + }, }, })