返回博客

Storybook 按需架构

为已构建的 Storybook 带来 3 倍更小的构建体积 & 更快的加载时间

loading
Tom Coleman
@tmeasday
最后更新

随着故事数量的增长,以高性能方式加载所有故事变得更加棘手。这最终会拖慢开发者体验。我们使用 Storybook 构建 Storybook,因此我们也感同身受。

在最近的版本中,性能已成为重中之重。最新版本在构建时间和捆绑包大小方面包含渐进但显著的改进。

我很高兴分享 Storybook 全新的 按需架构,这是 6.4 版本即将推出的根本性改变,可以提高已构建 Storybook 的性能。我们与 Webpack 和 Shopify UX 工程团队合作,将捆绑包大小减少了多达三倍。继续阅读以了解详情。

内部原理

在开始之前,让我们回顾一下内部原理。Storybook 是组件示例的集合,这些示例被称为故事。它们是 JavaScript 代码的小片段,用于独立地渲染一个 UI 组件(通常是设计系统或应用程序的一部分)。

故事在 CSF 文件(Component Story Format)中定义。与某个组件相关的所有故事都分组在同一个文件中。Storybook 的工作是获取 CSF 文件中定义的故事,并根据用户的请求在浏览器中渲染它。

为了渲染这些示例,我们需要在浏览器中加载所有相关代码。为此,我们使用 Webpack 创建一个 JavaScript 捆绑包,其中包含:所有 CSF 文件、您的组件以及渲染它们所需的资源。加上 Storybook 的运行时。

由于捆绑包大小对性能有巨大影响,我们将精力集中在减小它上。

如何将 Storybook 的捆绑包大小减半

谈到性能,更小 = 更快。Storybook 捆绑包越小,为您加载的速度就越快。考虑到这一点,我们进行了两项架构更改以加速您的开发者体验

  • 代码分割:为生产环境的 Storybook 带来更快的加载时间
  • 智能文件系统缓存:实现更快的开发环境启动

在 Storybook 的早期版本中,所有代码都被打包成一个大的捆绑包。更多的组件和故事会导致捆绑包变重,从而拖慢 Storybook。启动(尤其是在网络加载时)或启动开发服务器需要一些时间。

近年来,大型应用程序开始依赖于捆绑包分割。其思想是将那个大的捆绑包分割成更小、更易管理的块。此外,像 NextJS 这样的工具开创了延迟编译技术。在启动时构建整个应用程序需要时间。相反,它们只构建用户专注于特定任务所需的特定模块。

捆绑包分割的关键是只加载首次渲染所需的代码。所有其他内容都会在需要时异步获取(通过 import() 结构)。

应用程序通过手动指定和等待 import() 或通过像 NextJS 那样在页面路由上自动分割来实现这一点。第一种选项更手动,为您提供了更多控制权。第二种选项可以在框架层面进行优化,但通常会限制您可以执行的操作。

对于 Storybook,我们与 Webpack 团队合作探索了这两种方法。

行不通的方法:手动 import() 函数

自 Storybook 6.1 起,就可以使用 import() 函数进行代码分割,通过实验性功能 loaders 实现。

// A CSF file that establishes a import "boundary" to the component file
 
export default {
 title: "MyComponent",
 loaders: [async () => ({ Component: await import('./MyComponent') })],
 // In CSFv3 you could define this render() function for all components
 render: (args, { loaded: { Component } }) => <Component {...args} />,
};
 
export const MyStory = {
 args: { arg1: 'value' }
};

在上面的 CSF 文件中,没有直接的、静态的 import ./MyComponent ; 只是在提供的加载器内部有一个异步的 import()

通过这种设置,所有 CSF 文件会创建一个单一(初始)的捆绑包。而每个组件文件将形成自己的捆绑包及其依赖项。

在原型设计这种方法时,我们发现了两个主要缺点

  1. 要求用户为每个组件编写加载器既笨重又不直观,并且使得故事更难重用。代码分割似乎是 Storybook 用户无需关心的优化细节。
  2. 在实验中,我们经常发现包含所有 CSF 文件的初始捆绑包占 Storybook 总大小的很大一部分,从而降低了代码分割的好处。

这个大的初始捆绑包通常是因为很难保持 CSF 不包含其他组件依赖项。此外,在 Storybook 被嵌入或组合到其他上下文中的情况下,最小化初始捆绑包大小尤其重要。

有效的方法:自动代码分割

另一种方法是让 Storybook 的 store 将每个 CSF 文件视为单独的异步 import(),并“按需”加载故事。

