close
  • 简体中文
  • 故障排查

    部署 Storybook 到子目录或子路径

    自 v3.3.3 起。在 v3.3.2 及更早版本中,默认的 assetPrefix'/'(绝对路径),子目录部署需要手动配置。

    Rsbuild builder 默认使用相对资源路径 —— 与官方的 Vite(base: './')和 webpack(publicPath: '')builder 一致。Storybook 可以部署到任意子目录(例如 https://example.com/storybook/),无需额外配置。

    从 CDN 提供静态资源

    自 v3.3.3 起。在 v3.3.2 及更早版本中不支持此功能。

    若要从独立的 CDN 源(例如 https://cdn.example.com/assets/)提供 Storybook 的 JS/CSS 产物,请将 output.assetPrefix 设置为 CDN URL:

    import { mergeRsbuildConfig } from '@rsbuild/core'
    
    const config: StorybookConfig = {
      // --snip--
      rsbuildFinal: (config) => {
        return mergeRsbuildConfig(config, {
          output: {
            assetPrefix: 'https://cdn.example.com/',
          },
        })
      },
      // --snip--
    }

    preview 模板能够正确处理绝对 URL,因此 JS/CSS 产物会从 CDN 加载,而 iframe.html 仍可保留在源服务器上。注意,由于产物通过 ES module import 加载,CDN 必须提供合适的 CORS 响应头

    NOTE

    相较于官方 webpack5 builder,这是一项改进 —— 后者因模板始终在 import 路径前加上 ./,并不支持 CDN 资源托管。

    CSS url() 对静态资源的引用

    在 v3.3.2 及更早版本中,从根路径提供服务时 CSS url() 能正常工作,因为默认的 assetPrefix'/'。自 v3.3.3 起,默认值改为 ''(相对路径)以支持子目录,从而引入了这一限制。

    当 CSS 文件通过 url() 引用其他资源时(例如 background: url('./image.png')),生成的路径在生产构建中可能解析错误。这是因为 Rspack 输出的 url() 路径相对于输出根目录,而浏览器却相对于 CSS 文件所在位置进行解析。这是使用相对路径的一项已知权衡,官方 Storybook builder 也存在同样的限制

    如果你的 story 依赖 CSS url() 引用,可以通过 output.assetPrefix 切换为绝对路径:

    import { mergeRsbuildConfig } from '@rsbuild/core'
    
    const config: StorybookConfig = {
      // --snip--
      rsbuildFinal: (config) => {
        return mergeRsbuildConfig(config, {
          output: {
            assetPrefix: '/',
          },
        })
      },
      // --snip--
    }
    NOTE

    设置 assetPrefix: '/' 可以修复从根路径提供服务时的 CSS url() 引用,但会破坏子目录部署。请根据你的部署场景选择合适的方案。

    Template.toString() 在 Docs 源码片段中显示编译后的代码

    当使用 CSF 2 的 Template.bind({}) 模式,并通过 Template.toString() 在 Docs addon 中展示源码时,片段显示的是 bundler 编译后的输出,而非你的原始源码。

    这是符合预期的 —— Function.prototype.toString() 序列化的是运行时函数,而该函数已被 bundler 转换过。这并非 Rsbuild builder 特有的行为;webpack 和 Vite 也是如此。

    Storybook 推荐使用 CSF 3 格式配合自动源码片段功能,或通过 docs source 参数手动提供源码。更多细节请参阅 Storybook Docs 文档

    由意外文件引发的构建错误

    NOTE

    此问题主要影响较旧的 Rspack 版本。新版 Rspack 能正确处理 webpackInclude

    如果你遇到 Rspack 打包了不该打包的文件(例如位于意外位置的 Markdown 文件),可以使用 rspack.IgnorePlugin 将其排除。

    // .storybook/main.js
    import path from 'path'
    import { mergeRsbuildConfig } from '@rsbuild/core'
    
    export default {
      framework: 'storybook-react-rsbuild',
      async rsbuildFinal(config) {
        return mergeRsbuildConfig(config, {
          tools: {
            rspack: (config, { addRules, appendPlugins, rspack, mergeConfig }) => {
              return mergeConfig(config, {
                plugins: [
                  new rspack.IgnorePlugin({
                    checkResource: (resource, context) => {
                      const absPathHasExt = path.extname(resource)
                      if (absPathHasExt === '.md') {
                        return true
                      }
    
                      return false
                    },
                  }),
                ],
              })
            },
          },
        })
      },
    }

    MSW 集成与 lazy compilation

    mockServiceWorker.js 出现在某个 staticDirs 条目中时 —— 这是 msw-storybook-addon 以及任何手动搭建的 MSW 配置所采用的约定 —— builder 会自动禁用 lazy compilation,并在启动时输出一条警告。

    这样做是必要的,因为 MSW 的 Service Worker 会拦截 preview iframe 发出的每一个 fetch,包括 dev-server 的 lazy-compilation RPC(POST /lazy-compilation-using-)。SW 的 passthrough 会重新发起一个全新的 fetch(clonedRequest),而 dev-server 会中止它,这可能导致 story 在冷加载时卡在空白 iframe 上。该问题的根源在于 lazy-compilation 运行时通过有状态的 RPC 通道与 dev-server 通信的方式 —— 并非 builder 本身的缺陷 —— 当手动启用 lazy compilation 时,官方的 @storybook/builder-webpack5 同样受其影响。上游 mswjs/msw#834 讨论了一个相关的失败模式(SW 拦截 dev-server 的 HMR SSE)。

    保留这一自动禁用行为是最稳妥的选择。如果你从未显式设置 lazyCompilation,则无需做任何处理。

    如果你的项目需要在使用 MSW 的同时启用 lazy compilation,请在 builder 选项中显式设置 lazyCompilation —— 显式值始终优先于 MSW 的自动禁用:

    // .storybook/main.ts
    export default {
      framework: {
        options: {
          builder: {
            // Opt back in — expect occasional blank iframes on cold loads.
            lazyCompilation: { entries: false },
          },
        },
      },
    }

    重新启用后,请修补 public/mockServiceWorker.js,让 lazy-compilation RPC 绕过 MSW。在 fetch 事件监听器的顶部、MSW 自身逻辑运行之前,加入这段守卫代码:

    // public/mockServiceWorker.js
    self.addEventListener('fetch', function (event) {
      // Let Rspack / webpack's lazy-compilation RPC bypass MSW so the
      // dev-server sees the original client request, not a re-issued clone.
      if (event.request.url.includes('/lazy-compilation-using-')) {
        return
      }
      // ...existing MSW handler logic
    })

    注意事项:

    • msw init 会重新生成 mockServiceWorker.js 并覆盖该补丁。升级 MSW 后请重新应用它,或通过一个 postinstall 脚本自动化处理 —— 在守卫代码缺失时将其追加进去。
    • 该绕过严格限定在 lazy-compilation 路径上;普通的 story 请求仍会经过 MSW,你的 handler 行为照旧。
    • 这段子串匹配同时覆盖了 Rspack 实现(同源的 POST /lazy-compilation-using-)和 webpack5 实现(专用随机端口上的 GET /lazy-compilation-using-<module-path>),因此即便你日后切换到启用了 lazy compilation 的官方 webpack5 builder,同一段守卫代码也依然有效。

    为什么 sandboxes 使用 getAbsolutePath 来解析 framework?

    请参阅 Storybook 的 FAQ 了解原因。

    如何查看 Storybook 所使用的 Rsbuild 或 Rspack 配置?

    Rsbuild 提供了 CLI 调试模式。在运行 Storybook 时启用它,即可输出所生成配置文件的路径。

    开发环境:

    DEBUG=rsbuild storybook dev

    生产环境:

    DEBUG=rsbuild storybook build

    查看 CLI 输出,即可知道 Rsbuild 和 Rspack 配置被写入到了何处。

    我可以直接将这个 builder 与 Rspack 一起使用吗?

    可以。Rsbuild 构建于 Rspack 之上,因此你可以将自己的 Rspack 配置传入 Rsbuild builder。请参阅 Rspack 集成指南了解具体做法。