Vite构建ES6代码不完整,导出内容丢失问题排查
我正在搭建一个仓库,用于将HTML、CSS和JS转译为可在项目中导入的模块,但Vite无法正确转译JS文件——输出脚本中缺少我编写的部分代码。
我的配置与代码
Vite配置(临时禁用minify以查看可读内容)
import { defineConfig } from 'vite' import { resolve } from 'path' import { ViteMinifyPlugin } from 'vite-plugin-minify' export default defineConfig({ copyPublicDir: false, build: { minify: false, rollupOptions: { input: { footer: resolve(__dirname, 'src/footer/footer.html'), navbar: resolve(__dirname, 'src/navbar/navbar.html'), }, output: { entryFileNames: `assets/[name].js`, chunkFileNames: `assets/[name].js`, assetFileNames: `assets/[name].[ext]` } }, }, plugins: [ // ViteMinifyPlugin({}) ] })
测试用JS代码
function loadUserInfo() { return { email: "janedoe@abc.com", name: "Jane Doe", username: "janedoe" } } const world = "hello world" console.log("testing") export { loadUserInfo, world }
转译后的JS代码
(function polyfill() { const relList = document.createElement("link").relList; if (relList && relList.supports && relList.supports("modulepreload")) { return; } for (const link of document.querySelectorAll('link[rel="modulepreload"]')) { processPreload(link); } new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type !== "childList") { continue; } for (const node of mutation.addedNodes) { if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node); } } }).observe(document, { childList: true, subtree: true }); function getFetchOpts(link) { const fetchOpts = {}; if (link.integrity) fetchOpts.integrity = link.integrity; if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy; if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include"; else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit"; else fetchOpts.credentials = "same-origin"; return fetchOpts; } function processPreload(link) { if (link.ep) return; link.ep = true; const fetchOpts = getFetchOpts(link); fetch(link.href, fetchOpts); } })(); console.log("testing");
可以看到,只有console.log("testing")被保留,loadUserInfo和world的导出完全丢失。
更新情况
禁用Tree Shaking后问题暂时解决,但重新启用minify后,导出内容再次被移除:
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))n(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&n(o)}).observe(document,{childList:!0,subtree:!0});function s(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?r.credentials="include":e.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(e){if(e.ep)return;e.ep=!0;const r=s(e);fetch(e.href,r)}})();console.log("testing");
问题原因
这是Vite底层依赖的Rollup以及压缩工具(Terser/esbuild)的死代码消除机制导致的:
你的loadUserInfo和world虽然被export,但在当前入口(HTML文件)里没有被任何代码引用,工具会判定它们是"未使用的死代码",在打包或压缩时自动移除。
禁用Tree Shaking能解决是因为关闭了Rollup的死代码检测,但开启minify后,压缩工具同样会执行死代码消除,所以问题复现。
解决方案
1. 标记文件为有副作用
在项目根目录的package.json中添加配置,告诉Vite这些JS文件存在副作用,不能随意移除导出内容:
"sideEffects": ["./src/**/*.js"]
如果只有特定文件需要保留,可以指定具体路径;如果整个项目都需要,直接设为true即可。
2. 在HTML中显式引用导出内容
如果你的JS是通过HTML作为入口打包的,可以在HTML里添加模块脚本,主动引用导出的内容,避免被判定为死代码:
<script type="module"> // 根据实际路径调整 import { loadUserInfo, world } from './assets/footer.js'; // 无需实际使用,只要引用即可触发工具保留导出 void loadUserInfo; void world; </script>
3. 针对性关闭死代码消除
如果需要保留Tree Shaking和minify的其他优化,可以单独关闭死代码消除逻辑:
- 关闭Rollup的Tree Shaking:
export default defineConfig({ build: { rollupOptions: { treeshake: false, // ...其他配置 }, }, });
- 关闭Terser的未使用代码消除(如果用的是terser作为minify工具):
export default defineConfig({ build: { minify: 'terser', terserOptions: { compress: { unused: false, // 禁用未使用代码消除 }, }, // ...其他配置 }, });
注意:esbuild的minify目前没有细粒度关闭死代码消除的选项,若使用esbuild则需要切换为terser。
4. 调整导出方式
可以改用export default导出对象,确保工具不会误判为死代码:
// 使用default导出 export default { loadUserInfo: function() { return { email: "janedoe@abc.com", name: "Jane Doe", username: "janedoe" } }, world: "hello world" }
内容的提问来源于stack exchange,提问作者nayiaw

