ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Storybook × Next.js:手动将 React 项目的 framework 切换到 @storybook/nextjs,并看懂其 Preset 实现

Storybook × Next.js:手动将 React 项目的 framework 切换到 @storybook/nextjs,并看懂其 Preset 实现 Storybook × Next.js手动将 React 项目的 framework 切换到 storybook/nextjs并看懂其 Preset 实现本文以 Storybook 官方文档中「手动安装 Next.js framework」的核心配置片段为主体完整讲解如何将一个已有的 React Webpack 项目切换到storybook/nextjs框架从安装依赖、修改.storybook/main.js|ts中的framework字段到清理不再需要的旧 Addon并结合开源仓库源码深入解析这个字段背后触发的 Preset 解析、Builder/Renderer 装配与defineMain类型函数的真实行为。读完本文你可以独立完成框架切换并理解 Storybook 配置项与底层 preset 加载机制之间的对应关系。1. 适用场景为什么需要手动切换 framework当你的项目最初使用storybook/react-webpack5或其他 React 框架 preset初始化了 Storybook但业务本身是 Next.js 应用时官方推荐的做法是把整个框架 preset 切换为storybook/nextjs。这一流程在官方文档 Next.js framework 页面的 FAQ「How do I manually install the Next.js framework?」 中给出了完整步骤其核心配置片段正是 nextjs-add-framework.md 所定义的 diff 内容修改framework属性并同步更换配置类型的 import 来源。整个流程分为三步安装storybook/nextjs开发依赖修改.storybook/main.js|ts将framework从原框架改为storybook/nextjs并更换StorybookConfig类型或defineMain的 import 路径移除此前用于集成 Next.js 的第三方 Addon如storybook-addon-next。以下各节依次展开这三步并在第 5 节深入源码说明framework字段在 Storybook 内部究竟做了什么。2. 第一步安装 storybook/nextjs 包在修改任何配置之前先按你的包管理器安装框架包来自 nextjs-install.md# npm npm install --save-dev storybook/nextjs# pnpm pnpm add --save-dev storybook/nextjs# yarn yarn add --dev storybook/nextjs安装完成后从 package.json 的peerDependencies可以确认适用前提该 preset 要求项目满足next ^14.1.0 || ^15.0.0 || ^16.0.0、react ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0以及webpack ^5.0.0在peerDependenciesMeta中标记为可选因为 webpack 5 由 preset 内部依赖提供。如果你的 Next.js 版本不在上述范围内需要先升级 Next.js 再执行本切换。3. 第二步修改 .storybook/main.js|ts 中的 framework 属性这是核心文档 nextjs-add-framework.md 的主体内容。该片段覆盖了两种配置写法风格CSF 3 传统写法与 CSF Next 实验性写法以及两种文件语言.js/.ts共四种 diff 变体切换时请按你项目实际使用的风格对照执行。3.1 CSF 3 风格.storybook/main.js最小改动只有一个字段export default { // ... - framework: storybook/react-webpack5, framework: storybook/nextjs, };3.2 CSF 3 风格.storybook/main.tsTypeScript 版本除了改framework字段外还必须同步更换配置对象的类型来源——StorybookConfig类型由框架包提供切换框架后 import 路径要从旧框架包改为storybook/nextjs- import type { StorybookConfig } from storybook/your-previous-framework; import type { StorybookConfig } from storybook/nextjs; const config: StorybookConfig { // ... - framework: storybook/react-webpack5, framework: storybook/nextjs, }; export default config;这里your-previous-framework是占位符代表你当前正在使用的框架包名如storybook/react-webpack5。之所以必须改 import是因为StorybookConfig类型中framework字段的合法值与各框架扩展的 options 类型都由框架包自身声明沿用旧包的类型会导致字段校验与新框架的 options见第 5.5 节不匹配。3.3 CSF Next实验性风格defineMain写法如果你的main.ts|js采用实验性的defineMain辅助函数写法改动点从类型 import 变为defineMain的 import 来源- import { defineMain } from storybook/your-previous-framework/node; import { defineMain } from storybook/nextjs/node; export default defineMain({ // ... - framework: storybook/react-webpack5, framework: storybook/nextjs, });同一段配置在.storybook/main.jsJS 版 CSF Next 写法中完全相同- import { defineMain } from storybook/your-previous-framework/node; import { defineMain } from storybook/nextjs/node; export default defineMain({ // ... - framework: storybook/react-webpack5, framework: storybook/nextjs, });关于storybook/nextjs/node子路径导出可以在 src/node/index.ts 中确认其真实实现import type { StorybookConfig } from ../types.ts; export function defineMain(config: StorybookConfig) { return config; } export type { StorybookConfig };可以看到defineMain是一个恒等函数identity function运行时不做任何处理它的全部价值在于静态层面借助storybook/nextjs/node子路径导出的、由该框架 types.ts 定义的StorybookConfig类型让配置对象获得 Next.js 框架专属的字段补全与校验。因此把defineMain的 import 从旧框架包切到storybook/nextjs/node等价于把类型系统整体切换到 Next.js 框架的 schema 上。4. 第三步移除不再需要的 Next.js 集成 Addon切换到storybook/nextjs框架后此前用于把 Next.js 特性「注入」到普通 React Storybook 的第三方 Addon 会被 preset 的原生能力取代应当从addons数组中删除。根据 nextjs-remove-addons.md可以移除的是export default { // ... addons: [ // ... // These can both be removed // storybook-addon-next, // storybook-addon-next-router, ], };即storybook-addon-next与storybook-addon-next-router两个 Addon。TS 配置CSF 3 或 CSF Next 写法中的清理方式相同只是配置外层包的是StorybookConfig对象或defineMain({...})调用。清理后请确认package.json中对应的依赖也一并卸载避免残留依赖拉入与 preset 冲突的 webpack 插件。5. 源码纵深framework: storybook/nextjs在 Storybook 内部触发了什么配置层面改一个字段但运行时 Storybook 会据此把整个构建链换掉。以下基于本仓库code/frameworks/nextjs的源码说明这条链路。5.1 framework 字段即 preset 名称Storybook 的framework字段在内部按 preset 规则解析字符串值storybook/nextjs会加载该包的 preset 入口。对应到本仓库就是 preset.js一行 re-export 到构建产物dist/preset.js其源码为 src/preset.ts。preset 文件按约定导出若干PresetProperty钩子Storybook 在启动时逐个应用。5.2 core 钩子锁定 builder-webpack5 与 React renderersrc/preset.ts 的core钩子是切换后行为变化的核心首先它通过options.presets.apply(framework)回读framework配置并在 webpack 真正启动之前调用configureConfig加载 Next.js 的next.config.js配置。源码注释说明了原因让 Next.js 有机会先行覆写 webpack 内部行为否则storybook/builder-webpack5的文件系统缓存fsCache: true无法正常工作。同时支持从对象形式的framework.options.nextConfigPath中读取自定义的 Next.js 配置路径然后返回固定的构建链builder指向storybook/builder-webpack5可选合并framework.options.builderrenderer指向storybook/react/presetaddons钩子则自动追加storybook/preset-react-webpack。这也解释了为什么 package.json 的 dependencies 中会直接依赖storybook/builder-webpack5、storybook/react与storybook/preset-react-webpack——框架 preset 自身保证了构建链的完整性。5.3 previewAnnotations注入 preview 与 Next.js 运行时兼容层previewAnnotations 钩子会在 preview 编译前自动注入storybook/nextjs/preview注解对应 src/preview.tsx对于 Next.js 16 以下版本还会额外注入storybook/nextjs/config/preview源码中留有 TODO待只支持 Next.js 16 后移除。这两个文件承载了路由 Provider、next/image装饰器、styled-jsx、head 管理等运行时能力全部对用户透明——这正是「切换 framework 后无需再挂第三方 Addon」的原因。5.4 babel 钩子复用项目的 Next.js Babel 配置pretset.ts 的babel钩子 会解析项目现有的 Babel 配置识别其中的next/babelpreset字符串、数组或含file.request的配置项三种形态据此决定如何组装 Storybook 侧的 Babel 处理链内置 src/babel/preset.ts 与若干 Next.js 兼容插件如react-loadable-plugin、optimize-hook-destructuring等。从源码结构看这套机制保证你在 Next.js 项目中启用的 Babel 插件在 Storybook 里也能生效。5.5 framework 的对象形式nextConfigPath与builder从 core 钩子的实现 可以确认framework除了字符串形式外还支持对象形式其中options.nextConfigPath用于指定非默认的next.config.js位置options.builder用于向storybook/builder-webpack5透传 builder 级选项// 对应 preset.ts 中的读取逻辑 nextConfigPath: typeof framework string ? undefined : framework.options.nextConfigPath, // builder.options 合并自 ...(typeof framework string ? {} : framework.options.builder || {}),这两个选项的完整类型定义位于 src/types.ts即storybook/nextjs包导出的StorybookConfig所依赖的类型源。6. storybook/nextjs 提供的能力边界切换 framework 后获得哪些能力、边界在哪可以直接从 package.json 的exports映射与 src 目录结构 相互印证。exports暴露的用户可用子路径包括子路径对应源码用途storybook/nextjs/previewsrc/preview.tsxpreview 运行时注入入口storybook/nextjs/nodesrc/node/index.tsdefineMain与StorybookConfig类型storybook/nextjs/export-mocks及headers.mock、navigation.mock、router.mock、link.mock等src/export-mocks/在 Storybook 中 mock Next.js 的headers/cookies/useRouter/usePathname等 APIstorybook/nextjs/images/next-image与images/next-legacy-imagesrc/images/以 Next.js 方式渲染next/imagestorybook/nextjs/rsc/server-onlysrc/rsc/server-only.ts实验性 React Server Components 支持所需的桩模块storybook/nextjs/storybook-nextjs-font-loadersrc/font/webpack/loader/next/font的字体加载 Webpack loader从src目录结构看preset 还内置了routing/App Router 与 Pages Router 两种 Provider 的装饰器、styledJsx/styled-jsx 编译支持、swc/Next.js SWC loader 补丁、nodePolyfills/Node 模块 polyfill由依赖node-polyfill-webpack-plugin驱动、aliases/与imports/基于tsconfig-paths-webpack-plugin复用项目的tsconfig.json路径别名等。依赖列表中的styled-jsx、probe-image-size、react-refresh/pmmmwh/react-refresh-webpack-plugin等也都与这些能力一一对应。结合官方 FAQnextjs.mdx还有两个切换后必须知道的行为变化与限制图片导入语义变化启用该框架后静态图片 import 返回的是{ src, height, width, blurDataURL }对象Next.js 方式而不再是裸路径字符串故事中处理图片的地方需要相应调整Yarn v2/v3 用户注意由于 Yarn 2/3 的包解析规则不同可能出现Cant resolve css-loader/style-loader报错此时需要把这两个 loader 直接安装为项目依赖数据获取型页面app目录中直接 fetch 数据的页面组件引入 Node 专用模块会导致 Webpack 构建崩溃官方建议把纯组件拆到单独文件供故事使用或在webpackFinal中 polyfill 相关模块。7. 小结与验证路径手动切换storybook/nextjs框架的完整动作只有三处安装依赖、把framework改为storybook/nextjs并同步更换StorybookConfig/defineMain的 import 来源、移除storybook-addon-next(-router)类旧 Addonframework字段在运行时等价于「加载该包的 preset」storybook/nextjs的 presetsrc/preset.ts负责把 builder 锁定为storybook/builder-webpack5、renderer 锁定为 React preset并在启动前加载next.config.jsdefineMain是纯类型层面的恒等函数其意义在于把配置对象绑定到storybook/nextjs/node导出的框架类型上切换后 Next.js 的图片、字体、路由、headers/routermock 等能力由 preset 原生提供能力清单与边界可从 package.json 的exports/peerDependencies精确核对当前仓库版本为10.6.0-beta.1适用 Next.js 14.1/15/16。关键参考文件核心配置片段、安装命令片段、旧 Addon 清理片段、Next.js framework 官方文档页、preset 入口、preset 源码、node 子路径导出、包清单。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表