Storybook 10.0 插件迁移指南
我们真诚感谢插件创建者为保持 Storybook 生态系统的活力和最新性所付出的奉献和努力。随着 Storybook 升级到 10.0 版本,带来诸多新功能和改进,本指南旨在帮助你将插件从 9.x 版本迁移到 10.0 版本。如果你需要从早期版本的 Storybook 迁移插件,请首先参考 Storybook 9.0 插件迁移指南。
¥We sincerely appreciate the dedication and effort addon creators put into keeping the Storybook ecosystem vibrant and up-to-date. As Storybook evolves to version 10.0, bringing new features and improvements, this guide is here to assist you in migrating your addons from 9.x to 10.0. If you need to migrate your addon from an earlier version of Storybook, please first refer to the Addon migration guide for Storybook 9.0.
我们还提供了一个通用的 Storybook 迁移指南,它涵盖了 Storybook 实例的更新,而不是插件代码的更新。
¥We also have a general Storybook migration guide that covers updates to your Storybook instance rather than your addon code.
依赖更新
¥Dependency updates
你需要更新 Storybook 的依赖。为了确保终端用户的广泛兼容性,对等依赖必须指向 ^10.0.0。开发依赖可以设置为 ^10.0.0,如果你想全年试用最新的预发布版本,则可以设置为 next。
¥You will need to update your Storybook dependencies. Peer dependencies must point to ^10.0.0 to ensure broad compatibility for your end users. Development dependencies can be set to ^10.0.0, or to next if you want to try the latest prerelease all year round.
{
"devDependencies": {
"@storybook/addon-docs": "next",
"@storybook/react-vite": "next",
"storybook": "next"
},
"peerDependencies": {
"storybook": "^10.0.0"
}
}如果你的依赖中仍然包含 @storybook/addon-essentials、@storybook/addon-interactions、@storybook/addon-links 或 @storybook/blocks,则需要将其移除。自 Storybook 9 起,这些包已为空,并且以后将不再发布。
¥If you still have @storybook/addon-essentials, @storybook/addon-interactions, @storybook/addon-links, or @storybook/blocks in your dependencies, you will need to remove them. These packages are empty since Storybook 9 and will no longer be published going forward.
支持早期版本
¥Supporting earlier versions
如果你的插件支持 Storybook 的多个主要版本,则可以在同级依赖中指定更广泛的版本范围:
¥If your addon supports multiple major versions of Storybook, you can specify a wider version range in your peer dependencies:
{
"name": "your-storybook-addon",
"peerDependencies": {
"storybook": "^9.0.0 || ^10.0.0"
},
"devDependencies": {
"storybook": ">=10.0.0-0 <11.0.0-0" // For local development
}
}但是,我们建议你在发布 Storybook 新主要版本的同时,也发布插件的新主要版本。此实践:
¥However, we recommend releasing a new major version of your addon alongside new major versions of Storybook. This practice:
-
使代码维护更容易
¥Makes it easier to maintain your code
-
允许你利用新功能和改进
¥Allows you to take advantage of new features and improvements
-
为你的用户提供更清晰的升级路径
¥Provides a clearer upgrade path for your users
插件的主要变化
¥Key changes for addons
以下是版本 10.0 中影响插件开发的变更。
¥Here are the changes in version 10.0 that impact addon development.
仅 ESM 构建
¥ESM-only builds
Storybook 10 要求所有插件都以 ESM 格式构建。此更改简化了构建过程并降低了维护成本。你需要对 tsup.config.ts 进行多项更改,以便更轻松地在 addon-kit 仓库 中复制引用文件。
¥Storybook 10 requires all addons to be built as ESM-only. This change simplifies the build process and reduces maintenance overhead. You'll need to make many changes to tsup.config.ts, so it can be easier to copy the reference file in the addon-kit repository.
此更新带来了以下更改:
¥This update brings the following changes:
-
Node 目标版本从 Node 20.0 升级到 Node 20.19。
¥The Node target moves from Node 20.0 to Node 20.19
-
你不再需要构建 CJS 文件
¥You no longer need to build CJS files
-
你不再需要传递
globalManagerPackages和globalPreviewPackages参数¥You no longer need to pass
globalManagerPackagesandglobalPreviewPackages -
package.json中的bundler配置不再需要手动输入。¥The
bundlerconfig inpackage.jsonno longer needs to be manually typed -
我们建议你停止使用
exportEntries,并根据用户使用位置,将导出的条目切换到previewEntries和managerEntries。¥We recommend you stop using
exportEntriesand switch exported entries topreviewEntriesandmanagerEntriesinstead based on where they are consumed by your users
- import { readFile } from "node:fs/promises";
import { defineConfig, type Options } from "tsup";
- import { globalPackages as globalManagerPackages } from "storybook/internal/manager/globals";
- import { globalPackages as globalPreviewPackages } from "storybook/internal/preview/globals";
- const NODE_TARGET: Options["target"] = "node20";
+ const NODE_TARGET = "node20.19"; // Minimum Node version supported by Storybook 10
- type BundlerConfig = {
- bundler?: {
- exportEntries?: string[];
- nodeEntries?: string[];
- managerEntries?: string[];
- previewEntries?: string[];
- };
- };
export default defineConfig(async (options) => {
// reading the three types of entries from package.json, which has the following structure:
// {
// ...
// "bundler": {
- // "exportEntries": ["./src/index.ts"],
// "managerEntries": ["./src/manager.ts"],
- // "previewEntries": ["./src/preview.ts"],
+ // "previewEntries": ["./src/preview.ts", "./src/index.ts"],
// "nodeEntries": ["./src/preset.ts"]
// }
// }
- const packageJson = (await readFile("./package.json", "utf8").then(
- JSON.parse,
- )) as BundlerConfig;
+ const packageJson = (
+ await import("./package.json", { with: { type: "json" } })
+ ).default;
+
const {
bundler: {
- exportEntries = [],
managerEntries = [],
previewEntries = [],
nodeEntries = [],
- } = {},
+ },
} = packageJson;
const commonConfig: Options = {
- splitting: false,
+ splitting: true,
+ format: ["esm"],
- minify: !options.watch,
treeshake: true,
- sourcemap: true,
// keep this line commented until https://github.com/egoist/tsup/issues/1270 is resolved
// clean: options.watch ? false : true,
clean: false,
+ // The following packages are provided by Storybook and should always be externalized
+ // Meaning they shouldn't be bundled with the addon, and they shouldn't be regular dependencies either
+ external: ["react", "react-dom", "@storybook/icons"],
};
const configs: Options[] = [];
-
- // export entries are entries meant to be manually imported by the user
- // they are not meant to be loaded by the manager or preview
- // they'll be usable in both node and browser environments, depending on which features and modules they depend on
- if (exportEntries.length) {
- configs.push({
- ...commonConfig,
- entry: exportEntries,
- dts: {
- resolve: true,
- },
- format: ["esm", "cjs"],
- platform: "neutral",
- target: NODE_TARGET,
- external: [...globalManagerPackages, ...globalPreviewPackages],
- });
- }
// manager entries are entries meant to be loaded into the manager UI
// they'll have manager-specific packages externalized and they won't be usable in node
// they won't have types generated for them as they're usually loaded automatically by Storybook
if (managerEntries.length) {
configs.push({
...commonConfig,
entry: managerEntries,
- format: ["esm"],
platform: "browser",
- target: BROWSER_TARGETS,
+ target: "esnext", // we can use esnext for manager entries since Storybook will bundle the addon's manager entries again anyway
- external: globalManagerPackages,
});
}
// preview entries are entries meant to be loaded into the preview iframe
// they'll have preview-specific packages externalized and they won't be usable in node
// they'll have types generated for them so they can be imported when setting up Portable Stories
if (previewEntries.length) {
configs.push({
...commonConfig,
entry: previewEntries,
- dts: {
- resolve: true,
- },
- format: ["esm", "cjs"],
platform: "browser",
- target: BROWSER_TARGETS,
+ target: "esnext", // we can use esnext for preview entries since Storybook will bundle the addon's preview entries again anyway
- external: globalPreviewPackages,
+ dts: true,
});
}
// node entries are entries meant to be used in node-only
// this is useful for presets, which are loaded by Storybook when setting up configurations
// they won't have types generated for them as they're usually loaded automatically by Storybook
if (nodeEntries.length) {
configs.push({
...commonConfig,
entry: nodeEntries,
- format: ["cjs"],
platform: "node",
target: NODE_TARGET,
});接下来,更新 package.json 中的 exports 字段,以删除对 CJS 文件的引用。
¥Next, update the exports field in your package.json to remove mentions of CJS files.
"exports": {
".": {
"types": "./dist/index.d.ts",
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ "default": "./dist/index.js"
},
"./preview": {
- "types": "./dist/index.d.ts",
- "import": "./dist/preview.js",
- "require": "./dist/preview.cjs"
+ "types": "./dist/preview.d.ts",
+ "default": "./dist/preview.js"
},
- "./preset": "./dist/preset.cjs",
+ "./preset": "./dist/preset.js",
"./manager": "./dist/manager.js",
"./package.json": "./package.json"
},更新 tsconfig.json。
¥Update tsconfig.json.
{
"compilerOptions": {
// …
+ "moduleResolution": "bundler",
// …
- "module": "commonjs",
+ "module": "preserve",
// …
- "target": "ES2020",
+ "target": "esnext",
// …
- "lib": ["es2020", "dom", "dom.iterable"],
+ "lib": ["esnext", "dom", "dom.iterable"],
// …
- "rootDir": "./src",
+ "rootDir": ".",
},
- "include": ["src/**/*"]
+ "include": ["src/**/*", "tsup.config.ts"]
}最后,将插件根目录下的 preset.js 文件更改为 ESM 格式。此文件以前是 CJS 格式,因为 Storybook Node 应用仅支持 CJS。
¥Finally, change the preset.js file at the top-level of your addon to be ESM. This file used to be CJS because the Storybook Node app only supported CJS.
-// this file is slightly misleading. It needs to be CJS, and thus in this "type": "module" package it should be named preset.cjs
-// but Storybook won't pick that filename up so we have to name it preset.js instead
-
-module.exports = require('./dist/preset.cjs');
+export * from './dist/preset.js';本地插件加载
¥Local addon loading
由于插件现在仅支持 ESM,你必须更改在开发 Storybook 实例中加载插件的方式。导入和导出现在必须遵循 ESM 语法,相对路径必须使用 import.meta.resolve。
¥Because addons are now ESM-only, you must change how you load your own addon in your development Storybook instance. Imports and exports must now follow ESM syntax, and relative paths must use import.meta.resolve.
移除 .storybook/local-preset.cjs 并创建 .storybook/local-preset.ts,内容如下:
¥Remove .storybook/local-preset.cjs and create .storybook/local-preset.ts with the following content:
import { fileURLToPath } from "node:url";
export function previewAnnotations(entry = []) {
return [...entry, fileURLToPath(import.meta.resolve("../dist/preview.js"))];
}
export function managerEntries(entry = []) {
return [...entry, fileURLToPath(import.meta.resolve("../dist/manager.js"))];
}
export * from "../dist/preset.js";接下来,更新你的 main.ts 以引用新的预设文件:
¥Next, update your main.ts to reference the new preset file:
- addons: ["@storybook/addon-docs", "./local-preset.cjs"],
+ addons: ["@storybook/addon-docs", import.meta.resolve("./local-preset.ts")],可选更改
¥Optional changes
以下更改目前并非强制要求,但我们建议你进行这些更改以改善用户体验。
¥The following changes are not strictly required yet, but we recommend making them to improve your users' experience.
CSF 工厂支持
¥CSF Factories support
要在你的插件中支持 CSF 工厂注解,你需要将 src/index.ts 文件更新为使用新的 definePreviewAddon。此功能将成为 CSF Next 的一部分。强烈建议进行此更改,因为它将帮助你的用户充分利用 CSF 工厂的优势。
¥To support CSF Factories annotations in your addon, you will need to update your src/index.ts file to use the new definePreviewAddon. This feature will be part of CSF Next. This change is highly recommended, as it will help your own users reap the benefits of CSF Factories.
借助 CSF 工厂,用户可以链式调用预览配置,并受益于更佳的类型控制和更高的灵活性。插件必须导出注解才能与此新语法兼容。CSF 工厂将成为 Storybook 11 中编写故事的默认方式。
¥With CSF Factories, users can chain their preview configuration and benefit from better typing and more flexibility. Addons must export annotations to be compatible with this new syntax. CSF Factories will be the default way to write stories in Storybook 11.
- // make it work with --isolatedModules
- export default {};
+ import { definePreviewAddon } from "storybook/internal/csf";
+ import addonAnnotations from "./preview";
+
+ export default () => definePreviewAddon(addonAnnotations);移除 exportEntries
¥Removal of exportEntries
package.json 的 bundler 中的 exportEntries 属性用于从 src/index.ts 生成 index.js 构建输出。它兼容 Node.js,而不仅仅是浏览器。当插件作者将代码导出为 index.js 格式以用于 Storybook 预览或管理器时,此构建配置可能会导致一些不易察觉的错误。
¥The exportEntries property in package.json's bundler was used to produce the index.js build output from src/index.ts. It was compatible with Node.js, rather than strictly with browsers. This build configuration could cause subtle bugs when addon authors exported code in index.js for use in the Storybook preview or manager.
在 Storybook 10 addon-kit 中,我们从 bundler 配置中移除了 exportEntries,并将 src/index.ts 移到了 previewEntries 中。这样,从 src/index.ts 导出的任何代码都会被打包,以便在浏览器中使用,并可与 CSF Next 一起使用。如果你需要导出要在预览中运行的其他代码(例如可选的装饰器),可以将其添加到 src/index.ts 文件中。
¥In the Storybook 10 addon-kit, we removed exportEntries from the bundler config, and we moved src/index.ts to be part of previewEntries instead. This way, any code exported from src/index.ts is bundled for browsers and usable with CSF Next. If you need to export additional code to run in the preview (such as optional decorators), you can add them to src/index.ts.
如果你需要导出管理器的代码(例如用于侧边栏的 renderLabel 函数),可以创建一个新的 src/manager-helpers.ts 文件并将其添加到 managerEntries 文件中,如下所示:
¥If you need to export code for the manager (such as a renderLabel function for the sidebar), you can create a new src/manager-helpers.ts file and add it to managerEntries, like so:
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./preview": {
"types": "./dist/preview.d.ts",
"default": "./dist/preview.js"
},
"./preset": "./dist/preset.js",
"./manager": "./dist/manager.js",
+ "./manager-helpers": "./dist/manager-helpers.js",
"./package.json": "./package.json"
},
"bundler": {
"managerEntries": [
+ "src/manager-helpers.ts",
"src/manager.tsx"
]
}构建文件清理
¥Build file cleanup
我们建议你在构建过程中删除旧的构建文件。这将避免 dist 文件夹因过期的 JS 代码块而不断增长。你可以将以下内容添加到 package.json 脚本中:
¥We recommend removing your old build files as you build. This will avoid your dist folder growing with expired JS chunks. You may add the following to your package.json scripts:
{
"scripts": {
+ "prebuild": "node -e \"fs.rmSync('./dist', { recursive: true, force: true })\"",
}
}包关键字更改
¥Package keyword changes
我们已更新 Storybook addon-kit 模板中插件的默认关键字。
¥We've updated default keywords for addons in the Storybook addon-kit template.
"keywords": [
- "storybook-addons",
+ "storybook-addon",
],10.0.0 完整迁移指南
¥10.0.0 full migration guide
完整的变更列表请访问 Migration.md 文件
¥For a full list of changes, please visit the Migration.md file
迁移示例
¥Migration example
有关已更新以支持 Storybook 10.0 的插件的完整示例,请参阅 插件套件迁移 PR。合并后,它将演示 Storybook 10 的所有必要和推荐更改。
¥For a complete example of an addon updated to support Storybook 10.0, refer to the Addon Kit migration PR. Once merged, it will demonstrate all the necessary and recommended changes for Storybook 10.
发布
¥Releasing
要支持 Storybook 10.0,我们建议你发布插件的新主版本。对于实验性功能或测试,请使用 next 标签。这允许你在发布稳定版本之前收集反馈。
¥To support Storybook 10.0, we encourage you to release a new major version of your addon. For experimental features or testing, use the next tag. This allows you to gather feedback before releasing a stable version.
支持
¥Support
如果你按照本指南操作后仍然遇到插件问题,请在我们的 GitHub 代码库中提交 新讨论 问题,或加入我们的 Discord 通道 专用插件开发者通道,#addons 与我们联系。
¥If you're having issues with your addon after following this guide, please open a new discussion in our GitHub repository or come talk to us in our dedicated addon developer channel, #addons on Discord.
