Skip to content

Commit bb9912c

Browse files
committed
refactor(build): change a strategy to handle import.meta.env and undefined key
1 parent 779f779 commit bb9912c

File tree

2 files changed

+54
-30
lines changed

2 files changed

+54
-30
lines changed

packages/vite/src/node/__tests__/plugins/define.spec.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,19 @@ describe('definePlugin', () => {
8282
await transform(
8383
'const isLegacy = import.meta.env.LEGACY;\nimport.meta.env.UNDEFINED && console.log(import.meta.env.UNDEFINED);',
8484
),
85-
).toBe(
86-
'var define_import_meta_env_default = {"BASE_URL": "/", "MODE": "development", "DEV": true, "PROD": false, "SSR": false, "LEGACY": __VITE_IS_LEGACY__};\nconst isLegacy = __VITE_IS_LEGACY__;\ndefine_import_meta_env_default.UNDEFINED && console.log(define_import_meta_env_default.UNDEFINED);\n',
87-
)
85+
).toMatchInlineSnapshot(`
86+
"const isLegacy = __VITE_IS_LEGACY__;
87+
"
88+
`)
89+
})
90+
91+
test('replace bare import.meta.env', async () => {
92+
const transform = await createDefinePluginTransform()
93+
expect(await transform('const env = import.meta.env;'))
94+
.toMatchInlineSnapshot(`
95+
"const __vite_import_meta_env__ = {"BASE_URL": "/", "MODE": "development", "DEV": true, "PROD": false, "SSR": false};
96+
const env = __vite_import_meta_env__;
97+
"
98+
`)
8899
})
89100
})

packages/vite/src/node/plugins/define.ts

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { transform } from 'esbuild'
22
import { TraceMap, decodedMap, encodedMap } from '@jridgewell/trace-mapping'
33
import type { ResolvedConfig } from '../config'
44
import type { Plugin } from '../plugin'
5-
import { escapeRegex, getHash } from '../utils'
5+
import { escapeRegex } from '../utils'
66
import { isCSSRequest } from './css'
77
import { isHTMLRequest } from './html'
88

99
const nonJsRe = /\.json(?:$|\?)/
1010
const isNonJsRequest = (request: string): boolean => nonJsRe.test(request)
11+
const importMetaEnvMarker = '__vite_import_meta_env__'
12+
const bareImportMetaEnvRe = /import\.meta\.env(?!\.)\b/
1113

1214
export function definePlugin(config: ResolvedConfig): Plugin {
1315
const isBuild = config.command === 'build'
@@ -69,13 +71,24 @@ export function definePlugin(config: ResolvedConfig): Plugin {
6971
define['import.meta.env.SSR'] = ssr + ''
7072
}
7173
if ('import.meta.env' in define) {
72-
define['import.meta.env'] = serializeDefine({
73-
...importMetaEnvKeys,
74-
SSR: ssr + '',
75-
...userDefineEnv,
76-
})
74+
define['import.meta.env'] = importMetaEnvMarker
7775
}
7876

77+
const importMetaEnv = {
78+
...importMetaEnvKeys,
79+
SSR: ssr + '',
80+
...userDefineEnv,
81+
}
82+
const importMetaEnvVal = serializeDefine(importMetaEnv)
83+
// replace bare `import.meta.env` manually
84+
const banner = `const ${importMetaEnvMarker} = ${importMetaEnvVal};`
85+
// create regex pattern to match undefined `import.meta.env` properties
86+
// to replace it to `undefined` directly
87+
const undefinedPattern = new RegExp(
88+
`import\\.meta\\.env\\.(?!${Object.keys(importMetaEnv).map(escapeRegex).join('|')}).+?\\b`,
89+
'g',
90+
)
91+
7992
// Create regex pattern as a fast check before running esbuild
8093
const patternKeys = Object.keys(userDefine)
8194
if (replaceProcessEnv && Object.keys(processEnv).length) {
@@ -88,7 +101,7 @@ export function definePlugin(config: ResolvedConfig): Plugin {
88101
? new RegExp(patternKeys.map(escapeRegex).join('|'))
89102
: null
90103

91-
return [define, pattern] as const
104+
return [define, pattern, undefinedPattern, banner] as const
92105
}
93106

94107
const defaultPattern = generatePattern(false)
@@ -116,14 +129,30 @@ export function definePlugin(config: ResolvedConfig): Plugin {
116129
return
117130
}
118131

119-
const [define, pattern] = ssr ? ssrPattern : defaultPattern
132+
const [define, pattern, undefinedPattern, banner] = ssr
133+
? ssrPattern
134+
: defaultPattern
120135
if (!pattern) return
121136

122137
// Check if our code needs any replacements before running esbuild
123138
pattern.lastIndex = 0
124139
if (!pattern.test(code)) return
125140

126-
return await replaceDefine(code, id, define, config)
141+
// process undefined imports.meta.env properties
142+
const defineWithUndefined = { ...define }
143+
undefinedPattern.lastIndex = 0
144+
for (const undefinedEnvKey of [...code.matchAll(undefinedPattern)]) {
145+
defineWithUndefined[undefinedEnvKey[0]] = 'undefined'
146+
}
147+
148+
return await replaceDefine(
149+
code,
150+
id,
151+
defineWithUndefined,
152+
config,
153+
// if there is bare `import.meta.env`, then add the manual `import.meta.env`
154+
bareImportMetaEnvRe.test(code) ? banner : undefined,
155+
)
127156
},
128157
}
129158
}
@@ -133,21 +162,8 @@ export async function replaceDefine(
133162
id: string,
134163
define: Record<string, string>,
135164
config: ResolvedConfig,
165+
banner?: string,
136166
): Promise<{ code: string; map: string | null }> {
137-
// Because esbuild only allows JSON-serializable values, and `import.meta.env`
138-
// may contain values with raw identifiers, making it non-JSON-serializable,
139-
// we replace it with a temporary marker and then replace it back after to
140-
// workaround it. This means that esbuild is unable to optimize the `import.meta.env`
141-
// access, but that's a tradeoff for now.
142-
const replacementMarkers: Record<string, string> = {}
143-
const env = define['import.meta.env']
144-
if (env && !canJsonParse(env)) {
145-
const marker = `{ "_${getHash(env, env.length - 2)}_": "" }`
146-
// result generated by esbuild will not have the quotes around the keys
147-
replacementMarkers[marker.replace('"', '').replace('"', '')] = env
148-
define = { ...define, 'import.meta.env': marker }
149-
}
150-
151167
const esbuildOptions = config.esbuild || {}
152168

153169
const result = await transform(code, {
@@ -157,6 +173,7 @@ export async function replaceDefine(
157173
define,
158174
sourcefile: id,
159175
sourcemap: config.command === 'build' ? !!config.build.sourcemap : true,
176+
banner,
160177
})
161178

162179
// remove esbuild's <define:...> source entries
@@ -179,10 +196,6 @@ export async function replaceDefine(
179196
}
180197
}
181198

182-
for (const marker in replacementMarkers) {
183-
result.code = result.code.replaceAll(marker, replacementMarkers[marker])
184-
}
185-
186199
return {
187200
code: result.code,
188201
map: result.map || null,

0 commit comments

Comments
 (0)