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
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"] | |
} |
{ | |
"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"] | |
} |
删除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; |
import * as React from "react"; | |
const Home = () => { | |
return <h1>Welcome to My Next App!</h1>; | |
}; | |
export default Home; |
重新启动服务器并通过运行以下命令检查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; | |
}, | |
}; |
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; | |
}, | |
}; |
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; | |
} |
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; | |
} |
预览.js
在此文件中,我们配置了用于渲染组件的“预览” iframe。我们将在此处导入全局 scss 文件。
// Import global css here | |
import "../styles/global.scss"; |
// Import global css here | |
import "../styles/global.scss"; |
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")], | |
}; |
const path = require("path"); | |
module.exports = { | |
stories: ["../components/**/*.stories.tsx"], | |
addons: ["@storybook/preset-typescript"], | |
// Add nextjs preset | |
presets: [path.resolve(__dirname, "./next-preset.js")], | |
}; |
创建一个故事
让我们创建一个简单的按钮组件和一个故事来测试我们的 Storybook 设置。首先,创建一个 components 文件夹,Button.tsx
并Button.stories.tsx
在其中创建 2 个文件。
/* root folder*/
mkdir components
touch components/Button.tsx components/Button.stories.tsx
然后,将以下内容添加到两个文件中:
最后,添加 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 tests
和snapshot 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() }); |
const enzyme = require('enzyme'); | |
const Adapter = require('enzyme-adapter-react-16'); | |
enzyme.configure({ adapter: new Adapter() }); |
此代码也将在每次测试之前但在测试框架执行之后运行:
现在让我们为 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/'], | |
}; |
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/'], | |
}; |
配置 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!"); | |
}); | |
}); | |
}); |
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!"); | |
}); | |
}); | |
}); |
快照测试
最后,我将向您展示如何创建一个简单的快照测试。我们使用快照测试来保留 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
现在,添加 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.json
、node_modules
文件夹和.next
文件夹,然后重新启动服务器、故事书并再次重新运行测试。
结论
感谢您的阅读🙏🏻,如果您遇到任何问题并且它对您有帮助,请在评论中告诉我。
您也可以在此处克隆源代码以立即开始开发:https://github.com/trinwin/storybook-next-ts-template
鏂囩珷鏉ユ簮锛�https://dev.to/trinwin/2020-complete-setup-for-storybook-nextjs-typescript-scss-and-jest-4khm