
Storybook 按需架构
构建的 Storybook 体积缩小 3 倍,加载时间更快

随着 stories 数量的增长,以高性能的方式加载所有 stories 变得越来越棘手。这最终会拖累开发者体验。我们使用 Storybook 构建 Storybook,因此我们也感同身受。
在最近的版本中,性能已成为首要任务。最新版本在构建时间和包大小方面包含了渐进但明显的改进。
我很高兴分享 Storybook 全新的按需架构,这是 6.4 版本的一项根本性变革,它改进了已构建 Storybook 的性能。我们与 Webpack 和 Shopify UX 工程团队合作,将包大小减少了多达三倍。请继续阅读以了解其原理。
幕后原理
在我们开始之前,让我们回顾一下幕后发生了什么。Storybook 是组件示例(称为 stories)的集合。这些是小的 Javascript 代码片段,用于隔离渲染 UI 组件(通常是设计系统或应用程序的一部分)。
Stories 在 CSF 文件(Component Story Format,组件故事格式)中定义。与组件相关的所有 stories 都分组在同一个文件中。Storybook 的工作是获取在 CSF 文件中定义的 story,并在浏览器中根据用户请求进行渲染。
为了渲染这些示例,我们需要在浏览器中加载所有相关的代码。为此,我们使用 Webpack 创建一个 JavaScript 包,其中包含:所有 CSF 文件、您的组件以及渲染它们所需的资源,以及 Storybook 的运行时。
由于包大小对性能有巨大影响,因此我们将工作重点放在缩小包大小上。

如何将 Storybook 的包大小减半
当涉及到性能时,更小 = 更快。我们 Storybook 的包越小,加载速度就越快。考虑到这一点,我们进行了两项架构更改,以加快您的开发者体验
- 代码拆分:为生产环境 Storybook 启用更快的加载时间
- 智能文件系统缓存:启用更快的开发启动速度
在以前版本的 Storybook 中,所有代码都被打包成一个大包。更多的组件和 stories 导致包变得更大,从而减慢了 Storybook 的速度。启动(尤其是通过网络加载时)或启动开发服务器需要一段时间。
近年来,更大的应用程序开始依赖包拆分。其思想是将大包拆分成更小、更易于管理的部分。此外,像 NextJS 这样的工具率先使用了懒加载编译技术。在启动时构建整个应用程序需要时间。相反,它们只构建用户专注于特定任务所需的特定模块。
包拆分的关键是仅加载首次渲染所需的代码。其他所有内容都会在需要时异步获取(通过 import()
构造)。
应用程序通过手动指定和等待 import()
或通过在页面路由上自动拆分(如 NextJS 所做的那样)来实现这一点。第一种选择更手动,让您可以更好地控制体验。第二种选择可以在框架级别进行优化,但通常会限制您可以执行的操作。
对于 Storybook,我们与 Webpack 团队合作探索了这两种方法。
无效的方法:手动 import()
函数
自 Storybook 6.1 以来,可以使用 import()
函数来代码拆分您的 Storybook——使用实验性功能: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 文件中,没有直接静态导入 ./MyComponent
;只是在提供的 loader 内部有一个异步 import()
。
通过这种设置,所有 CSF 文件都会创建一个单独的(初始)包。而每个组件文件都将形成自己的包,以及其依赖项。
在对此方法进行原型设计时,我们发现了两个主要缺点
- 要求用户为每个组件编写 loader 是笨拙的、不直观的,并且使 stories 更难重用。代码拆分似乎是一个优化细节,您作为 Storybook 的用户,不应该需要关心。
- 在实验中,我们经常发现包含所有 CSF 文件的初始包占 Storybook 总大小的很大一部分,从而降低了代码拆分的好处。
初始包之所以很大,通常是因为很难保持 CSF “纯粹”,不受其他组件依赖项的影响。此外,在 Storybook 嵌入或组合到其他上下文中的情况下,最小化初始包大小尤其重要。
有效的方法:自动代码拆分
另一种方法是让 Storybook 的 store 将每个 CSF 文件视为单独的异步 import()
,并“按需”加载 stories
通过这种方式,每个 CSF 都会生成自己的包——组件加上加载和渲染 story 所需的最小依赖项。无需更改代码。这一切都在幕后发生,无需用户干预。
这种方法更复杂,并且在使用位置和时间方面存在一些注意事项(见下文)。但通常适用于即使是最复杂的 Storybook,只需进行最少的更改。
此行为将成为 Storybook 7.0 中的默认行为——它在 6.4 版本中通过 storyStoreV7
功能标志提供(见下文);之前的单包行为在 6.4 安装中仍然默认启用。
6.4 版本中的性能提升
引入代码拆分的主要目的是提高 Storybook 的性能。也就是说,安装和启动所需的时间,以及下载已构建 Storybook 并与之交互所需的时间。
6.4 版本中的更改侧重于在 Storybook 中启用代码拆分。直接影响将是更小的包大小,这意味着已构建的 Storybook 应该加载得更快。
例如,Chromatic Storybook(一个包含 2000 个 stories 的大型 Storybook)在升级到 v7 store 时显示了以下行为

同样,Shopify 的 Storybook 在启用 v7 store 后,初始包大小节省了 67.5%。

6.5+ 版本中性能方面的下一步是什么?
仅代码拆分不一定会改善使用 Storybook 的开发者体验。生成多个代码拆分包甚至可能比创建一个大包花费更长的时间。这取决于跨包的代码重复以及优化内容的复杂性。
然而,它解锁了进一步的优化。一个关键的优化是使用懒加载编译,仅生成渲染当前屏幕上可见 stories 所需的包。懒加载编译是 Webpack 5 的一项实验性功能,概念上类似于 NextJS 的即时页面构建。
懒加载编译和文件系统缓存的实验表明,在大型项目中,应该可以将开发启动时间和重建时间减少 3-5 倍。这将是 Storybook 6.5 的主要关注点。
此外,Webpack 拆分机制的其他优化现在也已解锁。我们鼓励用户尝试调整 Storybook 的默认 Webpack 设置,并将改进贡献回 6.5 版本。
注意事项
自动代码拆分方法棘手的部分在于,我们不再在“启动”时加载所有 CSF 文件。相反,我们需要从节点上下文中静态计算 Storybook 的 stories 列表(“Story Index”)。这意味着我们不评估您的 story 文件,而只是解析它们并分析生成的 AST。这限制了您可以在 CSF 文件中执行的操作(其中一些限制我们可能会在未来的迭代中删除)
- 仅支持 CSF 格式 (v1-v3);不支持
storiesOf()
。 - CSF 标题和 story 名称必须是静态定义的(即
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 构建数百个组件和数千个 stories。您进行了哪些调整来加速您的 Storybook?我们很乐意听取您的意见。请在 Twitter 上联系我们,或访问 Storybook Discord。
按需架构功能由 Tom Coleman(我!)、Juho Vepsäläinen 和 Michael Shilman 开发,并得到了整个 Storybook 社区的反馈。
Storybook 是 1320 多名社区贡献者的成果,并由顶级维护者的指导委员会组织。您也可以贡献新功能、修复错误或改进文档。加入我们的 Discord,在 Open Collective 上支持我们,或者直接参与 GitHub。
体积缩小 3 倍,速度提升 4 倍!
— Storybook (@storybookjs) 2021 年 10 月 13 日
Storybook 6.4 为静态构建的 Storybook 带来了巨大的性能提升。
通过自动代码拆分,它只加载第一个 story 所需的代码。
其影响——更小的包大小和更快的加载时间。https://#/K5F5D1ZhW8 pic.twitter.com/37Kw6z7T97