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 8 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.

### envVariblePrefix
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether envVariblePrefix is a good name? I'm open to a better name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference would be to use envPrefix. We can leave the final call to be discussed with the rest of the team though this week.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong opinion here, both names are okay

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions. I'm changing to envPrefix for now and waiting for the final call.


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

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

:::warning SECURITY NOTES

- `envVariblePrefix` 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 [envVariblePrefix](/config/index#envVariblePrefix) 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({
envVariblePrefix: ['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,
resolveEnvVariblePrefix,
UserConfig
} from '../config'

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

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

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

test('should work correctly for valid envVariblePrefix value', () => {
const config: UserConfig = { envVariblePrefix: [' ', 'CUSTOM_'] }
expect(resolveEnvVariblePrefix(config)).toMatchObject([' ', 'CUSTOM_'])
})
})
36 changes: 30 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 `envVariblePrefix` will be exposed to your client source code via import.meta.env.
* @default 'VITE_'
*/
envVariblePrefix?: 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, resolveEnvVariblePrefix(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,14 @@ async function loadConfigFromBundledFile(
export function loadEnv(
mode: string,
envDir: string,
prefix = 'VITE_'
prefixes: string[]
hyf0 marked this conversation as resolved.
Show resolved Hide resolved
): 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.`
)
}

const env: Record<string, string> = {}
const envFiles = [
/** mode local file */ `.env.${mode}.local`,
Expand All @@ -967,7 +974,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 +998,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 +1010,17 @@ export function loadEnv(
}
}
}

return env
}

export function resolveEnvVariblePrefix({
envVariblePrefix = 'VITE_'
}: UserConfig): string[] {
envVariblePrefix = arraify(envVariblePrefix)
if (envVariblePrefix.some((prefix) => prefix === '')) {
throw new Error(
`envVariblePrefix option contains value '', which could lead unexpected exposure of sensitive information.`
)
}
return envVariblePrefix
}
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]
}