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 224c534c497ece..e5f72e6ac65957 100644
--- a/packages/vite/src/node/config.ts
+++ b/packages/vite/src/node/config.ts
@@ -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
@@ -1083,7 +1079,7 @@ export function loadEnv(
}
for (const file of envFiles) {
- const path = lookupFile(envDir, [file], true)
+ const path = lookupFile(envDir, [file], { pathOnly: true, rootDir: 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 9cba9c7c79b3a3..935c5c8401071b 100644
--- a/packages/vite/src/node/utils.ts
+++ b/packages/vite/src/node/utils.ts
@@ -298,20 +298,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)
}
}