加入直播:美国东部时间周四上午 11 点,Storybook 9 发布及 AMA(问答环节)

Next.js + Storybook

一个无需配置的 Storybook 插件,让 Next.js 功能在 Storybook 中直接可用

在 Github 查看

Storybook Addon Next

⚠️ 已废弃 ⚠️

此插件已废弃,请使用 @storybook/nextjs。后者是 Storybook 官方插件,用于在 Storybook 中支持 Next.js 功能。它支持 storybook-addon-next 的所有功能,甚至更多!我甚至与他们一起开发了这个官方插件,所以你可以放心使用。

请参阅迁移文档了解如何迁移的详细信息。


😱 对 Next.js 的零配置支持: 厌倦了编写和调试 webpack 配置?Next.js 开箱即用的功能,本插件让它们在 Storybook 中成为可能。

current version Commitizen friendly semantic-release semantic-release: angular

目录

支持的功能

👉 Next.js Image 组件(部分支持)

👉 Next.js 路由

👉 Sass/Scss

👉 Css/Sass/Scss 模块

👉 Styled JSX

👉 Postcss

👉 绝对导入

👉 Runtime 配置

👉 自定义 Webpack 配置

👉 Typescript

要求

  • Next.js >= 9.x
  • Storybook >= 6.x
    • Storybook webpack 5 构建器
      • 介绍
      • 安装指南
      • 并非此插件不支持 webpack 4 构建器,只是需求不多,而且 Storybook 推荐用于 Next.js 应用的是 webpack 5。如果您认为您有充分的用例,欢迎随时提出问题
  • 您的 Next.js 配置文件使用 .js 扩展名而非 .mjs 扩展名(即 next.config.js 而非 next.config.mjs

示例

要运行任何示例,请首先在仓库根目录下运行 yarn build 来构建插件。

开始

安装

使用 yarn 安装 storybook-addon-next

yarn add --dev storybook-addon-next

npm

npm install --save-dev storybook-addon-next

在 main.js 中注册插件

// .storybook/main.js

module.exports = {
  // other config ommited for brevity
  addons: [
    // ...
    'storybook-addon-next'
    // ...
  ]
}

搞定!

🥳🎉 就这样!支持的功能应该开箱即用。

更多关于本插件如何支持各项功能的详细信息,请参见文档

如果任何功能不按预期工作,欢迎随时提出问题

文档

选项

如有需要,可以将一个选项对象传递给本插件以进行额外配置。

例如

// .storybook/main.js
const path = require('path')

module.exports = {
  // other config ommited for brevity
  addons: [
    // ...
    {
      name: 'storybook-addon-next',
      options: {
        nextConfigPath: path.resolve(__dirname, '../next.config.js')
      }
    }
    // ...
  ]
}
  • nextConfigPath: next.config.js 的绝对路径

Next.js Image 组件(部分支持)

next/image 众所周知很难与 Storybook 协同工作。本插件允许您使用 Next.js 的 Image 组件,无需任何配置!

由于 Image 组件的功能(例如图片优化)是通过需要 Next.js 配置文件由框架读取和处理的选项来配置的,并且 Next.js 没有公开用于解析这些选项的公共函数,因此无法稳定支持这些功能。

如果您希望看到更好的支持,欢迎为 Next.js 方面的讨论我们这边的讨论贡献力量。

本地图片

本地图片通过本插件可以正常工作!请注意,此功能仅在 Next.js v11 中添加

import Image from 'next/image'
import profilePic from '../public/me.png'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
        // width={500} automatically provided
        // height={500} automatically provided
        // blurDataURL="../public/me.png" set to equal the image itself (for this addon)
        // placeholder="blur" // Optional blur-up while loading
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

远程图片

远程图片也完全正常工作!

import Image from 'next/image'

export default function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src="/me.png"
        alt="Picture of the author"
        width={500}
        height={500}
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

优化

所有 Next.js Image 都会自动取消优化

如果使用 placeholder="blur",使用的 blurDataURL 是图片的 src(因此实际上禁用了占位符)。

有关 Next.js Image 在 Storybook 中的处理方式的更多讨论,请参见此问题

AVIF

本插件尚不支持此格式。如果您希望看到此功能,欢迎随时提出问题

Next.js 路由

此解决方案很大程度上基于 storybook-addon-next-router,因此非常感谢 lifeiscontent 提供了本插件可以借鉴的优秀解决方案。

Next.js 的路由会自动进行存根处理,以便当路由被交互时,所有交互都会自动记录到 Storybook actions 选项卡中(如果您安装了 actions 插件)。

覆盖默认设置

每个故事的覆盖可以通过在故事参数上添加 nextRouter 属性来完成。插件会将您在此处放置的内容浅层合并到路由中。

import SomeComponentThatUsesTheRouter from "./SomeComponentThatUsesTheRouter";

export default {
  title: "My Story",
};

// if you have the actions addon
// you can click the links and see the route change events there
export const Example = () => <SomeComponentThatUsesTheRouter />;

Example.parameters: {
  nextRouter: {
    path: "/profile/[id]",
    asPath: "/profile/ryanclementshax",
    query: {
      id: "ryanclementshax"
    }
  }
}

请参阅此示例作为参考。

全局默认设置

全局默认设置可以在preview.js中设置,并会与默认路由进行浅层合并。

export const parameters = {
  nextRouter: {
    path: '/some-default-path',
    asPath: '/some-default-path',
    query: {}
  }
}

请参阅此示例作为参考。

默认路由

存根路由的默认值如下(有关全局变量如何工作的详细信息,请参见全局变量

const defaultRouter = {
  locale: context?.globals?.locale,
  route: '/',
  pathname: '/',
  query: {},
  asPath: '/',
  push(...args: unknown[]) {
    action('nextRouter.push')(...args)
    return Promise.resolve(true)
  },
  replace(...args: unknown[]) {
    action('nextRouter.replace')(...args)
    return Promise.resolve(true)
  },
  reload(...args: unknown[]) {
    action('nextRouter.reload')(...args)
  },
  back(...args: unknown[]) {
    action('nextRouter.back')(...args)
  },
  prefetch(...args: unknown[]) {
    action('nextRouter.prefetch')(...args)
    return Promise.resolve()
  },
  beforePopState(...args: unknown[]) {
    action('nextRouter.beforePopState')(...args)
  },
  events: {
    on(...args: unknown[]) {
      action('nextRouter.events.on')(...args)
    },
    off(...args: unknown[]) {
      action('nextRouter.events.off')(...args)
    },
    emit(...args: unknown[]) {
      action('nextRouter.events.emit')(...args)
    }
  },
  isFallback: false
}

Actions 集成注意事项

如果您覆盖某个函数,您将失去自动的 action 选项卡集成,需要自己构建。

export const parameters = {
  nextRouter: {
    push() {
      // we lose the default implementation that logs the action into the action tab
    }
  }
}

自己实现的样子如下(请确保您已安装 @storybook/addon-actions 包)

import { action } from '@storybook/addon-actions'

export const parameters = {
  nextRouter: {
    push(...args) {
      // custom logic can go here
      // this logs to the actions tab
      action('nextRouter.push')(...args)
      // return whatever you want here
      return Promise.resolve(true)
    }
  }
}

Sass/Scss

全局 sass/scss 样式表也无需额外配置即可支持。只需将它们导入到preview.js中即可。

import '../styles/globals.scss'

这将自动包含您在 next.config.js 文件中的任何自定义 sass 配置

目前仅支持 Next.js 配置的 .js 扩展名,不支持 .mjs。更多详细信息请参见next.config.js。目前,您需要使用 .js 扩展名。欢迎在此讨论中提供帮助以获得支持。

const path = require('path')

module.exports = {
  // any options here are included in sass compilation for your stories
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')]
  }
}

Css/Sass/Scss 模块

Next.js 开箱即用地支持css 模块,因此本插件也支持。

// this import works just fine in Storybook now
import styles from './Button.module.css'
// sass/scss is also supported
// import styles from './Button.module.scss'
// import styles from './Button.module.sass'

export function Button() {
  return (
    <button type="button" className={styles.error}>
      Destroy
    </button>
  )
}

Styled JSX

Next.js 内置的 CSS-in-JS 解决方案是styled-jsx,本插件也开箱即用地支持它,零配置。

// This works just fine in Storybook with this addon
function HelloWorld() {
  return (
    <div>
      Hello world
      <p>scoped!</p>
      <style jsx>{`
        p {
          color: blue;
        }
        div {
          background: red;
        }
        @media (max-width: 600px) {
          div {
            background: blue;
          }
        }
      `}</style>
      <style global jsx>{`
        body {
          background: black;
        }
      `}</style>
    </div>
  )
}

export default HelloWorld

您也可以使用自己的 babel 配置。这是一个如何自定义 styled-jsx 的示例。

// .babelrc or whatever config file you use
{
  "presets": [
    [
      "next/babel",
      {
        "styled-jsx": {
          "plugins": ["@styled-jsx/plugin-sass"]
        }
      }
    ]
  ]
}

如果您使用 monorepo,您可能需要将 babel 配置手动添加到您的 Storybook 项目中。只需在您的 Storybook 项目中添加一个 babel 配置,并包含以下内容即可开始。

{
  "presets": ["next/babel"]
}

Postcss

Next.js 允许您自定义 postcss 配置。因此,本插件将自动处理您的 postcss 配置。

这使得诸如零配置 tailwindcss 等酷炫功能成为可能!请参阅带 tailwindcss 的示例作为参考!它是Next.js 的 tailwindcss 示例的克隆,但使用 Storybook 和本插件进行了设置。

绝对导入

再见 ../!绝对导入从根目录开始,在本插件中可以正常工作。

// All good!
import Button from 'components/button'
// Also good!
import styles from 'styles/HomePage.module.css'

export default function HomePage() {
  return (
    <>
      <h1 className={styles.title}>Hello World</h1>
      <Button />
    </>
  )
}
// preview.js

// Also ok in preview.js!
import 'styles/globals.scss'

// ...

Runtime 配置

Next.js 支持Runtime Configuration,它允许您导入一个方便的 getConfig 函数,以便在运行时获取在 next.config.js 文件中定义的特定配置。

在使用本插件的 Storybook 环境中,您可以期望 Next.js 的Runtime Configuration功能正常工作。

注意,由于 Storybook 不会服务器渲染您的组件,您的组件将只能看到它们在客户端通常看到的内容(即它们看不到 serverRuntimeConfig,但会看到 publicRuntimeConfig)。

例如,考虑以下 Next.js 配置

// next.config.js
module.exports = {
  serverRuntimeConfig: {
    mySecret: 'secret',
    secondSecret: process.env.SECOND_SECRET // Pass through env variables
  },
  publicRuntimeConfig: {
    staticFolder: '/static'
  }
}

在 Storybook 中调用 getConfig 将返回以下对象

{
  "serverRuntimeConfig": {},
  "publicRuntimeConfig": {
    "staticFolder": "/static"
  }
}

自定义 Webpack 配置

Next.js 开箱即用提供许多功能,例如 sass 支持,但有时我们会向 Next.js 添加自定义 webpack 配置修改。本插件处理您想添加的大多数 webpack 修改。如果 Next.js 开箱即用支持某个功能,那么本插件会使该功能在 Storybook 中开箱即用。如果 Next.js 不开箱即用支持某些功能,但提供了易于配置的方法,那么本插件也会为 Storybook 提供相同的功能。如果您发现添加此插件后,仍需要配置 webpack 才能使 Next.js 功能在 Storybook 中工作,这很可能是一个 bug,请随时提出问题

Storybook 需要的任何 webpack 修改都应根据 Storybook 的文档在.storybook/main.js中进行。

注意:并非所有 webpack 修改都可以在 next.config.js.storybook/main.js 之间复制粘贴。建议您研究如何正确地对 Storybook 的 webpack 配置进行修改,以及webpack 如何工作

欢迎随时贡献一个示例,以帮助社区。

下面是如何使用本插件为 Storybook 添加 svgr 支持的示例。完整示例可以在此处找到。

// .storybook/main.js
module.exports = {
  // other config omitted for brevity
  webpackFinal: async config => {
    // this modifies the existing image rule to exclude .svg files
    // since we want to handle those files with @svgr/webpack
    const imageRule = config.module.rules.find(rule => rule.test.test('.svg'))
    imageRule.exclude = /\.svg$/

    // configure .svg files to be loaded with @svgr/webpack
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack']
    })

    return config
  }
}

