diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 06b222736667c8..acda2d3f7d0ff5 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -29,6 +29,7 @@ import { dynamicImport, isExternalUrl, isObject, + isTS, lookupFile, mergeAlias, mergeConfig, @@ -37,7 +38,12 @@ import { } from './utils' import { resolvePlugins } from './plugins' import type { ESBuildOptions } from './plugins/esbuild' -import { CLIENT_ENTRY, DEFAULT_ASSETS_RE, ENV_ENTRY } from './constants' +import { + CLIENT_ENTRY, + DEFAULT_ASSETS_RE, + DEFAULT_CONFIG_FILES, + ENV_ENTRY +} from './constants' import type { InternalResolveOptions, ResolveOptions } from './plugins/resolve' import { resolvePlugin } from './plugins/resolve' import type { LogLevel, Logger } from './logger' @@ -825,56 +831,20 @@ export async function loadConfigFromFile( const getTime = () => `${(performance.now() - start).toFixed(2)}ms` let resolvedPath: string | undefined - let isTS = false - let isESM = false let dependencies: string[] = [] - // check package.json for type: "module" and set `isMjs` to true - try { - const pkg = lookupFile(configRoot, ['package.json']) - if (pkg && JSON.parse(pkg).type === 'module') { - isESM = true - } - } catch (e) {} - if (configFile) { // explicit config path is always resolved from cwd resolvedPath = path.resolve(configFile) - isTS = configFile.endsWith('.ts') - - if (configFile.endsWith('.mjs')) { - isESM = true - } } else { // implicit config file loaded from inline root (if present) // otherwise from cwd - const jsconfigFile = path.resolve(configRoot, 'vite.config.js') - if (fs.existsSync(jsconfigFile)) { - resolvedPath = jsconfigFile - } - - if (!resolvedPath) { - const mjsconfigFile = path.resolve(configRoot, 'vite.config.mjs') - if (fs.existsSync(mjsconfigFile)) { - resolvedPath = mjsconfigFile - isESM = true - } - } + for (const filename of DEFAULT_CONFIG_FILES) { + const filePath = path.resolve(configRoot, filename) + if (!fs.existsSync(filePath)) continue - if (!resolvedPath) { - const tsconfigFile = path.resolve(configRoot, 'vite.config.ts') - if (fs.existsSync(tsconfigFile)) { - resolvedPath = tsconfigFile - isTS = true - } - } - - if (!resolvedPath) { - const cjsConfigFile = path.resolve(configRoot, 'vite.config.cjs') - if (fs.existsSync(cjsConfigFile)) { - resolvedPath = cjsConfigFile - isESM = false - } + resolvedPath = filePath + break } } @@ -883,6 +853,19 @@ export async function loadConfigFromFile( return null } + let isESM = false + if (/\.m[jt]s$/.test(resolvedPath)) { + isESM = true + } else if (/\.c[jt]s$/.test(resolvedPath)) { + isESM = false + } else { + // check package.json for type: "module" and set `isESM` to true + try { + const pkg = lookupFile(configRoot, ['package.json']) + isESM = !!pkg && JSON.parse(pkg).type === 'module' + } catch (e) {} + } + try { let userConfig: UserConfigExport | undefined @@ -890,15 +873,16 @@ export async function loadConfigFromFile( const fileUrl = pathToFileURL(resolvedPath) const bundled = await bundleConfigFile(resolvedPath, true) dependencies = bundled.dependencies - if (isTS) { + + if (isTS(resolvedPath)) { // before we can register loaders without requiring users to run node // with --experimental-loader themselves, we have to do a hack here: // bundle the config file w/ ts transforms first, write it to disk, // load it with native Node ESM, then delete the file. - fs.writeFileSync(resolvedPath + '.js', bundled.code) - userConfig = (await dynamicImport(`${fileUrl}.js?t=${Date.now()}`)) + fs.writeFileSync(resolvedPath + '.mjs', bundled.code) + userConfig = (await dynamicImport(`${fileUrl}.mjs?t=${Date.now()}`)) .default - fs.unlinkSync(resolvedPath + '.js') + fs.unlinkSync(resolvedPath + '.mjs') debug(`TS + native esm config loaded in ${getTime()}`, fileUrl) } else { // using Function to avoid this from being compiled away by TS/Rollup @@ -972,7 +956,7 @@ async function bundleConfigFile( { name: 'inject-file-scope-variables', setup(build) { - build.onLoad({ filter: /\.[jt]s$/ }, async (args) => { + build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => { const contents = await fs.promises.readFile(args.path, 'utf8') const injectValues = `const __dirname = ${JSON.stringify(path.dirname(args.path))};` + @@ -982,7 +966,7 @@ async function bundleConfigFile( )};` return { - loader: args.path.endsWith('.ts') ? 'ts' : 'js', + loader: isTS(args.path) ? 'ts' : 'js', contents: injectValues + contents } }) diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index d45bfa088f80f1..d85130aba14499 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -20,6 +20,15 @@ export const DEFAULT_EXTENSIONS = [ '.json' ] +export const DEFAULT_CONFIG_FILES = [ + 'vite.config.js', + 'vite.config.mjs', + 'vite.config.ts', + 'vite.config.cjs', + 'vite.config.mts', + 'vite.config.cts' +] + export const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/ export const OPTIMIZABLE_ENTRY_RE = /\.(?:m?js|ts)$/ diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 5967aadfc29f58..c554fa0396f87b 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1056,3 +1056,5 @@ export function stripBomTag(content: string): string { return content } + +export const isTS = (filename: string): boolean => /\.[cm]?ts$/.test(filename) diff --git a/playground/resolve-config/__tests__/resolve-config.spec.ts b/playground/resolve-config/__tests__/resolve-config.spec.ts index 324d797ea31ad8..6682deb9805a34 100644 --- a/playground/resolve-config/__tests__/resolve-config.spec.ts +++ b/playground/resolve-config/__tests__/resolve-config.spec.ts @@ -49,4 +49,20 @@ describe.runIf(isBuild)('build', () => { build('ts-module') expect(getDistFile('ts-module', 'js')).toContain('console.log(true)') }) + it('loads vite.config.mts', () => { + build('mts') + expect(getDistFile('mts', 'mjs')).toContain('console.log(true)') + }) + it('loads vite.config.mts with package#type module', () => { + build('mts-module') + expect(getDistFile('mts-module', 'js')).toContain('console.log(true)') + }) + it('loads vite.config.cts', () => { + build('cts') + expect(getDistFile('cts', 'mjs')).toContain('console.log(true)') + }) + it('loads vite.config.cts with package#type module', () => { + build('cts-module') + expect(getDistFile('cts-module', 'js')).toContain('console.log(true)') + }) }) diff --git a/playground/resolve-config/__tests__/serve.ts b/playground/resolve-config/__tests__/serve.ts index d61ee70b7df50a..b2cc1ccc1e1e77 100644 --- a/playground/resolve-config/__tests__/serve.ts +++ b/playground/resolve-config/__tests__/serve.ts @@ -5,7 +5,7 @@ import path from 'node:path' import fs from 'fs-extra' import { isBuild, rootDir } from '~utils' -const configNames = ['js', 'cjs', 'mjs', 'ts'] +const configNames = ['js', 'cjs', 'mjs', 'ts', 'mts', 'cts'] export async function serve() { if (!isBuild) return @@ -18,9 +18,9 @@ export async function serve() { const pathToConf = fromTestDir(configName, `vite.config.${configName}`) await fs.copy(fromTestDir('root'), fromTestDir(configName)) - await fs.rename(fromTestDir(configName, 'vite.config.js'), pathToConf) + await fs.rename(fromTestDir(configName, 'vite.config.ts'), pathToConf) - if (configName === 'cjs') { + if (['cjs', 'cts'].includes(configName)) { const conf = await fs.readFile(pathToConf, 'utf8') await fs.writeFile( pathToConf, @@ -28,6 +28,12 @@ export async function serve() { ) } + // Remove TS annotation for plain JavaScript file. + if (configName.endsWith('js')) { + const conf = await fs.readFile(pathToConf, 'utf8') + await fs.writeFile(pathToConf, conf.replace(': boolean', '')) + } + // copy directory and add package.json with "type": "module" await fs.copy(fromTestDir(configName), fromTestDir(`${configName}-module`)) await fs.writeJSON(fromTestDir(`${configName}-module`, 'package.json'), { diff --git a/playground/resolve-config/root/vite.config.js b/playground/resolve-config/root/vite.config.ts similarity index 69% rename from playground/resolve-config/root/vite.config.js rename to playground/resolve-config/root/vite.config.ts index ed72046f940d59..a6ad03b2122361 100644 --- a/playground/resolve-config/root/vite.config.js +++ b/playground/resolve-config/root/vite.config.ts @@ -1,5 +1,6 @@ +const __CONFIG_LOADED__: boolean = true export default { - define: { __CONFIG_LOADED__: true }, + define: { __CONFIG_LOADED__ }, logLevel: 'silent', build: { minify: false,