Vue SFC bundleless 特判
问题背景
Vue 单文件组件不是普通 JavaScript 文件。@rsbuild/plugin-vue 底层使用 rspack-vue-loader 将 .vue 文件拆成 script、template、style 等部分,再通过 helper 组合成最终组件。
在普通 bundle 模式下,这些 loader 内部模块会被打进 bundle,用户通常感知不到。但在 Rslib 的 bundleless 模式下,外部化逻辑会拦截模块请求并尝试保留模块结构。这就可能把 Vue loader 内部 request 错当成用户源码模块。
具体冲突
Rslib bundleless 想保留的是:
这种用户源码层面的 import。
但 rspack-vue-loader 可能生成:
这些不是公共模块边界。它们只是 loader 编译 .vue 时的内部模块。
如果 Rslib 把它们 external 到最终产物中,消费者会看到 loader 内部路径,或者看到带 ?vue&type= 的虚拟 request。消费者项目没有义务安装相同 loader,也不应该解析这些内部 request。
当前策略
Core 在 composeBundlelessExternalConfig 中定义:
然后对两类 request 放行:
这里的 callback() 表示不 external、不重写,让 Rspack 正常处理并 bundle 这些内部模块。
关键源码
源码位置:
packages/core/src/config.ts:1379packages/core/src/config.ts:1499
相关测试:
tests/integration/vuetests/e2e/vue-component
tests/integration/vue/index.test.ts 的 bundleless 快照能看到最终输出是普通 JS 和 CSS 文件,而不是 loader 内部 request。
坏产物示例
如果没有这层特判,可能出现类似:
这种产物有几个问题:
- 消费者需要解析
rspack-vue-loader内部模块。 - 消费者需要理解
?vue&type=虚拟请求。 .vue的内部拆分结构泄露到库 API。- loader 版本变化会破坏已发布产物。
Rslib 要发布的是库,不是把 loader 编译过程交给消费者重跑。
正确产物目标
正确目标是:
- 用户源码的模块边界保留。
- Vue SFC 内部 block 组合逻辑被编译进输出 JS。
- CSS 被抽取到对应 CSS 文件。
- import 路径只指向正常 JS/CSS 产物。
- 产物不包含
rspack-vue-loader内部 request。
例如 bundleless 下可以看到类似:
而 Button.js 内部已经是 Vue 运行时可执行的组件对象和 render 相关逻辑。
为什么不是所有 Vue request 都放行
判断条件有两个约束:
- request 必须包含
rspack-vue-loader。 - request 必须包含
dist/exportHelper.js或?vue&type=。
这避免误伤用户正常 import。例如用户自己的文件路径里包含 vue,不会被放行。用户 import vue 包本身,也不会被这个规则处理。
这个特判范围窄,是合理的。
为什么 Svelte 没同类逻辑
Svelte 目前没有在 core 中出现类似 rspack-svelte-loader 的 request 特判。原因不是 Svelte 不需要编译,而是当前 loader 输出没有以同样方式把内部 block request 暴露到 Rslib bundleless external 的冲突点。
如果未来 Svelte loader 也生成类似:
并且这些 request 进入 bundleless external,那么才需要同类处理。
修改风险
这段逻辑绑定 rspack-vue-loader 的内部 request 形态。升级 loader 时要确认:
- helper 路径是否仍是
dist/exportHelper.js。 - virtual block query 是否仍包含
?vue&type=。 - 新增的内部 request 是否需要放行。
- 旧规则是否会误伤新的用户 request。
如果改动,必须跑 Vue integration 和 E2E。