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

👉 绝对导入

👉 运行时配置

👉 自定义 Webpack 配置

👉 Typescript

需求

  • Next.js >= 9.x
  • Storybook >= 6.x
    • Storybook webpack 5 构建器
      • 简介
      • 安装指南
      • 并不是说此插件无法支持 webpack 4 构建器,只是还没有必要,而且 Storybook 建议将其用于 nextjs 应用程序。如果您认为自己有一个好的用例,请随时 打开一个问题
  • 您的 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')
      }
    }
    // ...
  ]
}
  • nextConfigPathnext.config.js 的绝对路径

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

next/image 是出了名的难 用 storybook 来实现。此插件允许您在无需配置的情况下使用 Next.js 的 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 集成注意事项

如果您覆盖了某个函数,您将失去自动 actions 选项卡集成,并必须自行构建。

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

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"]
        }
      }
    ]
  ]
}

如果您使用的是单仓库,则可能需要将 babel 配置自己添加到 storybook 项目中。只需将一个 babel 配置添加到您的 storybook 项目中,其中包含以下内容即可开始。

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

Postcss

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

这使得您可以实现一些很酷的功能,比如无需配置的 tailwindcss!请查看 with-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'

// ...

运行时配置

Next.js 允许 运行时配置,它让您可以导入一个方便的 getConfig 函数,以在运行时获取 next.config.js 文件中定义的某些配置。

在使用此插件的 Storybook 上下文中,您可以预期 Next.js 的 运行时配置 功能可以正常运行。

注意,由于 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 中正常工作,这很可能是错误,请随时打开一个问题

任何希望用于 Storybook 的 webpack 修改都应在.storybook/main.js 中根据 Storybook 的文档进行。

注意:并非所有 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 对绝对导入和模块路径别名的支持提供了额外的支持。简而言之,它会考虑您的tsconfig.jsonbaseUrlpaths。因此,像下面这样的tsconfig.json 会开箱即用。

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

next.config.js

ESM

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

针对 Yarn v2 和 v3 用户的注意事项

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

模块未找到:错误:无法解析 'css-loader'
模块未找到:错误:无法解析 '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 配置文件的 .mjs 扩展名时,此插件会中断

目前,此附加组件不支持使用next.config.mjs。有关更多详细信息,请参见next.config.js。目前,您需要使用.js 扩展名。欢迎为此讨论做出贡献,以获得对它的支持。

模块未找到:错误:无法解析 [包名]

如果您使用的是 Yarn v2 或 v3,则可能会遇到这种情况。有关更多详细信息,请参见Yarn v2 和 v3 用户的说明

类似项目

想建议其他功能吗?

我乐意与您讨论。请随时打开一个问题

没有找到您要查找的内容?

这份文档对您来说不够充分吗?

它是否令人困惑?

它是否……我敢说……不准确?

如果以上任何一项描述了您对这份文档的感受。请随时打开一个问题