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.
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.