Typescript

Storybook 处理大多数Typescript配置,但本插件为 Next.js 对Absolute Imports 和 Module path aliases的支持添加了额外支持。简而言之,它考虑到了您 tsconfig.json 中的baseUrlpaths。因此,像下面这样的 tsconfig.json 将开箱即用。

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/components/*": ["components/*"]
    }
  }
}

next.config.js

ESM

目前,本插件支持的 Next.js 配置格式只有 commonjs 版本(即 next.config.js)。这主要是因为我还没有弄清楚如何从 Storybook 插件(据我所知,它绑定到 commonjs 模块)中 require 一个 .mjs 文件。如果您能提供帮助,如果您能为此讨论做出贡献,以获得对 .mjs 版本的支持(如果这种支持是可能的),我将不胜感激。

致 Yarn v2 和 v3 用户的注意事项

如果您使用的是 Yarn v2 或 v3,您可能会遇到 Storybook 无法解析 style-loadercss-loader 的问题。例如,您可能会收到以下错误:

模块未找到: Error: Can't resolve 'css-loader'
模块未找到: Error: Can't resolve 'style-loader'

这是因为这些版本的 Yarn 与 Yarn v1.x 具有不同的包解析规则。如果出现这种情况,只需直接安装该包即可。

常见问题解答

静态导入的图片无法加载

确保您在常规开发中使用 next image 时,处理图片导入的方式与在此处相同。

在使用 storybook-addon-next 之前,图片导入只会导入图片的原始路径(例如 'static/media/stories/assets/plugin.svg')。使用 storybook-addon-next 后,图片导入按照“Next.js 方式”工作,这意味着我们现在导入图片时会得到一个对象。例如

{
  "src": "static/media/stories/assets/plugin.svg",
  "height": 48,
  "width": 48,
  "blurDataURL": "static/media/stories/assets/plugin.svg"
}

因此,如果在 Storybook 中某些内容没有正确显示图片,请确保您期望导入返回的是一个对象,而不仅仅是资产路径。

有关 Next.js 如何处理静态图片导入的更多详细信息,请参见本地图片

当 Next config 使用 .mjs 扩展名时,此插件会崩溃

目前,本插件不支持使用 next.config.mjs。更多详细信息请参见next.config.js。目前,您必须使用 .js 扩展名。欢迎在此讨论中提供帮助以获得此支持。

模块未找到: Error: Can't resolve [package name]

如果您使用 Yarn v2 或 v3,可能会出现此问题。更多详细信息请参见致 Yarn v2 和 v3 用户的注意事项

类似项目

想建议更多功能吗?

我愿意讨论。欢迎随时提出问题

没有找到您要找的内容?

这份文档对您来说是否不足?

是否令人困惑?

是否...我敢说...不准确?

如果以上任何一点描述了您对这份文档的感受,请随时提出问题