fix(build): handle invalid JSON in import.meta.env by yuzheng14 · Pull Request #17648 · vitejs/vite (original) (raw)

Bug Reproduction

fix #17710

NOTE(bluwy): the solution description below is outdated. The new solution is based on #17648 (comment)

When import.meta.env has some invalid JSON value, such as __VITE_IS_LEGACY__ imported by @vitejs/plugin-legacy, Vite will replace the import.meta.env in import.meta.env.SOMEVAR_UNDEFINED to a plain object. Then the javascript file will be an invalid javascript file which will produce a error when handled by proceeding commonjs plugin.

The minimum reproducible set is here: https://stackblitz.com/edit/stackblitz-starters-xr8lwk?file=index.html

Reason

As previous description, vite:define plugin will replace import.meta.env.SOMEVAR_UNDEFINED to {"BASE_URL": "/", "MODE": "development", "DEV": true, "PROD": false, "SSR": false, "LEGACY": __VITE_IS_LEGACY__}.SOMEVAR_UNDEFINED.

The reason is, vite:define will replace the actual import.meta.env with a marker.

if (env && !canJsonParse(env)) {
const marker = `_${getHash(env, env.length - 2)}_`
replacementMarkers[marker] = env
define = { ...define, 'import.meta.env': marker }
}

The original value of import.meta.env is a object, while the marker is an identifier. The replace strategy of esbuild to handle this two type is different.

Replacement expressions other than arrays and objects are substituted inline, which means that they can participate in constant folding. Array and object replacement expressions are stored in a variable and then referenced using an identifier instead of being substituted inline, which avoids substituting repeated copies of the value but means that the values don't participate in constant folding.

--esbuild documentation

Consider the following example.

import.meta.env.SOMEVAR_UNDEFINED import.meta.env.SOMEVAR_UNDEFINED

When import.meta.env is a identifier, esbuild will replace it in place.

hashGeneratedByViteDefine.SOMEVAR_UNDEFINED hashGeneratedByViteDefine.SOMEVAR_UNDEFINED

After that, vite:define will replace the marker with the original import.meta.env

for (const marker in replacementMarkers) {
result.code = result.code.replaceAll(marker, replacementMarkers[marker])
}

{"BASE_URL": "/", "MODE": "development", "DEV": true, "PROD": false, "SSR": false, "LEGACY": VITE_IS_LEGACY}.SOMEVAR_UNDEFINED {"BASE_URL": "/", "MODE": "development", "DEV": true, "PROD": false, "SSR": false, "LEGACY": VITE_IS_LEGACY}.SOMEVAR_UNDEFINED

Now it's a invalid javascript code.

Solution

Align the type of the original and marker.

I generate a marker which is a valid JSON, so that the replace strategy will be same.

Radical Solution

I found that the value of import.meta.env is spread in config.

{ "loader": "js", "charset": "utf8", "platform": "neutral", "define": { "process.env": "{}", "global.process.env": "{}", "globalThis.process.env": "{}", "process.env.NODE_ENV": ""production"", "global.process.env.NODE_ENV": ""production"", "globalThis.process.env.NODE_ENV": ""production"", "import.meta.hot": "undefined", "import.meta.env.BASE_URL": ""/"", "import.meta.env.MODE": ""production"", "import.meta.env.DEV": "false", "import.meta.env.PROD": "true", "import.meta.env.SSR": "false", "import.meta.env.LEGACY": "VITE_IS_LEGACY", "import.meta.env": "ecc578fd856ad0ab7198de04a00b9405c7a6d2549217f51adfff717ee9f7b851_____________________________________________" }, "sourcefile": "/Users/yuzheng14/code/repositories/legacy-conflict/node_modules/.pnpm/vue@2.7.16/node_modules/vue/dist/vue.runtime.esm.js", "sourcemap": false }

And esbuild will replace it first.

Under this case, import.env.meta will be only be replace when the value of it is not defined. So why not leave a {} for import.meta.env just like process.env?

Object.assign(processEnv, {
'process.env': `{}`,
'global.process.env': `{}`,
'globalThis.process.env': `{}`,
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
'global.process.env.NODE_ENV': JSON.stringify(nodeEnv),
'globalThis.process.env.NODE_ENV': JSON.stringify(nodeEnv),
})

But I couldn't aware the side effects the this solution, so that I choose to modify the generation of marker.