通过这种方式,每个 CSF 生成自己的捆绑包——组件加上加载和渲染故事所需的最少依赖项。无需进行代码更改。这一切都在幕后发生,无需用户干预。

这种方法更复杂,并在使用地点和时间上有一些注意事项(见下文)。但通常适用于最复杂的 Storybook,只需最少的更改。

此行为将在 Storybook 7.0 中成为默认设置——在 6.4 中通过 storyStoreV7 功能标志可用(见下文);之前的单一捆绑包行为在 6.4 安装中仍默认启用。

6.4 的性能提升

引入代码分割的主要目的是提高 Storybook 的性能。也就是说,安装和启动所需的时间以及下载和与已构建 Storybook 交互所需的时间。

6.4 中的更改重点在于启用 Storybook 中的代码分割。直接影响将是捆绑包大小减小,这意味着已构建的 Storybook 应该加载得更快。

例如,Chromatic Storybook 是一个包含 2000 个故事的较大 Storybook,升级到 v7 store 后显示出以下行为

类似地,Shopify 的 Storybook 在启用 v7 store 后,初始捆绑包大小节省了 67.5%。

6.5+ 版本的性能展望

仅靠代码分割不一定能改善使用 Storybook 的开发者体验。生成多个代码分割的捆绑包甚至可能比创建一个大捆绑包耗时更长。这取决于捆绑包之间的代码重复以及优化内容的复杂性。

然而,它解锁了进一步的优化。一个关键的优化是使用延迟编译,只生成渲染当前屏幕上可见故事所需的捆绑包。延迟编译是 Webpack 5 的一个实验性功能,概念上类似于 NextJS 的即时页面构建。

延迟编译和文件系统缓存的实验表明,在大型项目中,应该可以将开发启动时间和重建时间缩短 3-5 倍。这将是 Storybook 6.5 的一个主要重点。

此外,Webpack 分割机制的其他优化现已解锁。我们鼓励用户尝试调整 Storybook 的默认 Webpack 设置,并将改进贡献回 6.5 版本。

注意事项

自动代码分割方法的棘手之处在于我们不再在“启动”时加载所有 CSF 文件。相反,我们需要在 Node 上下文中静态地计算 Storybook 的故事列表(即“故事索引”)。这意味着我们不会评估您的故事文件,而只是解析它们并分析生成的 AST。这会对您在 CSF 文件中可以执行的操作产生一些限制(其中一些限制我们可能会在未来的迭代中移除)。

  • 仅支持 CSF 格式(v1-v3);不支持 storiesOf()
  • CSF 的标题和故事名称必须静态定义(例如 title: 'Component',而不是 title: MyTitle)。
  • 自定义 storySort 函数提供的 API 功能更有限。

立即试用

自动代码分割现已在 6.4 beta 版本中可用。只需一分钟即可试用,您可以在项目根目录下运行以下命令

npx sb upgrade --prerelease

如果您还没有使用 Storybook,入门非常简单

npx sb@next init

然后启用功能标志

// .storybook/main.js
module.exports = {
  features: {
    storyStoreV7: true,
  }
};

助力塑造下一代 Storybook!

Storybook 按需架构带来了显著的性能优势,并允许 尝试其他 Webpack 优化。

开发者每天使用 Storybook 构建数百个组件和数千个故事。您为了加速您的 Storybook 做过哪些调整?我们很想听听您的想法。请在 Twitter 上与我们联系,或访问 Storybook Discord

按需架构功能由 Tom Coleman(我!)、Juho VepsäläinenMichael Shilman 开发,并获得了整个 Storybook 社区的反馈。

Storybook 是 1320 多名社区提交者的成果,由顶尖维护者组成的指导委员会组织。您也可以贡献新功能、修复 bug 或改进文档。加入我们的  Discord,在 Open Collective 上支持我们,或直接在 GitHub 上参与。

加入 Storybook 邮件列表

获取最新新闻、更新和版本

7,180开发者及仍在增长

我们正在招聘!

加入 Storybook 和 Chromatic 背后的团队。构建数十万开发者在生产环境中使用的工具。远程优先。

查看职位

热门文章

交互式故事 (beta)

使用 play 函数模拟用户行为
loading
Varun Vachhar

Storybook 和 Next.js 入门

通过四个简单步骤将 Storybook 与 Next.js 集成
loading
Michael Chan

UI 测试手册

一个不会拖慢您的测试工作流程
loading
Varun Vachhar
加入社区
7,180开发者及仍在增长
原因为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与其中博客
案例展示探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI