@@ -2,12 +2,14 @@ import { transform } from 'esbuild'
22import { TraceMap , decodedMap , encodedMap } from '@jridgewell/trace-mapping'
33import type { ResolvedConfig } from '../config'
44import type { Plugin } from '../plugin'
5- import { escapeRegex , getHash } from '../utils'
5+ import { escapeRegex } from '../utils'
66import { isCSSRequest } from './css'
77import { isHTMLRequest } from './html'
88
99const nonJsRe = / \. j s o n (?: $ | \? ) /
1010const isNonJsRequest = ( request : string ) : boolean => nonJsRe . test ( request )
11+ const importMetaEnvMarker = '__vite_import_meta_env__'
12+ const bareImportMetaEnvRe = / i m p o r t \. m e t a \. e n v (? ! \. ) \b /
1113
1214export 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