2020 Storybook、Nextjs、Typescript、SCSS 和 Jest 的完整设置要求创建 Nextjs 应用 TypeScript Storybook Jest 结论

2025-06-10

2020 Storybook、Nextjs、Typescript、SCSS 和 Jest 的完整设置

要求

创建 Nextjs 应用

TypeScript

故事书

笑话

结论

在本文中,我将逐步指导您使用 Next、Typescript、SCSS 和 Jest 设置 Storybook。

Storybook 是一款开源的 UI 组件开发工具,能够帮助开发者高效、有序地构建精美的 UI。然而,使用 Nextjs 进行设置可能会比较棘手。

要求

  • Node.js 10.13 或更高版本
  • 支持 MacOS、Windows(包括 WSL)和 Linux

创建 Nextjs 应用

使用 create-next-app 创建一个新的 Next.js 应用,它会自动为你设置一切。要创建项目,请运行以下命令:

$ npx create-next-app
✔ What is your project named? … my-app
✔ Pick a template › Default starter app
  • 输入您的项目名称+按回车键
  • 系统会要求您选择一个模板:使用箭头键 ⬇ 选择一个Default starter app并按回车键

安装完成后,启动开发服务器:

cd my-app
yarn run dev

您应该在 localhost:3000 上看到此页面
替代文本

TypeScript

接下来,让我们为我们的 Next 应用配置 Typescript

$ yarn add -D typescript @types/react @types/node

在根文件夹中创建一个tsconfig.json— — 这是您将放置打字稿配置的地方。

/* root folder */
$ touch tsconfig.json

并将以下配置添加到文件:

{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noImplicitAny": false,
"jsx": "preserve",
"baseUrl": "./",
"paths": {
"@components/*": ["./components/*"]
}
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
view raw tsconfig.json hosted with ❤ by GitHub
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noImplicitAny": false,
"jsx": "preserve",
"baseUrl": "./",
"paths": {
"@components/*": ["./components/*"]
}
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
view raw tsconfig.json hosted with ❤ by GitHub

删除index.js并创建index.tsx文件。您可以手动操作,也可以在根文件夹中使用这些命令

/* root folder */
rm -f pages/index.js
touch pages/index.tsx

添加以下内容index.tsx

import * as React from "react";
const Home = () => {
return <h1>Welcome to My Next App!</h1>;
};
export default Home;
view raw index.tsx hosted with ❤ by GitHub
import * as React from "react";
const Home = () => {
return <h1>Welcome to My Next App!</h1>;
};
export default Home;
view raw index.tsx hosted with ❤ by GitHub

重新启动服务器并通过运行以下命令检查http://localhost:3000/

$ yarn run dev

替代文本

故事书

接下来,我们将为 Storybook 配置 Nextjs、SCSS 和 Typescript

$ yarn add -D @storybook/react @storybook/preset-typescript

创建.storybook文件夹和故事书配置文件:

/* root folder */
mkdir .storybook
cd .storybook
touch .storybook/main.js .storybook/next-preset.js .storybook/preview.js

现在我们将介绍如何配置这些文件。

next-preset.js

在此文件中,我们将配置 Typescript 和 SCSS 以与 Storybook 配合使用

$ yarn add -D sass style-loader css-loader sass-loader @babel/core babel-loader babel-preset-react-app

添加以下配置next-preset.js

const path = require('path');
module.exports = {
webpackFinal: async (baseConfig, options) => {
const { module = {} } = baseConfig;
const newConfig = {
...baseConfig,
module: {
...module,
rules: [...(module.rules || [])],
},
};
// TypeScript
newConfig.module.rules.push({
test: /\.(ts|tsx)$/,
include: [path.resolve(__dirname, '../components')],
use: [
{
loader: 'babel-loader',
options: {
presets: ['next/babel', require.resolve('babel-preset-react-app')],
plugins: ['react-docgen'],
},
},
],
});
newConfig.resolve.extensions.push('.ts', '.tsx');
// SCSS
newConfig.module.rules.push({
test: /\.(s*)css$/,
loaders: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, '../styles/global.scss'),
});
// If you are using CSS Modules, check out the setup from Justin (justincy)
// Many thanks to Justin for the inspiration
// https://gist.github.com/justincy/b8805ae2b333ac98d5a3bd9f431e8f70#file-next-preset-js
return newConfig;
},
};
view raw next-preset.js hosted with ❤ by GitHub
const path = require('path');
module.exports = {
webpackFinal: async (baseConfig, options) => {
const { module = {} } = baseConfig;
const newConfig = {
...baseConfig,
module: {
...module,
rules: [...(module.rules || [])],
},
};
// TypeScript
newConfig.module.rules.push({
test: /\.(ts|tsx)$/,
include: [path.resolve(__dirname, '../components')],
use: [
{
loader: 'babel-loader',
options: {
presets: ['next/babel', require.resolve('babel-preset-react-app')],
plugins: ['react-docgen'],
},
},
],
});
newConfig.resolve.extensions.push('.ts', '.tsx');
// SCSS
newConfig.module.rules.push({
test: /\.(s*)css$/,
loaders: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, '../styles/global.scss'),
});
// If you are using CSS Modules, check out the setup from Justin (justincy)
// Many thanks to Justin for the inspiration
// https://gist.github.com/justincy/b8805ae2b333ac98d5a3bd9f431e8f70#file-next-preset-js
return newConfig;
},
};
view raw next-preset.js hosted with ❤ by GitHub

