Skip to content
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

feat: allow custom vite env prefix #4676

Merged
merged 10 commits into from Aug 27, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/config/index.md
Expand Up @@ -333,6 +333,18 @@ export default defineConfig(async ({ command, mode }) => {

See [here](/guide/env-and-mode#env-files) for more about environment files.

### envPrefix

- **Type:** `string | string[]`
- **Default:** `VITE_`

Env variables starts with `envPrefix` will be exposed to your client source code via import.meta.env.

:::warning SECURITY NOTES

- `envPrefix` should not be set as `''`, which will expose all your env variables and cause unexpected leaking of of sensitive information. Vite will throw error when detecting `''`.
:::

## Server Options

### server.host
Expand Down
2 changes: 2 additions & 0 deletions docs/guide/env-and-mode.md
Expand Up @@ -44,6 +44,8 @@ VITE_SOME_KEY=123

Only `VITE_SOME_KEY` will be exposed as `import.meta.env.VITE_SOME_KEY` to your client source code, but `DB_PASSWORD` will not.

If you want to customize env variables prefix, see [envPrefix](/config/index#envPrefix) option.

:::warning SECURITY NOTES

- `.env.*.local` files are local-only and can contain sensitive variables. You should add `.local` to your `.gitignore` to avoid them being checked into git.
Expand Down
1 change: 1 addition & 0 deletions packages/playground/env/.env
@@ -1,2 +1,3 @@
VITE_CUSTOM_ENV_VARIABLE=1
CUSTOM_PREFIX_ENV_VARIABLE=1
VITE_EFFECTIVE_MODE_FILE_NAME=.env
5 changes: 5 additions & 0 deletions packages/playground/env/__tests__/env.spec.ts
Expand Up @@ -22,6 +22,10 @@ test('custom', async () => {
expect(await page.textContent('.custom')).toBe('1')
})

test('custom-prefix', async () => {
expect(await page.textContent('.custom-prefix')).toBe('1')
})

test('mode file override', async () => {
expect(await page.textContent('.mode-file')).toBe(`.env.${mode}`)
})
Expand All @@ -40,6 +44,7 @@ test('env object', async () => {
const envText = await page.textContent('.env-object')
expect(JSON.parse(envText)).toMatchObject({
VITE_EFFECTIVE_MODE_FILE_NAME: `.env.${mode}`,
CUSTOM_PREFIX_ENV_VARIABLE: '1',
VITE_CUSTOM_ENV_VARIABLE: '1',
BASE_URL: '/',
MODE: mode,
Expand Down
2 changes: 2 additions & 0 deletions packages/playground/env/index.html
Expand Up @@ -4,6 +4,7 @@ <h1>Environment Variables</h1>
<p>import.meta.env.DEV: <code class="dev"></code></p>
<p>import.meta.env.PROD: <code class="prod"></code></p>
<p>import.meta.env.VITE_CUSTOM_ENV_VARIABLE: <code class="custom"></code></p>
<p>import.meta.env.CUSTOM_PREFIX_ENV_VARIABLE: <code class="custom-prefix"></code></p>
<p>
import.meta.env.VITE_EFFECTIVE_MODE_FILE_NAME: <code class="mode-file"></code>
</p>
Expand All @@ -17,6 +18,7 @@ <h1>Environment Variables</h1>
text('.dev', import.meta.env.DEV)
text('.prod', import.meta.env.PROD)
text('.custom', import.meta.env.VITE_CUSTOM_ENV_VARIABLE)
text('.custom-prefix', import.meta.env.CUSTOM_PREFIX_ENV_VARIABLE)
text('.mode-file', import.meta.env.VITE_EFFECTIVE_MODE_FILE_NAME)
text('.inline', import.meta.env.VITE_INLINE)
text('.node-env', process.env.NODE_ENV)
Expand Down
5 changes: 5 additions & 0 deletions packages/playground/env/vite.config.js
@@ -0,0 +1,5 @@
const { defineConfig } = require('vite')

module.exports = defineConfig({
envPrefix: ['VITE_', 'CUSTOM_PREFIX_']
})
27 changes: 26 additions & 1 deletion packages/vite/src/node/__tests__/config.spec.ts
@@ -1,5 +1,11 @@
import { InlineConfig } from '..'
import { mergeConfig, resolveConfig, UserConfigExport } from '../config'
import {
mergeConfig,
resolveConfig,
UserConfigExport,
resolveEnvPrefix,
UserConfig
} from '../config'

describe('mergeConfig', () => {
test('handles configs with different alias schemas', () => {
Expand Down Expand Up @@ -139,3 +145,22 @@ describe('resolveConfig', () => {
})
})
})

describe('resolveEnvPrefix', () => {
test(`use 'VITE_' as default value`, () => {
const config: UserConfig = {}
expect(resolveEnvPrefix(config)).toMatchObject(['VITE_'])
})

test(`throw error if envPrefix contains ''`, () => {
let config: UserConfig = { envPrefix: '' }
expect(() => resolveEnvPrefix(config)).toThrow()
config = { envPrefix: ['', 'CUSTOM_'] }
expect(() => resolveEnvPrefix(config)).toThrow()
})

test('should work correctly for valid envPrefix value', () => {
const config: UserConfig = { envPrefix: [' ', 'CUSTOM_'] }
expect(resolveEnvPrefix(config)).toMatchObject([' ', 'CUSTOM_'])
})
})
37 changes: 31 additions & 6 deletions packages/vite/src/node/config.ts
Expand Up @@ -9,6 +9,7 @@ import {
} from './server'
import { CSSOptions } from './plugins/css'
import {
arraify,
createDebugger,
isExternalUrl,
isObject,
Expand Down Expand Up @@ -170,6 +171,11 @@ export interface UserConfig {
* @default root
*/
envDir?: string
/**
* Env variables starts with `envPrefix` will be exposed to your client source code via import.meta.env.
* @default 'VITE_'
*/
envPrefix?: string | string[]
/**
* Import aliases
* @deprecated use `resolve.alias` instead
Expand Down Expand Up @@ -328,7 +334,9 @@ export async function resolveConfig(
const envDir = config.envDir
? normalizePath(path.resolve(resolvedRoot, config.envDir))
: resolvedRoot
const userEnv = inlineConfig.envFile !== false && loadEnv(mode, envDir)
const userEnv =
inlineConfig.envFile !== false &&
loadEnv(mode, envDir, resolveEnvPrefix(config))

// 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
Expand Down Expand Up @@ -947,15 +955,15 @@ async function loadConfigFromBundledFile(
export function loadEnv(
mode: string,
envDir: string,
prefix = 'VITE_'
prefixes: string | string[] = 'VITE_'
): Record<string, string> {
if (mode === 'local') {
throw new Error(
`"local" cannot be used as a mode name because it conflicts with ` +
`the .local postfix for .env files.`
)
}

prefixes = arraify(prefixes)
const env: Record<string, string> = {}
const envFiles = [
/** mode local file */ `.env.${mode}.local`,
Expand All @@ -967,7 +975,10 @@ export function loadEnv(
// check if there are actual env variables starting with VITE_*
// these are typically provided inline and should be prioritized
for (const key in process.env) {
if (key.startsWith(prefix) && env[key] === undefined) {
if (
prefixes.some((prefix) => key.startsWith(prefix)) &&
env[key] === undefined
) {
env[key] = process.env[key] as string
}
}
Expand All @@ -988,7 +999,10 @@ export function loadEnv(

// only keys that start with prefix are exposed to client
for (const [key, value] of Object.entries(parsed)) {
if (key.startsWith(prefix) && env[key] === undefined) {
if (
prefixes.some((prefix) => key.startsWith(prefix)) &&
env[key] === undefined
) {
env[key] = value
} else if (key === 'NODE_ENV') {
// NODE_ENV override in .env file
Expand All @@ -997,6 +1011,17 @@ export function loadEnv(
}
}
}

return env
}

export function resolveEnvPrefix({
envPrefix = 'VITE_'
}: UserConfig): string[] {
envPrefix = arraify(envPrefix)
if (envPrefix.some((prefix) => prefix === '')) {
throw new Error(
`envPrefix option contains value '', which could lead unexpected exposure of sensitive information.`
)
}
return envPrefix
}
4 changes: 4 additions & 0 deletions packages/vite/src/node/utils.ts
Expand Up @@ -512,3 +512,7 @@ export function resolveHostname(

return { host, name }
}

export function arraify<T>(target: T | T[]): T[] {
return Array.isArray(target) ? target : [target]
}