Bundleless、CSS 与资源
Bundleless 的目标
Bundleless 模式不是简单复制源码文件。Rslib 仍然使用 Rspack 编译每个入口,让源码经过 loader、插件、语法转换、CSS 处理和资源处理。它的目标是:保留源码模块结构,同时让输出 import 指向构建后的文件。
这比复制文件复杂,但它解决了几个实际问题:
- TypeScript、JSX、Vue、Svelte 等仍需要转换。
- CSS Modules 需要生成 JS mapping。
- 全局 CSS 需要抽取成
.css文件。 - 资源 import 需要保持可发布的相对路径。
- 无扩展名或 TS 扩展名 import 需要改成 JS 输出扩展名。
- dts 输出路径需要与 JS 输出路径对齐。
Bundleless 的 entry 模型
bundleless 中,如果用户没有配置 entry,Rslib 默认使用:
composeEntryConfig 会用 tinyglobby 扫描文件,并过滤声明文件。每个源文件都会成为一个 Rspack entry。entry name 不是简单 basename,而是相对于 outBase 的路径。
例如:
如果 outBase 是 src,entry name 大致是:
全局 CSS entry 会带 __rslib_css__ 前缀,用来标记后续要删除的虚拟 JS asset。
outBase 是 bundleless 的坐标系
outBase 的默认值是所有非声明输入文件的最长公共路径。它决定:
- 输出目录结构。
- entry name。
- bundleless external 是否处理某个请求。
- 源文件之间相对路径如何改写。
- CSS loader 如何计算路径。
- watch 模式如何监听上下文。
如果用户显式设置 outBase,Rslib 会按用户值解析为绝对路径。维护时要特别注意用户 outBase 可能不是 src,也可能是绝对路径。
Bundleless external 的本质
bundleless 输出依靠 external function 来“阻止打包并改写路径”。当某个源文件 import 另一个源文件时,Rslib 不希望把被 import 文件打进当前 entry,而是希望当前输出文件继续 import 另一个输出文件。
流程如下:
这个机制让 Rspack 对每个 entry 独立编译,但源文件之间仍以 import 关系保留。
Vue loader 虚拟请求的例外
Bundleless external 的默认策略是“源码文件之间的 import 不打进当前 entry,而是改写成输出路径”。但 Vue SFC loader 生成的内部请求不能按这个策略处理。rspack-vue-loader 会在编译 .vue 文件时生成虚拟 block request 和 helper request,例如:
这些 request 不是用户源码里希望保留到库产物中的公共 import,而是 loader 为了把 SFC 拆成 script、template、style 并重新组合组件而产生的内部模块。如果 Rslib 的 bundleless external 逻辑把它们当普通源码依赖处理,就可能在输出中泄露 loader 内部路径,甚至生成消费者项目无法解析的 import。
因此 composeBundlelessExternalConfig 里有一个明确的 Vue 专项例外:
当 request 命中以下两类情况时,Rslib 会直接 callback() 放行,让 Rspack 正常 bundle 这些内部模块:
这条规则的含义是:
- 用户源码之间的 import 继续按 bundleless 规则保留模块结构。
- Vue loader 生成的 helper 和虚拟 block request 不保留到产物中。
- 最终产物不应该暴露
rspack-vue-loader的内部实现细节。
这是 core 里非常明确的 Vue-specific hack。它存在的原因不是 Vue 组件库本身特殊,而是 Vue SFC loader 的模块图形态会和 Rslib 的 bundleless external 重写机制相冲突。
Redirect 配置
redirect 控制 bundleless 中不同资源类型的路径和扩展名改写:
维护 redirect 时要用产物验证。配置对象看起来简单,但每一项都会影响最终 import 字符串。
CSS 分两类
Rslib 在 bundleless 中区分两类 CSS:
原因是 CSS Modules 的 import 需要得到 class name mapping,运行时看到的是 JS 对象;全局 CSS import 只需要让样式文件存在并被引用。
cssExternalHandler 是 CSS 请求重写的中心。它会:
- 跳过 css-loader helper。
- 对 CSS 文件内部的 asset import 放行,不把 asset external 掉。
- 对 CSS Modules 改成 JS 输出扩展名。
- 对全局 CSS 改成
.css。
自定义 CSS extract loader
css/libCssExtractLoader.ts 是 bundleless CSS 的关键。普通应用构建里的 CSS extract 逻辑不完全适合库产物,因为库产物需要保留更精确的相对路径和 CSS Modules 语义。
Rslib 的 pluginLibCss 会在 Rsbuild bundler chain 中:
- 找到 CSS、SASS、LESS、STYLUS 规则。
- 把默认 mini css extract loader 替换成
libCssExtractLoader。 - 把默认 CSS extract plugin 替换成
LibCssExtractPlugin。 - 在 assets 阶段删除带
__rslib_css__标记的虚拟 JS asset。
维护 CSS 逻辑时,要同时检查普通 CSS、CSS Modules、预处理器、CSS 中 url、source map、banner/footer。
资源处理的目标
库构建里的资源处理和应用不同。应用构建通常可以把资源复制到 dist 并通过 publicPath 拼接 URL;库构建更需要保留相对 import,让消费者的 bundler 或运行时继续处理。
asset/assetConfig.ts 的核心目标:
- 默认
dataUriLimit = 0,不内联资源。 - 默认
assetPrefix = "auto"。 - 对 JS issuer 的资源设置 preserve import。
- 对 CSS issuer 使用复制出来的 asset rule,避免 CSS url 受到 JS preserve 影响。
- 修补 SVGR 相关 publicPath 行为。
JS issuer 和 CSS issuer
同一个 foo.svg,从 JS import 和从 CSS url 引入,语义不同:
JS import 通常需要保留为模块引用,CSS url 则需要保持 CSS 资源路径语义。pluginLibAsset 会基于 issuer 区分规则,给 CSS issuer 创建单独 oneOf。
SVGR 的特殊性
SVGR 同时涉及 SVG 作为组件和 SVG 作为 url。Rslib 做了几件修补:
- 如果用户使用 SVGR url-loader,替换 publicPath 为占位符。
- 使用
LibSvgrPatchPlugin在后续阶段移除不适合库产物的 runtime publicPath。 - bundle 模式下,
?url也设置 importMode preserve。 - bundleless 模式下,不支持 query import 时调整 issuer,让 SVG 走合适 loader。
SVGR 相关逻辑容易受 Rsbuild 和 loader 版本影响。改动时最好写 E2E 或 integration case 验证真实输出。
Watch 行为
bundleless entry 是 glob 展开的,普通文件变化可能需要重新扫描 entry。EntryChunkPlugin 会把 outBase 加入 Rspack compilation contextDependencies,帮助 watch 感知目录级变化。
这不是源码热更新意义上的 HMR,而是让 bundleless 多入口场景中新增文件、删除文件、改名等操作能被 watch 发现。
常见问题和排查
输出 import 仍然指向 .ts
检查:
- 当前是否
bundle: false。 autoExtension是否关闭。redirect.js.extension是否关闭。- 请求是否被用户 externals 提前命中。
- 请求是否来自 outBase 外部。
CSS Modules import 指向了 .css
检查:
- CSS Modules 文件名是否符合
.module.*。 output.cssModules.auto是否改变匹配规则。cssExternalHandler是否拿到了正确的 redirectedPath。
全局 CSS 生成空 JS 文件
全局 CSS entry 会产生虚拟 JS asset,Rslib 应在 pluginLibCss 的 processAssets 阶段删除它。如果还存在,检查 asset 名是否包含 __rslib_css__ 标记,以及插件是否只在 bundleless 下启用。
资源路径变成绝对 publicPath
检查用户是否显式设置了 publicPath。Rslib 只有在 publicPath 是 auto 时才会尽量 preserve import;用户显式设置 publicPath 时,应尊重用户配置。
测试建议
Bundleless 改动至少应覆盖:
- 单入口和多入口。
- 嵌套目录 entry。
- 无扩展名 import。
.ts到.mjs或.cjs重写。- CSS Modules。
- 全局 CSS。
- CSS 中 asset url。
- JS 中 asset import。
- Vue 或其他 loader 生成的虚拟请求。
- user externals 和 auto external 组合。