SCSS

在根目录中创建样式文件夹并添加全局 scss 文件。

/* root folder */
mkdir styles
touch styles/global.scss
html {
background: #f1f1f1;
max-width: 100%;
}
body {
background: linear-gradient(
315deg,
var(#f1f1f1) 0%,
var(#e7e7e7) 100%
);
font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", system-ui, sans-serif !important;
}
view raw global.scss hosted with ❤ by GitHub
html {
background: #f1f1f1;
max-width: 100%;
}
body {
background: linear-gradient(
315deg,
var(#f1f1f1) 0%,
var(#e7e7e7) 100%
);
font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", system-ui, sans-serif !important;
}
view raw global.scss hosted with ❤ by GitHub

预览.js

在此文件中,我们配置了用于渲染组件的“预览” iframe。我们将在此处导入全局 scss 文件。

// Import global css here
import "../styles/global.scss";
view raw preview.js hosted with ❤ by GitHub
// Import global css here
import "../styles/global.scss";
view raw preview.js hosted with ❤ by GitHub

main.js

main.js 是最重要的配置文件。Storybook 的主要配置都放在这里。

const path = require("path");
module.exports = {
stories: ["../components/**/*.stories.tsx"],
addons: ["@storybook/preset-typescript"],
// Add nextjs preset
presets: [path.resolve(__dirname, "./next-preset.js")],
};
view raw main.js hosted with ❤ by GitHub
const path = require("path");
module.exports = {
stories: ["../components/**/*.stories.tsx"],
addons: ["@storybook/preset-typescript"],
// Add nextjs preset
presets: [path.resolve(__dirname, "./next-preset.js")],
};
view raw main.js hosted with ❤ by GitHub

创建一个故事

让我们创建一个简单的按钮组件和一个故事来测试我们的 Storybook 设置。首先,创建一个 components 文件夹,Button.tsxButton.stories.tsx在其中创建 2 个文件。

/* root folder*/
mkdir components
touch components/Button.tsx components/Button.stories.tsx

然后,将以下内容添加到两个文件中:

import * as React from "react";
type Props = {
text: string;
};
export default ({ text }: Props) => <button>{text}</button>;
view raw Button.tsx hosted with ❤ by GitHub
import * as React from "react";
type Props = {
text: string;
};
export default ({ text }: Props) => <button>{text}</button>;
view raw Button.tsx hosted with ❤ by GitHub
import { storiesOf } from "@storybook/react";
import Button from "./Button";
storiesOf("Button", module).add("with text", () => {
return <Button text="Hello World" />;
});
storiesOf("Button", module).add("with emoji", () => {
return <Button text="😀 😎 👍 💯" />;
});
import { storiesOf } from "@storybook/react";
import Button from "./Button";
storiesOf("Button", module).add("with text", () => {
return <Button text="Hello World" />;
});
storiesOf("Button", module).add("with emoji", () => {
return <Button text="😀 😎 👍 💯" />;
});

最后,添加 npm 脚本来package.json启动故事书。

{
  ...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "storybook": "start-storybook -p 6006 -c .storybook"
  }
}

现在,让我们运行我们的故事书。

$ yarn storybook

替代文本

您应该看到我们的全局 scss 样式已生效,并且我们之前创建的用于测试按钮的 2 个故事也已生效。

替代文本

替代文本

笑话

接下来,我们将在 Jest 中添加unit testssnapshot tests用于测试 Nextjs 和 Typescript 中的组件。

首先,让我们为 Jest 安装这些开发依赖项。

$ yarn add -D jest @types/jest ts-jest babel-jest @types/enzyme enzyme enzyme-adapter-react-16

我们需要配置 Enzyme 来使用适配器,这可以在 Jest 的引导文件中完成。让我们创建一个 config 文件夹,并将安装文件放入其中。

/* root folder */
mkdir config
touch config/setup.js
const enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
enzyme.configure({ adapter: new Adapter() });
view raw setup.js hosted with ❤ by GitHub
const enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
enzyme.configure({ adapter: new Adapter() });
view raw setup.js hosted with ❤ by GitHub

此代码也将在每次测试之前但在测试框架执行之后运行:

现在让我们为 Jest 创建一个配置文件。如果你将上面的设置文件放在了其他位置,请确保更改setupFiles: […]in 的值jest.config.js

/* root folder */
$ touch jest.config.js
module.exports = {
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!**/node_modules/**',
'!**/.storybook/**',
'!**/tests/**',
'!**/coverage/**',
'!jest.config.js',
],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
setupFiles: ['<rootDir>/config/setup.js'],
preset: 'ts-jest',
testPathIgnorePatterns: ['/.next/', '/node_modules/', '/lib/', '/tests/', '/coverage/', '/.storybook/'],
testRegex: '(/__test__/.*|\\.(test|spec))\\.(ts|tsx|js)$',
testURL: 'http://localhost',
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/fileMock.js',
},
transform: {
'.(ts|tsx)': 'babel-jest',
},
transformIgnorePatterns: ['<rootDir>/node_modules/'],
};
view raw jest.config.js hosted with ❤ by GitHub
module.exports = {
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!**/node_modules/**',
'!**/.storybook/**',
'!**/tests/**',
'!**/coverage/**',
'!jest.config.js',
],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
setupFiles: ['<rootDir>/config/setup.js'],
preset: 'ts-jest',
testPathIgnorePatterns: ['/.next/', '/node_modules/', '/lib/', '/tests/', '/coverage/', '/.storybook/'],
testRegex: '(/__test__/.*|\\.(test|spec))\\.(ts|tsx|js)$',
testURL: 'http://localhost',
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/fileMock.js',
},
transform: {
'.(ts|tsx)': 'babel-jest',
},
transformIgnorePatterns: ['<rootDir>/node_modules/'],
};
view raw jest.config.js hosted with ❤ by GitHub

配置 babel.config.json

最后,我们将添加 babel 配置。让我们通过运行以下命令将这些 dev 依赖项添加到 package.json 中:

yarn add -D @babel/preset-env @babel/preset-react @babel/preset-flow @babel/plugin-transform-runtime babel-plugin-transform-es2015-modules-commonjs

在根文件夹中,创建一个 Babel 配置文件。由于某些原因,babel.rc 无法正常工作,因此我不得不将其替换为babel.config.json

/* root folder */
$ touch babel.config.json
{
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-modules-commonjs",
"@babel/plugin-proposal-class-properties"
],
"env": {
"development": {
"plugins": ["transform-es2015-modules-commonjs"]
},
"test": {
"plugins": [
"transform-es2015-modules-commonjs",
"@babel/plugin-proposal-class-properties"
],
"presets": ["@babel/preset-react"]
}
}
}
{
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-modules-commonjs",
"@babel/plugin-proposal-class-properties"
],
"env": {
"development": {
"plugins": ["transform-es2015-modules-commonjs"]
},
"test": {
"plugins": [
"transform-es2015-modules-commonjs",
"@babel/plugin-proposal-class-properties"
],
"presets": ["@babel/preset-react"]
}
}
}

让我们创建一个测试

现在,让我们运行一个简单的单元测试来测试我们之前创建的索引文件,以确保它具有欢迎消息“欢迎使用我的下一个应用程序!”作为“h1”元素。

首先,创建一个__test__文件夹将我们的测试文件保存在一个地方并创建index.test.tsx文件。

/* root folder */
mkdir components/__test__
touch components/__test__/index.test.tsx
import React from "react";
import { mount } from "enzyme";
import Home from "../../pages/index";
describe("Pages", () => {
describe("Home", () => {
it("should render without throwing an error", function () {
const wrap = mount(<Home />);
expect(wrap.find("h1").text()).toBe("Welcome to My Next App!");
});
});
});
view raw index.test.tsx hosted with ❤ by GitHub
import React from "react";
import { mount } from "enzyme";
import Home from "../../pages/index";
describe("Pages", () => {
describe("Home", () => {
it("should render without throwing an error", function () {
const wrap = mount(<Home />);
expect(wrap.find("h1").text()).toBe("Welcome to My Next App!");
});
});
});
view raw index.test.tsx hosted with ❤ by GitHub

快照测试

最后,我将向您展示如何创建一个简单的快照测试。我们使用快照测试来保留 UI 组件结构的副本或快照,以便在进行任何更改后,我们可以查看更改并更新快照。您可以在此处阅读有关快照测试的更多信息。

首先,让我们安装 react-test-renderer,这是一个库,它使您能够将 React 组件渲染为 JavaScript 对象,而无需 DOM。

$ yarn add -D react-test-renderer

现在,创建一个名为的文件Button.snapshot.test.tsx来测试为 Button 组件创建新的快照。

$ touch components/__test__/Button.snapshot.test.tsx
import React from "react";
import Button from "../Button";
import renderer from "react-test-renderer";
it("renders correctly", () => {
const tree = renderer.create(<Button text="Some Text" />).toJSON();
expect(tree).toMatchSnapshot();
});
import React from "react";
import Button from "../Button";
import renderer from "react-test-renderer";
it("renders correctly", () => {
const tree = renderer.create(<Button text="Some Text" />).toJSON();
expect(tree).toMatchSnapshot();
});

现在,添加 npm 脚本来package.json运行测试

{
  ...
  "scripts": {
    ...
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

继续并运行测试。

$ yarn run test

您应该看到 1 个单元测试和 1 个快照测试已通过

替代文本

如果遇到诸如“ The default export is not a React Component in page: ‘/’”或“ ReferenceError: regeneratorRuntime is not defined”之类的错误,请尝试删除package-lock.jsonnode_modules文件夹和.next文件夹,然后重新启动服务器、故事书并再次重新运行测试。

结论

感谢您的阅读🙏🏻,如果您遇到任何问题并且它对您有帮助,请在评论中告诉我。

您也可以在此处克隆源代码以立即开始开发:https://github.com/trinwin/storybook-next-ts-template

通过MediumLinkedInGithubTwitter 🤓与我联系

鏂囩珷鏉ユ簮锛�https://dev.to/trinwin/2020-complete-setup-for-storybook-nextjs-typescript-scss-and-jest-4khm
PREV
分享您的 Github Profile Readme
NEXT
程序员总是想太多