Skip to content

Commit

Permalink
feat: allow reassigning define globals (#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Feb 18, 2022
1 parent 7014c2b commit 15ea3a3
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 42 deletions.
30 changes: 0 additions & 30 deletions packages/vitest/src/node/plugins/envReplacer.ts

This file was deleted.

54 changes: 42 additions & 12 deletions packages/vitest/src/node/plugins/index.ts
@@ -1,12 +1,11 @@
import type { Plugin as VitePlugin } from 'vite'
import { configDefaults } from '../../defaults'
import type { UserConfig } from '../../types'
import type { ResolvedConfig, UserConfig } from '../../types'
import { deepMerge, ensurePackageInstalled, notNullish } from '../../utils'
import { resolveApiConfig } from '../config'
import { Vitest } from '../core'
import { GlobalSetupPlugin } from './globalSetup'
import { MocksPlugin } from './mock'
import { EnvReplacerPlugin } from './envReplacer'

export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest()): Promise<VitePlugin[]> {
let haveStarted = false
Expand All @@ -27,6 +26,39 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())
const preOptions = deepMerge({}, options, viteConfig.test ?? {})
preOptions.api = resolveApiConfig(preOptions)

// store defines for globalThis to make them
// reassignable when running in worker in src/runtime/setup.ts
const defines: Record<string, any> = {}

for (const key in viteConfig.define) {
const val = viteConfig.define[key]
let replacement: any
try {
replacement = typeof val === 'string' ? JSON.parse(val) : val
}
catch {
// probably means it contains reference to some variable,
// like this: "__VAR__": "process.env.VAR"
continue
}
if (key.startsWith('import.meta.env.')) {
const envKey = key.slice('import.meta.env.'.length)
process.env[envKey] = replacement
delete viteConfig.define[key]
}
else if (key.startsWith('process.env.')) {
const envKey = key.slice('process.env.'.length)
process.env[envKey] = replacement
delete viteConfig.define[key]
}
else if (!key.includes('.')) {
defines[key] = replacement
delete viteConfig.define[key]
}
}

(options as ResolvedConfig).defines = defines

return {
// we are setting NODE_ENV when running CLI to 'test',
// but it can be overridden
Expand All @@ -37,6 +69,14 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())
// setting this option can bypass that and fallback to cjs version
mainFields: [],
},
define: {
'process.env.NODE_ENV': 'process.env.NODE_ENV',
'global.process.env.NODE_ENV': 'global.process.env.NODE_ENV',
'globalThis.process.env.NODE_ENV': 'globalThis.process.env.NODE_ENV',
// so people can reassign envs at runtime
// import.meta.env.VITE_NAME = 'app' -> process.env.VITE_NAME = 'app'
'import.meta.env': 'process.env',
},
server: {
...preOptions.api,
open: preOptions.ui && preOptions.open
Expand Down Expand Up @@ -74,15 +114,6 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())

for (const name in envs)
process.env[name] ??= envs[name]

// account for user env defines
for (const key in viteConfig.define) {
if (key.startsWith('import.meta.env.')) {
const val = viteConfig.define[key]
const envKey = key.slice('import.meta.env.'.length)
process.env[envKey] = typeof val === 'string' ? JSON.parse(val) : val
}
}
},
async configureServer(server) {
if (haveStarted)
Expand All @@ -97,7 +128,6 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())
await server.watcher.close()
},
},
EnvReplacerPlugin(),
MocksPlugin(),
GlobalSetupPlugin(ctx),
options.ui
Expand Down
9 changes: 9 additions & 0 deletions packages/vitest/src/runtime/setup.ts
Expand Up @@ -8,6 +8,10 @@ import { rpc } from './rpc'

let globalSetup = false
export async function setupGlobalEnv(config: ResolvedConfig) {
// should be redeclared for each test
// if run with "threads: false"
setupDefines(config.defines)

if (globalSetup)
return

Expand All @@ -20,6 +24,11 @@ export async function setupGlobalEnv(config: ResolvedConfig) {
(await import('../integrations/globals')).registerApiGlobally()
}

function setupDefines(defines: Record<string, any>) {
for (const key in defines)
(globalThis as any)[key] = defines[key]
}

export function setupConsoleLogSpy() {
const stdout = new Writable({
write(data, encoding, callback) {
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/src/types/config.ts
Expand Up @@ -324,5 +324,7 @@ export interface ResolvedConfig extends Omit<Required<UserConfig>, 'config' | 'f

reporters: (Reporter | BuiltinReporters)[]

defines: Record<string, any>

api?: ApiConfig
}
55 changes: 55 additions & 0 deletions test/core/test/define.test.ts
@@ -0,0 +1,55 @@
import { afterAll, expect, test } from 'vitest'

declare let __DEFINE__: string
declare let __JSON__: any
declare let __MODE__: string
declare let SOME: {
VARIABLE: string
SOME: {
VARIABLE: string
}
}

// functions to test that they are not statically replaced
const get__DEFINE__ = () => __DEFINE__
const get__JSON__ = () => __JSON__
const get__MODE__ = () => __MODE__

const MODE = process.env.MODE

afterAll(() => {
process.env.MODE = MODE
})

test('process.env.HELLO_PROCESS is defined on "defined" but exists on process.env', () => {
expect('HELLO_PROCESS' in process.env).toBe(true)
expect(process.env.HELLO_PROCESS).toBe('hello process')
})

test('can redeclare standard define', () => {
expect(get__DEFINE__()).toBe('defined')
__DEFINE__ = 'new defined'
expect(get__DEFINE__()).toBe('new defined')
})

test('can redeclare json object', () => {
expect(get__JSON__()).toEqual({ hello: 'world' })
__JSON__ = { hello: 'test' }
const name = '__JSON__'
expect(get__JSON__()).toEqual({ hello: 'test' })
expect((globalThis as any)[name]).toEqual({ hello: 'test' })
})

test('reassigning __MODE__', () => {
const env = process.env.MODE
expect(get__MODE__()).toBe(env)
process.env.MODE = 'development'
expect(get__MODE__()).toBe('development')
})

test('dotted defines are processed by Vite, but cannot be reassigned', () => {
expect(SOME.VARIABLE).toBe('variable')
expect(SOME.SOME.VARIABLE).toBe('nested variable')
SOME.VARIABLE = 'new variable'
expect(SOME.VARIABLE).not.toBe('new variable')
})
10 changes: 10 additions & 0 deletions test/core/vitest.config.ts
Expand Up @@ -19,6 +19,16 @@ export default defineConfig({
],
define: {
'import.meta.env.TEST_NAME': '"hello world"',
'process.env.HELLO_PROCESS': '"hello process"',
// can reassign
'__DEFINE__': '"defined"',
'__JSON__': JSON.stringify({ hello: 'world' }),
// edge cases
// should not be available for reassigning as __MODE__ = 'test2'
// but can reassign with process.env.MODE = 'test2'
'__MODE__': 'process.env.MODE',
'SOME.VARIABLE': '"variable"',
'SOME.SOME.VARIABLE': '"nested variable"',
},
test: {
testTimeout: 2000,
Expand Down

0 comments on commit 15ea3a3

Please sign in to comment.