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
后端开发教程 - Java、Spring Boot 实战 - msg200.com





