Vite 库模式输出 ESM 格式时的依赖处理方案

🧩 环境信息

json 复制代码
{
  "vite": "5.4.15",
  "vue": "2.7.16"
}

📖 背景说明

当前项目是一个前端框架型宿主项目,主要负责:

  • 管理功能菜单与用户模块;

  • 通过 iframe 嵌入多个子前端项目

  • 在"工作台"页面中动态加载来自其他前端项目的小组件(Widget)

为了便于这些小组件的动态加载与复用,我们使用了 Vite 的库模式(Library Mode) 来进行打包。


⚙️ 初始配置

最早的 widget.vite.config.js 如下:

js 复制代码
return {
  build: {
    lib: {
      entry: {
        customReport: 'widgets/custom_report/index.js'
      },
      fileName: (_, entryName) => `${entryName}.js`,
      formats: ['es']
    },
    rollupOptions: {
      output: {
        chunkFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[name].[ext]',
        manualChunks: id => {
          if (id.includes('node_modules')) {
            for (let chunk of chunks) {
              if (id.includes(`node_modules/${chunk}`)) return chunk
            }
          }
        }
      }
    }
  }
}

起初并没有将 vue 从打包中排除,因为看似一切正常,直到后来出现了严重问题👇


💥 问题分析:Vue 多实例导致渲染死循环

在某个小组件中使用函数式组件渲染 VNode 数组时,页面发生死循环渲染

vue 复制代码
<template>
  <VNode :data="bottomInst" />
</template>

<script>
export default {
  components: {
    VNode: {
      functional: true,
      render: (h, ctx) =>
        isFunction(ctx.props.data) ? ctx.props.data() : ctx.props.data
    }
  }
}
</script>
  • 页面会不断触发 render(),导致浏览器卡死。

  • 后续测试发现是因为宿主项目与子组件中使用的 Vue 实例不一致

换句话说,小组件自己又打包进了一份 vue,与宿主项目的 Vue 实例"冲突"了。


🧠 回到根源:Vue 外部化问题

Vite 官方文档明确建议:

当你的库要被宿主项目引用时,应使用 build.lib外部化处理依赖 ,例如 vuereact

官方示例:

js 复制代码
export default defineConfig({
  build: {
    lib: {
      entry: {
        'my-lib': resolve(__dirname, 'lib/main.js'),
        secondary: resolve(__dirname, 'lib/secondary.js')
      },
      name: 'MyLib'
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

⚠️ 实际遇到的问题

当配置为多入口时:

  • 默认打包格式会变为 ['es', 'cjs']

  • 如果强制设置 formats: ['umd'],Rollup 会报错;

  • 而在 ESM 模式下globals 配置不会生效(即不会转成全局变量访问)。

因此,当外部化 vue 后,代码仍然保留:

js 复制代码
import vue from 'vue'

浏览器在执行时会报错:

javascript 复制代码
Uncaught TypeError: Failed to resolve module specifier "vue".

因为浏览器根本不知道 "vue" 这个导入路径应该从哪里加载。


✅ 正确的解决方案

方法一:使用 HTML Import Map(推荐,前提是宿主环境支持)

如果不需要兼容 Chrome 89 以下版本,可以在宿主项目的 HTML 中声明:

html 复制代码
<script type="importmap">
{
  "imports": {
    "vue": "https://example.com/vue.js"
  }
}
</script>

这样浏览器在解析到:

js 复制代码
import Vue from 'vue'

时,会自动从 /vue.js 加载,而不是去找 node_modules

✅ 优点:

  • 符合原生 ES Module 机制;

  • 适合现代浏览器;

  • 简洁直观。

❌ 缺点:

  • Chrome 89 以下(含 IE)不支持 importmap

  • 需要在宿主 HTML 中维护映射。


方法二:使用 Rollup 的 output.paths 配置

如果你希望在构建阶段就将路径转换成一个可访问的 URL,可使用:

js 复制代码
return {
  build: {
    lib: {
      entry: {
        customReport: 'widgets/custom_report/index.js'
      },
      fileName: (_, entryName) => `${entryName}.js`,
      formats: ['es']
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        paths: {
          vue: '/dist/assets/vue.js'
        }
      }
    }
  }
}

打包后:

js 复制代码
// ✅ 自动替换成可访问路径
import vue from '/dist/assets/vue.js'

宿主项目只需确保 /dist/assets/vue.js 存在(即宿主与子项目共享同一份 Vue 文件)。

✅ 优点:

  • 不依赖 importmap;

  • 可控制精确路径;

  • 浏览器 100% 可识别。

❌ 缺点:

  • 宿主项目需要在相同路径下提供该文件;

  • 一旦路径变更,需要同步更新。


🧾 总结对比

方案 关键配置 优点 缺点 适用场景
方法一 ImportMap <script type="importmap"> 原生 ESM 支持、配置简单 浏览器兼容性要求高 现代浏览器环境
方法二 Rollup paths rollupOptions.output.paths 无需 importmap,构建期解决 路径需宿主一致 自定义部署结构、多版本环境

🚀 实践建议

  1. 统一宿主与子项目的 Vue 实例来源

    → 不要在子组件中重复打包 vue

  2. 打包时外部化 Vue

    js 复制代码
    external: ['vue']
  3. 根据浏览器兼容性选择方案

    • 支持现代浏览器 → ✅ ImportMap;

    • 需要兼容旧环境 → ✅ Rollup paths

相关推荐
亿元程序员8 分钟前
明明直接用就可以了,非要在Creator里面写???
前端
wadesir32 分钟前
Nginx负载均衡代理协议详解(从零开始搭建高可用Web服务)
前端·nginx·负载均衡
秋氘渔33 分钟前
Vue 3 组合式写法:侦听器 watch 和 watchEffect 的区别及使用技巧
前端·javascript·vue.js·watch·watcheffect
想睡八个小时42 分钟前
已包含的文件名 “a.vue“ 仅大小写与文件名 “A.vue“ 不同
前端·vscode
The_era_achievs_hero1 小时前
Echarts
前端·javascript·echarts
涔溪2 小时前
Vite 和 Webpack 这两款主流前端构建工具的核心区别,包括它们的设计理念、工作机制和实际使用体验上的差异。
前端·webpack·vite
0思必得02 小时前
[Web自动化] 开发者工具元素(Elements)面板
运维·前端·自动化·web自动化·开发者工具
遇到困难睡大觉哈哈2 小时前
Harmony os ——ArkTS 语言笔记(五):泛型、空安全与可选链
前端·笔记·安全·harmonyos·鸿蒙
八哥程序员3 小时前
你真的理解了 javascript 中的原型及原型链?
前端·javascript
冴羽3 小时前
10 个 Nano Banana Pro 专业级生图技巧
前端·人工智能·aigc