构建并发布组件库 - React、TypeScript、Storybook
AWS AI 直播!
介绍
本文最后,您将创建自己的自定义React 组件库,并将其发布到npm,这样其他人就可以通过简单的npm install来使用它。
为什么?
React凭借其组件驱动的架构,主导了现代Web开发。
组件可以是大型应用程序的最小原子单元,而且我们往往会经常重用它们。例如,按钮几乎随处可见,登录页面、注册页面、行动号召(CTA)等等。
这些可重用组件构成页面,页面又构成应用程序。拥有组件库有很多优势。
- 风格一致
- 发展速度
- 可维护性
今天,我们将学习如何构建现代组件库(如Chakra UI、Material UI ),并将其用于我们的项目中。
所需工具和知识
- VS Code(或你喜欢的任何代码编辑器)
- NPM
- Git
- React
- TypeScript
- 故事书
让我们开始建造吧🛠️
搭建骨架🩻
- 创建一个空目录或
cd将其复制到现有目录中
mkdir abhi-cl-blog -> cd abhi-cl-blog
- 初始化项目
npm init
这将创建一个 package.json 文件package.json,只需按回车键继续,我们稍后会对其进行编辑。下图将与为您生成的 package.json 文件类似。
- 初始化 Git
git init
首次学习/构建时,请使用原子提交,这样可以帮助我们回溯/找到出错的步骤。
- 安装 React 和 TypeScript 即可开始使用
npm install react react-dom typescript @types/react --save-dev
--save-dev 将它们安装为devDependency(阅读更多)
注意:由于我们将把这个库发布到 npm 供其他人使用,因此我们必须确保用户在使用我们的库时拥有正确版本的依赖项,所以我们将 react 和 react-dom 保存为
peerDependencies.
- 创建一个
.gitignore文件,以便node_modules稍后排除其他文件。
在这个阶段创建一个提交,如果之后遇到错误,你可以回滚到这个阶段,而不是对着电脑大喊大叫然后重新开始教程🙂
构建我们的第一个组件
要创建我们的组件,请构建以下结构
-
├── src
│ ├── components
| │ ├── Button
| | │ ├── Button.tsx
| | │ └── index.ts
| │ └── index.ts
│ └── index.ts
├── package.json
└── package-lock.json
我们正在构建一个库,希望用户能够轻松地使用/导入index我们的组件,因此我们在每个层级都创建了文件(点击此处了解更多信息)。
共有三个 index.ts文件,请在继续操作前仔细检查。
- 初始化并配置 TypeScript
npx tsc --init
这将在项目根目录创建一个tsconfig.json文件,其中包含 TypeScript 的默认配置,我们将修改其中一些配置。
{
"compilerOptions": {
"target": "es2016",
"jsx": "react",
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"declarationDir": "types",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
这就是我们的tsconfig.json文件,把它复制到你的项目中。
具体内容可以在我的gist中查看。
如果您想了解更多关于 tsconfig 的信息,请继续阅读。
- 在内部构建 Button.tsx
src/components/Button。
Button.tsx
import React from "react";
export interface ButtonProps{
label: string;
}
const Button = ( {label}: ButtonProps) => {
return <button>{label}</button>
}
export default Button;
这里我们定义了按钮将接收的 props 的接口。
然后,我们构建一个简单的<Button />组件,该组件接受label一个 prop 作为参数,并返回一个带有所传递的 label prop 的 html 按钮元素。
我们将发布一个包含单个组件的库,并确认其功能正常后,再根据需要添加更多组件。
现在我们将导出按钮。
第一次出口:src/components/Button/index.ts
// This is importing Button and exporting it directly
// Syntactic sugar
export { default } from "./Button";
第二次出口:src/components/index.ts
export { default as Button } from "./Button";
第三出口:src/index.ts
export * from './components';
如果你想进行比较,请查看此提交以比较你的文件。
想了解更多关于上述导出功能的信息?请查看StackOverflow 上的这个回答。
添加 Rollup
Rollup 是一个类似于 webpack 的工具,我们将使用它来打包我们的库,然后发布到 npm。
注意:在开始之前,请务必记住,这些打包工具使用了大量软件包,而且这些软件包更新频繁,因此您可能会遇到一些错误。
我会尽量解释每个安装步骤的作用,以便您在遇到问题时可以尝试解决。如果您发现任何问题,也可以在此处留言,我会尽力修复。
第一步:
npm install --save-dev tslib
步骤二:
npm install rollup @rollup/plugin-node-resolve
@rollup/plugin-typescript @rollup/plugin-commonjs
rollup-plugin-dts --save-dev
-
@rollup/plugin-node-resolve允许 Rollup 解析库的依赖项,从而可以从外部包导入模块。 -
@rollup/plugin-typescript:需要tslib作为对等依赖项,因此,步骤 1. 用于转译库中的 TypeScript 代码。 -
@rollup/plugin-commonjs将 CommonJS 模块转换为 ES6。 -
rollup-plugin-dts用于生成 .d.ts 文件,该文件为库提供 TypeScript 类型定义。这对 TypeScript 用户来说非常重要,因为它允许他们以完全的类型安全方式使用该库。
现在我们将在项目根目录下创建一个配置文件。
rollup.config.mjs
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import packageJson from "./package.json" assert { type: "json" };
export default [
{
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
},
{
file: packageJson.module,
format: "esm",
},
],
plugins: [
resolve(),
commonjs(),
typescript({tsconfig: "./tsconfig.json"}),
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
},
];
上面的代码块是一个 Rollup 配置文件,用于打包使用 Typescript 创建的 React 组件库。
第一个配置对象input是我们库的入口点,即目录中导出我们所有组件的index.ts文件。src
我们使用 ESM 和 commonJS 模块来分发我们的库,以便用户可以选择使用哪种类型。
我们调用的这三个插件决定了实际生成的 JavaScript 代码。
第二个配置对象:
它决定了我们库中的类型是如何分布的,并且它使用dts插件来实现这一点。
我们现在将进行更新main,并module在我们的package.json
//package.json
{
"name": "abhi-cl-blog", // 👈name it what you want
"version": "0.0.1",
"description": "A Component Library for Building React Applications faster",
"scripts": {
// 👇👇This is what you will run to create a library
"rollup-build-lib": "rollup -c"
},
"author": "Abhijit Sharma",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^11.0.0",
"@types/react": "^18.0.27",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^3.14.0",
"rollup-plugin-dts": "^5.1.1",
"tslib": "^2.5.0",
"typescript": "^4.9.5"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
//new additions 👇👇
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"files": [
"dist"
],
"types": "dist/index.d.ts"
}
- "main" - commonJS 模块的输出路径。
- "module" - es6 模块的输出路径。
- “文件” - 我们已经定义了整个库的输出目录。
- “types” - 我们已经定义了库中类型的位置。
- “scripts”——我们将使用它来运行我们的脚本。例如:npm run rollup-build-lib
运行汇总脚本:
npm run rollup-build-lib
你会注意到出现了一个名为 dist 的新文件夹。
将我们的库发布到 npm
-
创建一个 npm 帐户,如果您已有帐户,请忽略此操作。
-
在项目目录的根目录下,运行
npm login -
请更新您的
package.json信息,使其包含正确的名称、版本和描述信息。初始版本号保留为 0.0.1
-
跑步
npm publish
恭喜🥳,您已将组件库发布到 npm。
如果你搞不定,网上有很多教程和视频讲解。这个YouTube频道很棒。
在项目中测试我们的库
-
创建一个新的 React 应用(使用 CRA、Vite 等)。
-
打开新应用
-
从 npm 安装你的库
npm install <YOUR_PACKAGE_NAME>
// npm install abhi-cl-blog
- 让我们
<Button/>在……中使用我们的组件App.tsx
import React from "react";
import { Button } from "YOUR_PACKAGE_NAME";
// import {Button} from "abhi-cl-blog";
function App() {
return <Button label="Building Stuff is fun"/>;
}
export default App;
- 保存并重启应用程序后,我们看到组件按预期工作。
给自己点个赞!你刚刚构建了一个可用的组件库,现在所有人都可以使用它了🙌
如果您想继续自行学习,现在可以离开本教程了,因为下一部分我们将学习如何添加
- CSS
- 故事书
添加 CSS
如果我们想让我们的组件拥有一些样式,我们就必须使用 CSS。
- 在目录
button.css内创建一个文件Buttonsrc/components/Button/button.css
button.css
.btn{
background-color: blueviolet;
}
btn在我们的类中使用该类Button.tsx。
import React from "react";
import "./button.css" // 👈new addition
export interface ButtonProps{
label: string;
}
const Button = ({label}: ButtonProps) => {
// btn class added 👇👇
return <button className="btn">{label}</button>
}
export default Button;
这看起来似乎可行,但import "./button.css"Rollup 无法识别,因此无法使用。我们需要添加一些配置,才能让 Rollup 理解我们写入的内容。
npm install postcss rollup-plugin-postcss —save-dev
rollup-plugin-postcss用于将 CSS 文件打包到最终构建中,而postcss它本身用于转换 CSS 以使其与不同的浏览器兼容(当我们使用 Tailwind 时将使用)。
更新我们的汇总配置
rollup.config.mjs
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import packageJson from "./package.json" assert { type: "json" };
import postcss from "rollup-plugin-postcss"; //👈 new
export default [
{
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
},
{
file: packageJson.module,
format: "esm",
},
],
plugins: [
resolve(),
commonjs(),
typescript({tsconfig: "./tsconfig.json"}),
// 👇 new
postcss({
plugins: []
})
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
external: [/\.(css|less|scss)$/], //👈 new
},
];
现在我们可以重新发布(或更新)我们的软件包了。
- 将版本号更新
package.json为 0.0.2
npm run rollup-build-lib
num publish
请在您的演示应用程序中再次进行测试,以查看 CSS 是否已生效。
使用 terser 进行优化
这是可选步骤,只是为了减小软件包的大小。
npm install --save-dev @rollup/plugin-terser rollup-plugin-peer-deps-external
安装完成后,我们将更新汇总配置。
rollup.config.mjs
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import packageJson from "./package.json" assert { type: "json" };
import postcss from "rollup-plugin-postcss";
// 👇new imports
import terser from "@rollup/plugin-terser";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
export default [
{
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
},
{
file: packageJson.module,
format: "esm",
},
],
plugins: [
peerDepsExternal(), // 👈 new line
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
postcss({
plugins: []
}),
terser(), // 👈 new line
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
external: [/\.(css|less|scss)$/],
},
];
-
运行
npm run rollup-build-lib以创建更新的 dist -
更新版本号
package.json -
运行
npm publish以更新库
整合故事书
Storybook 是一款功能强大的工具,可用于独立开发和测试组件。它允许你在沙盒环境中构建和查看组件,而无需担心应用程序的其他部分。这使得组件的迭代开发更加便捷,并确保它们在集成到大型应用程序之前能够正常工作。
此外,Storybook 还提供了一种极佳的方式来记录组件,方便其他开发者理解其使用方法。总而言之,对于任何构建组件库或使用可重用 UI 组件的开发者来说,Storybook 都是一款必不可少的工具。
从本质上讲,storybook 可以让我们在无需创建 React 应用的情况下测试我们的按钮。
在项目根目录中运行,
npx storybook init
Storybook 如何检测到我们的项目是用 React 编写的?(谷歌)
你会注意到故事书创建了一些新文件夹.storybook。 请删除该目录src/stories,因为我们将学习如何创建自己的故事。src/stories
src/components/Button让我们来创作我们的故事,在名为“故事”的目录中创建一个文件Button.stories.tsx
src/components/Button/Button.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Button from './Button';
// You can learn about this: https://storybook.js.org/docs/react/writing-stories/introduction
export default {
title: 'Button',
component: Button,
} as ComponentMeta<typeof Button>;
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />
export const Primary = Template.bind({});
Primary.args = {
label: "Primary"
}
export const Secondary = Template.bind({})
Secondary.args = {
label: "Secondary"
}
Storybook 中有两个基本的组织层级:
组件及其子故事。
您可以将每个故事视为组件的一种排列组合。每个组件可以包含任意数量的故事,具体取决于您的需要。
- 成分 (
Button) - 故事 (
Primary Button) - 故事 (
Secondary Button) - 故事 (
Large Button)
export default定义将在 Storybook 中显示的按钮
Template这Template.bind真是一个很棒的想法,你可以点击这里查看。
让我们一起读故事书
npm run storybook
如果遇到错误,不要着急,仔细阅读错误信息并尝试修复,因为这些工具会经常更新。
如果运行良好,你会看到这个
这仅仅是个开始,如果你通过文档了解更多关于 Storybook 的信息(他们甚至还有一些很棒的 YouTube 视频),你一定会喜欢的。
最后想说的话
你读完这篇文章做得很好,这篇文章让你接触到了很多新概念,例如……
- 修改配置文件
- 捆绑您自己的图书馆
- 发布到 npm
- 进行原子提交
- 故事书
这些都是非常宝贵的学习经历,恭喜你🥳。
现在,你已经准备好构建自己的组件库,并将其发布到全世界。
如果您喜欢这篇文章,并且认为它对其他人有帮助,请随意分享。如果您觉得有什么可以改进或补充的地方,欢迎留言。
如果您想阅读更多内容:
文章来源:https://dev.to/abhijitdotsharma/build-and-publish-a-component-library-react-typescript-storybook-34ba





