Skip to content

Commit

Permalink
feat: expose env variables on import.meta.env
Browse files Browse the repository at this point in the history
BREAKING CHANGE: env variables are now exposed on `import.meta.env`
instead of `process.env`.

  - For example, with a `.env` file containing `VITE_FOO=1`, you can
    access it as `import.meta.env.VITE_FOO`.

  - Only variables that start with `VITE_` are exposed to the client
    code. This is because sometimes users may use the same `.env` file
    for build scripts or other server-side code where it may contain
    sensitive information that should not be exposed in client-side code.

  - `import.meta.env.MODE` will be the mode the app is running in
    (default is `development` in dev and `production` in build).

  - `import.meta.env.BASE_URL` will be the base public URL as specified
    via the `base` config option.

  - `import.meta.env.DEV` will be `true` when mode is `development`.

  - `import.meta.env.PROD` will be `true` when mode is `production`.

  - `process.env` is still shimmed because some dependencies rely on it,
     but will only expose `process.env.NODE_ENV` and will not contain
     any user env variables.
  • Loading branch information
yyx990803 committed Jun 19, 2020
1 parent e6182c3 commit 51e9c83
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 43 deletions.
4 changes: 2 additions & 2 deletions playground/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
CUSTOM_ENV_VARIABLE=9527
EFFECTIVE_MODE_FILE_NAME=.env
VITE_CUSTOM_ENV_VARIABLE=9527
VITE_EFFECTIVE_MODE_FILE_NAME=.env
2 changes: 1 addition & 1 deletion playground/.env.development
Original file line number Diff line number Diff line change
@@ -1 +1 @@
EFFECTIVE_MODE_FILE_NAME=.env.development
VITE_EFFECTIVE_MODE_FILE_NAME=.env.development
2 changes: 1 addition & 1 deletion playground/.env.production
Original file line number Diff line number Diff line change
@@ -1 +1 @@
EFFECTIVE_MODE_FILE_NAME=.env.production
VITE_EFFECTIVE_MODE_FILE_NAME=.env.production
6 changes: 0 additions & 6 deletions playground/App.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<template>
<h1>Vite Playground</h1>
<p class="base">
<code>process.env.BASE_URL: {{ base }}</code>
</p>
<TestEnv />
<h2>Async Component</h2>
<TestAsync />
Expand Down Expand Up @@ -53,9 +50,6 @@ import TestNormalizePublicPath from './TestNormalizePublicPath.vue'
import TestDynamicImport from './dynamic-import/TestDynamicImport.vue'
export default {
data: () => ({
base: process.env.BASE_URL
}),
components: {
TestEnv,
TestModuleResolve,
Expand Down
30 changes: 23 additions & 7 deletions playground/TestEnv.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
<template>
<h2>Environment Variables</h2>
<p class="node-env">
<code>process.env.NODE_ENV: {{ NODE_ENV }}</code>
<p class="base">
<code>import.meta.env.BASE_URL: {{ base }}</code>
</p>
<p class="mode">
<code>import.meta.env.MODE: {{ mode }}</code>
</p>
<p class="dev">
<code>import.meta.env.DEV: {{ dev }}</code>
</p>
<p class="prod">
<code>import.meta.env.PROD: {{ prod }}</code>
</p>
<p class="custom-env-variable">
<code>process.env.CUSTOM_ENV_VARIABLE: {{ CUSTOM_ENV_VARIABLE }}</code>
<code>import.meta.env.VITE_CUSTOM_ENV_VARIABLE: {{ custom }}</code>
</p>
<p class="effective-mode-file-name">
<code>process.env.EFFECTIVE_MODE_FILE_NAME: {{ EFFECTIVE_MODE_FILE_NAME }}</code>
<code>import.meta.env.VITE_EFFECTIVE_MODE_FILE_NAME: {{ modeFile }}</code>
</p>
<p class="node-env">
<code>process.env.NODE_ENV: {{ NODE_ENV }}</code>
</p>
</template>

<script>
export default {
data() {
return {
NODE_ENV: process.env.NODE_ENV,
CUSTOM_ENV_VARIABLE: process.env.CUSTOM_ENV_VARIABLE,
EFFECTIVE_MODE_FILE_NAME: process.env.EFFECTIVE_MODE_FILE_NAME
base: import.meta.env.BASE_URL,
mode: import.meta.env.MODE,
dev: import.meta.env.DEV,
prod: import.meta.env.PROD,
custom: import.meta.env.VITE_CUSTOM_ENV_VARIABLE,
modeFile: import.meta.env.VITE_EFFECTIVE_MODE_FILE_NAME,
NODE_ENV: process.env.NODE_ENV
}
}
}
Expand Down
17 changes: 12 additions & 5 deletions src/node/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,12 @@ export async function build(options: BuildConfig): Promise<BuildResult> {

const basePlugins = await createBaseRollupPlugins(root, resolver, options)

env.NODE_ENV = mode!
const envReplacements = Object.keys(env).reduce((replacements, key) => {
replacements[`process.env.${key}`] = JSON.stringify(env[key])
// user env variables loaded from .env files.
// only those prefixed with VITE_ are exposed.
const userEnvReplacements = Object.keys(env).reduce((replacements, key) => {
if (key.startsWith(`VITE_`)) {
replacements[`import.meta.env.${key}`] = JSON.stringify(env[key])
}
return replacements
}, {} as Record<string, string>)

Expand All @@ -252,8 +255,12 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
createReplacePlugin(
(id) => /\.(j|t)sx?$/.test(id),
{
...envReplacements,
'process.env.BASE_URL': JSON.stringify(publicBasePath),
...userEnvReplacements,
'import.meta.env.BASE_URL': JSON.stringify(publicBasePath),
'import.meta.env.MODE': JSON.stringify(mode),
'import.meta.env.DEV': String(mode === 'development'),
'import.meta.env.PROD': String(mode === 'production'),
'process.env.NODE_ENV': JSON.stringify(mode),
'process.env.': `({}).`,
'import.meta.hot': `false`
},
Expand Down
7 changes: 6 additions & 1 deletion src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,12 @@ function loadEnv(mode: string, root: string): Record<string, string> {
if (result.error) {
throw result.error
}
Object.assign(env, result.parsed)
for (const key in result.parsed) {
// only keys that start with VITE_ are exposed.
if (key.startsWith(`VITE_`)) {
env[key] = result.parsed![key]
}
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/node/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from 'path'
import fs from 'fs-extra'
import { RequestListener, Server } from 'http'
import { ServerOptions } from 'https'
import Koa, { DefaultState, DefaultContext } from 'koa'
Expand All @@ -18,8 +20,7 @@ import { serviceWorkerPlugin } from './serverPluginServiceWorker'
import { htmlRewritePlugin } from './serverPluginHtml'
import { proxyPlugin } from './serverPluginProxy'
import { createCertificate } from '../utils/createCertificate'
import fs from 'fs-extra'
import path from 'path'
import { envPlugin } from './serverPluginEnv'
export { rewriteImports } from './serverPluginModuleRewrite'

export type ServerPlugin = (ctx: ServerPluginContext) => void
Expand Down Expand Up @@ -77,6 +78,7 @@ export function createServer(config: ServerConfig): Server {
htmlRewritePlugin,
// user plugins
...(Array.isArray(configureServer) ? configureServer : [configureServer]),
envPlugin,
moduleResolvePlugin,
proxyPlugin,
serviceWorkerPlugin,
Expand Down
23 changes: 23 additions & 0 deletions src/node/server/serverPluginEnv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ServerPlugin } from '.'

export const envPublicPath = '/vite/env'

export const envPlugin: ServerPlugin = ({ app, config }) => {
const mode = config.mode || 'development'
const env = JSON.stringify({
...config.env,
BASE_URL: '/',
MODE: mode,
DEV: mode === 'development',
PROD: mode === 'production'
})

app.use((ctx, next) => {
if (ctx.path === envPublicPath) {
ctx.type = 'js'
ctx.body = `export default ${env}`
return
}
return next()
})
}
15 changes: 6 additions & 9 deletions src/node/server/serverPluginHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ export const htmlRewritePlugin: ServerPlugin = ({
resolver,
config
}) => {
// inject process.env flags
// since some ESM builds expect these to be replaced by the bundler
const { env = {}, mode } = config

const devInjectionCode =
`\n<script type="module">\n` +
// import hmr client first to establish connection
`import "${hmrClientPublicPath}"\n` +
`window.process = { env: ${JSON.stringify({
...env,
NODE_ENV: mode,
BASE_URL: '/'
})}}\n` +
// inject process.env.NODE_ENV
// since some ESM builds expect these to be replaced by the bundler
`window.process = { env: { NODE_ENV: ${JSON.stringify(
config.mode || 'development'
)} }}\n` +
`</script>\n`

const scriptRE = /(<script\b[^>]*>)([\s\S]*?)<\/script>/gm
Expand Down
16 changes: 12 additions & 4 deletions src/node/server/serverPluginModuleRewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { readBody, cleanUrl, isExternalUrl } from '../utils'
import chalk from 'chalk'
import { isCSSRequest } from '../utils/cssUtils'
import { envPublicPath } from './serverPluginEnv'

const debug = require('debug')('vite:rewrite')

Expand Down Expand Up @@ -124,6 +125,7 @@ export function rewriteImports(
debug(`${importer}: rewriting`)
const s = new MagicString(source)
let hasReplaced = false
let hasInjectedEnv = false
let hasRewrittenForHMR = false

const prevImportees = importeeMap.get(importer)
Expand Down Expand Up @@ -178,15 +180,21 @@ export function rewriteImports(
}
} else {
if (id === 'import.meta') {
if (
!hasRewrittenForHMR &&
source.substring(start, end + 4) === 'import.meta.hot'
) {
if (!hasRewrittenForHMR && source.slice(end, end + 4) === '.hot') {
debugHmr(`rewriting ${importer} for HMR.`)
rewriteFileWithHMR(root, source, importer, resolver, s)
hasRewrittenForHMR = true
hasReplaced = true
}

if (!hasInjectedEnv && source.slice(end, end + 4) === '.env') {
s.prepend(
`import __VITE_ENV__ from "${envPublicPath}";\n` +
`import.meta.env = __VITE_ENV__;\n`
)
hasInjectedEnv = true
hasReplaced = true
}
} else {
debug(`[vite] ignored dynamic import(${id})`)
}
Expand Down
19 changes: 14 additions & 5 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,22 @@ describe('vite', () => {
})

test('env variables', async () => {
expect(await getText('.base')).toMatch(`process.env.BASE_URL: /`)
expect(await getText('.node-env')).toMatch(
`process.env.NODE_ENV: ${isBuild ? 'production' : 'development'}`
)
const mode = isBuild ? 'production' : 'development'

expect(await getText('.base')).toMatch(`BASE_URL: /`)
expect(await getText('.mode')).toMatch(`MODE: ${mode}`)
expect(await getText('.dev')).toMatch(`DEV: ${!isBuild}`)
expect(await getText('.prod')).toMatch(`PROD: ${isBuild}`)
expect(await getText('.custom-env-variable')).toMatch(
'process.env.CUSTOM_ENV_VARIABLE: 9527'
'VITE_CUSTOM_ENV_VARIABLE: 9527'
)
expect(await getText('.effective-mode-file-name')).toMatch(
`VITE_EFFECTIVE_MODE_FILE_NAME: ${
isBuild ? `.env.production` : `.env.development`
}`
)

expect(await getText('.node-env')).toMatch(`NODE_ENV: ${mode}`)
})

test('module resolving', async () => {
Expand Down

0 comments on commit 51e9c83

Please sign in to comment.