diff --git a/packages/playground/.env b/packages/playground/.env
new file mode 100644
index 00000000000000..94a99bb79ef20d
--- /dev/null
+++ b/packages/playground/.env
@@ -0,0 +1 @@
+VITE_PARENT_ENV=dont_load_me
diff --git a/packages/playground/env-nested/.env b/packages/playground/env-nested/.env
new file mode 100644
index 00000000000000..2e8565f90df52b
--- /dev/null
+++ b/packages/playground/env-nested/.env
@@ -0,0 +1 @@
+VITE_PARENT_ENV=dont_load_me
\ No newline at end of file
diff --git a/packages/playground/env-nested/__tests__/env-nested.spec.ts b/packages/playground/env-nested/__tests__/env-nested.spec.ts
new file mode 100644
index 00000000000000..1ceebde7a044b7
--- /dev/null
+++ b/packages/playground/env-nested/__tests__/env-nested.spec.ts
@@ -0,0 +1,15 @@
+import { isBuild } from 'testUtils'
+
+const mode = isBuild ? `production` : `development`
+
+test('mode', async () => {
+ expect(await page.textContent('.mode')).toBe(mode)
+})
+
+test('mode file override', async () => {
+ expect(await page.textContent('.mode-file')).toBe(`.env.${mode}`)
+})
+
+test('should not load parent .env file', async () => {
+ expect(await page.textContent('.parent-env')).not.toBe('dont_load_me')
+})
diff --git a/packages/playground/env-nested/envs/.env.development b/packages/playground/env-nested/envs/.env.development
new file mode 100644
index 00000000000000..1557708651f4dc
--- /dev/null
+++ b/packages/playground/env-nested/envs/.env.development
@@ -0,0 +1 @@
+VITE_EFFECTIVE_MODE_FILE_NAME=.env.development
diff --git a/packages/playground/env-nested/envs/.env.production b/packages/playground/env-nested/envs/.env.production
new file mode 100644
index 00000000000000..13c1a2ab36869f
--- /dev/null
+++ b/packages/playground/env-nested/envs/.env.production
@@ -0,0 +1 @@
+VITE_EFFECTIVE_MODE_FILE_NAME=.env.production
diff --git a/packages/playground/env-nested/index.html b/packages/playground/env-nested/index.html
new file mode 100644
index 00000000000000..8f775d5cb30bba
--- /dev/null
+++ b/packages/playground/env-nested/index.html
@@ -0,0 +1,16 @@
+
Nested Environment Variables
+import.meta.env.MODE:
+
+ import.meta.env.VITE_EFFECTIVE_MODE_FILE_NAME:
+
+Empty import.meta.env.VITE_PARENT_ENV:
+
+
diff --git a/packages/playground/env-nested/package.json b/packages/playground/env-nested/package.json
new file mode 100644
index 00000000000000..8fecc69a41c2f4
--- /dev/null
+++ b/packages/playground/env-nested/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "test-env-nested",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/packages/playground/env-nested/vite.config.js b/packages/playground/env-nested/vite.config.js
new file mode 100644
index 00000000000000..0e46100698650d
--- /dev/null
+++ b/packages/playground/env-nested/vite.config.js
@@ -0,0 +1,5 @@
+const { defineConfig } = require('vite')
+
+module.exports = defineConfig({
+ envDir: './envs'
+})
diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts
index fbda8d5166cb1b..a17e3058092ea0 100644
--- a/packages/vite/src/node/config.ts
+++ b/packages/vite/src/node/config.ts
@@ -385,7 +385,7 @@ export async function resolveConfig(
: resolvedRoot
const userEnv =
inlineConfig.envFile !== false &&
- loadEnv(mode, envDir, resolveEnvPrefix(config))
+ loadEnv(mode, envDir, resolveEnvPrefix(config), false)
// Note it is possible for user to have a custom mode, e.g. `staging` where
// production-like behavior is expected. This is indicated by NODE_ENV=production
@@ -401,11 +401,7 @@ export async function resolveConfig(
const resolvedBuildOptions = resolveBuildOptions(config.build)
// resolve cache directory
- const pkgPath = lookupFile(
- resolvedRoot,
- [`package.json`],
- true /* pathOnly */
- )
+ const pkgPath = lookupFile(resolvedRoot, [`package.json`], { pathOnly: true })
const cacheDir = config.cacheDir
? path.resolve(resolvedRoot, config.cacheDir)
: pkgPath
@@ -1054,7 +1050,8 @@ async function loadConfigFromBundledFile(
export function loadEnv(
mode: string,
envDir: string,
- prefixes: string | string[] = 'VITE_'
+ prefixes: string | string[] = 'VITE_',
+ searchAboveEnvDir = true
): Record {
if (mode === 'local') {
throw new Error(
@@ -1083,7 +1080,10 @@ export function loadEnv(
}
for (const file of envFiles) {
- const path = lookupFile(envDir, [file], true)
+ const path = lookupFile(envDir, [file], {
+ pathOnly: true,
+ rootDir: searchAboveEnvDir ? undefined : envDir
+ })
if (path) {
const parsed = dotenv.parse(fs.readFileSync(path), {
debug: process.env.DEBUG?.includes('vite:dotenv') || undefined
diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts
index 3012e6d57e6c65..07e30cbc8ee286 100644
--- a/packages/vite/src/node/utils.ts
+++ b/packages/vite/src/node/utils.ts
@@ -292,20 +292,28 @@ export function isDefined(value: T | undefined | null): value is T {
return value != null
}
+interface LookupFileOptions {
+ pathOnly?: boolean
+ rootDir?: string
+}
+
export function lookupFile(
dir: string,
formats: string[],
- pathOnly = false
+ options?: LookupFileOptions
): string | undefined {
for (const format of formats) {
const fullPath = path.join(dir, format)
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
- return pathOnly ? fullPath : fs.readFileSync(fullPath, 'utf-8')
+ return options?.pathOnly ? fullPath : fs.readFileSync(fullPath, 'utf-8')
}
}
const parentDir = path.dirname(dir)
- if (parentDir !== dir) {
- return lookupFile(parentDir, formats, pathOnly)
+ if (
+ parentDir !== dir &&
+ (!options?.rootDir || parentDir.startsWith(options?.rootDir))
+ ) {
+ return lookupFile(parentDir, formats, options)
}
}