直接调用 console.log(import.meta.env) 会标记整个对象为已使用,阻止打包工具静态分析和剔除,导致本可摇树的代码残留;应只访问明确字段如 import.meta.env.vite_api_url。

直接调用 console.log(import.meta.env) 本身不会“导致周围代码无法被摇树”,真正的问题在于:这个操作会**让整个 import.meta.env 对象被标记为“已使用”**,从而阻止打包工具(如 Rollup,Vite 底层所用)对它的静态分析和安全剔除 —— 进而可能连带保留本可被删除的、与该对象同模块或同作用域的其他代码。
为什么 import.meta.env 是个“全量引用陷阱”
Vite 在构建时会把 import.meta.env 静态替换为一个字面量对象(例如 { VITE_API_URL: "https://api.example.com", MODE: "production" })。但这个对象是“整体注入”的,不是按属性粒度导出的模块。
一旦你在代码中写了:
-
console.log(import.meta.env)—— 工具必须保留整个对象,因为无法判断你后续是否要读取其中某个未显式写出的字段(比如import.meta.env.UNDEFINED_KEY); -
console.log({ ...import.meta.env })或JSON.stringify(import.meta.env)—— 同样触发全量保留; - 哪怕只是
if (import.meta.env) { ... },也可能因控制流分析保守而保留关联逻辑。
它如何干扰 Tree Shaking 的判定链
Tree Shaking 依赖精确的“导出-导入-使用”静态图。而 import.meta.env 不是 ESM 模块导出,它是构建器注入的全局符号。工具对它的处理逻辑是:
- 识别到任何对
import.meta.env的访问 → 视为“环境对象被使用” → 注入完整对象字面量; - 若该对象出现在某个函数/模块顶层,且该模块还导出了其他变量或副作用代码,Rollup 可能因“不确定是否纯”而放弃对该模块做深度 shaking;
- 尤其当
console.log(import.meta.env)和你的业务逻辑写在同一个文件、甚至同一作用域块里,压缩/优化阶段可能不敢剔除看似“无关”的函数或常量。
更隐蔽的风险:开发期无感,构建后膨胀
开发时(vite dev)import.meta.env 是真实对象,console.log 看似无害;但构建后它变成硬编码对象字面量,体积变大,且因全量存在,可能阻止 rollup 的 pure 判断,使本可被 tree-shake 的工具函数、配置对象、甚至未使用的 if (import.meta.env.DEV) { ... } 分支残留。
例如:
import { someUtil } from './utils';
console.log(import.meta.env); // ← 这一行会让 utils 模块的副作用或导出难以被安全剔除
if (import.meta.env.DEV) {
console.log('dev only'); // 即使 DEV 是 false,这段也可能因上下文污染未被移除
}
安全替代方案
只访问明确需要的字段,不碰整个对象:
- ✅ 推荐:
console.log(import.meta.env.VITE_API_URL)—— 字段级访问,工具可精准替换并保留 shaking 能力; - ✅ 封装成函数:
const getEnv = () => ({ API_URL: import.meta.env.VITE_API_URL });,再调用console.log(getEnv()); - ✅ 构建前临时注释掉所有
import.meta.env全量日志,仅保留必要字段调试; - ❌ 避免:
console.table(import.meta.env)、for (const k in import.meta.env) {...}、Object.keys(import.meta.env)—— 这些全部触发全量引用。
本质上,这不是 bug,而是构建机制的设计约束:静态替换的对象不具备模块化导出的可分析性。克制全量访问,才能守住 Tree Shaking 的效果边界。











