【react storybook】从零搭建react脚手架,并使用storybook发布组件库到npm,并生成可视化UI文档

news2024/12/22 12:57:49

storybook

  • 成品展示
  • 开发准备
  • 开发组件
  • 写MDX文档
  • 发布文档
  • 发布组件


成品展示

可视化UI文档页面:
在这里插入图片描述
在这里插入图片描述

可视化UI文档地址:

https://guozia007.gitee.io/storybook-ui/?path=/docs/mdx-button--default-story

组件库地址:

https://www.npmjs.com/package/storybook-ui-public

项目地址:

https://gitee.com/guozia007/storybook-ui

开发准备

在gitee或者github创建仓库,然后clone到本地。

这次使用了storybook,很多开发环境下的依赖都不需要装了。

根目录创建.gitignore,屏蔽这些,不上传,基本操作了

node_modules
lib
dist

初始化生成pkg文件:

npm init -y

安装react:

npm i react react-dom -D

安装webpack5:

// 这次就不需要安装webpack-dev-server了,因为storybook已经准备好了开发环境的服务
npm i webpack webpack-cli -D

babel那一套,看哪个没有就装哪个,有些是已经通过storybook装好了:

npm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D

根目录下创建.babelrc.js,配置babel:

module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-react'
  ]
}

根目录下创建jsconfig.json

{
  "compilerOptions": {
    "outDir": "./lib/",
    "module": "ESNext",
    "target": "ES5",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "jsx": "react",
    "allowJs": true,
    "allowSyntheticDefaultImports":true
  },
  "exclude": ["node_modules", "lib"]
}

stories目录下的文件全部清空,然后创建Button.stories.mdx

用于后面写可视化UI文档。

在根目录下创建src目录,我们的组件要写这里面。

安装less相关:

npm i less less-loader -D

安装postcss相关,用于做样式兼容配置:

npm i postcss postcss-loader postcss-preset-env -D

安装glob,用于获取入口路径:

npm i glob -D

安装css相关plugin:

npm i mini-css-extract-plugin -D

npm i css-minimizer-webpack-plugin -D

安装storybook管理webpack的工具,默认在使用的版本为4,我们安装5,

用以支持webpack5:

npm i @storybook/builder-webpack5 -D

npm i @storybook/manager-webpack5 -D

在根目录下创建webpack.config.js,配置生产模式下的webpack:

// webpack.config.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const glob = require('glob');

const getStyleLoader = (importLoaders, loaderName) => {
  return [
    MiniCssExtractPlugin.loader,
    {
      loader: 'css-loader',
      options: {
        importLoaders
      }
    },
    {
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: [
            'postcss-preset-env'
          ]
        }
      }
    },
    loaderName
  ].filter(Boolean);
}

const entries = {};
const fileNames = glob.sync('./src/**/*.js?(x)');
// console.log('fileNames: ', fileNames);
fileNames.forEach(file => {
  const filePath = file.replace(/^\.\/src\/(.+)\.jsx?$/, '$1');
  entries[filePath] = file;
})

module.exports = {
  mode: 'production',
  entry: entries,
  output: {
    path: path.resolve(__dirname, 'lib'),
    filename: '[name].js',
    clean: true,
    library: {
      name: 'storybook-ui',
      type: 'umd'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: getStyleLoader(1)
      },
      {
        test: /\.less$/,
        use: getStyleLoader(2, 'less-loader')
      },
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css'
    })
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',
      name: 'chunk'
    },
    minimizer: [
      new CssMinimizerPlugin()
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json']
  },
  externals: {
    react: {
      root: 'React',
      commonjs2: 'react',
      commonjs: 'react',
      amd: 'react',
    },
    'react-dom': {
      root: 'ReactDOM',
      commonjs2: 'react-dom',
      commonjs: 'react-dom',
      amd: 'react-dom',
    }
  }
}

配置.storybook/main.js

const path = require('path');

module.exports = {
  "stories": [
    "../stories/**/*.stories.mdx",
    "../stories/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  "framework": "@storybook/react",
  core: {
    builder: {
      name: 'webpack5',
      options: {
        lazyCompilation: true,
        fsCache: true
      }
    },
  },
  webpackFinal: async (config, { configType }) => {
    // 配置支持less
    // 配置支持postcss兼容
    config.module.rules.push({
      test: /\.less$/,
      include: path.resolve(__dirname, '../src'),
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: {
              plugins: [
                'postcss-preset-env'
              ]
            }
          }
        },
        'less-loader'
      ]
    });

    return config;
  }
}

配置package.json

{
  "name": "storybook-ui-public",
  "version": "0.0.2",
  "description": "使用storybook发布组件",
  "main": "lib/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "build": "webpack --config webpack.config.js",
    "pub": "npm run build && npm publish --access=public"
  },
  "repository": {
    "type": "git",
    "url": "https://gitee.com/guozia007/storybook-ui.git"
  },
  "homepage": "https://guozia007.gitee.io/storybook-ui/?path=/docs/mdx-button--default-story",
  "keywords": [
    "storybook",
    "ui",
    "framework",
    "component",
    "react component",
    "frontend"
  ],
  "author": "guozi007a",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.21.0",
    "@babel/preset-env": "^7.20.2",
    "@babel/preset-react": "^7.18.6",
    "@storybook/addon-actions": "^6.5.16",
    "@storybook/addon-docs": "^6.5.16",
    "@storybook/addon-essentials": "^6.5.16",
    "@storybook/addon-interactions": "^6.5.16",
    "@storybook/addon-links": "^6.5.16",
    "@storybook/builder-webpack4": "^6.5.16",
    "@storybook/builder-webpack5": "^6.5.16",
    "@storybook/manager-webpack4": "^6.5.16",
    "@storybook/manager-webpack5": "^6.5.16",
    "@storybook/react": "^6.5.16",
    "@storybook/testing-library": "0.0.13",
    "babel-loader": "^8.3.0",
    "css-loader": "^6.7.3",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "glob": "^8.1.0",
    "less": "^4.1.3",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.2",
    "postcss": "^8.4.21",
    "postcss-loader": "^7.0.2",
    "postcss-preset-env": "^8.0.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"
  },
  "publishConfig": {
    "registry": "https://registry.npmjs.org/"
  },
  "browserslist": [
    ">= 0.25%",
    "last 1 version",
    "not dead"
  ],
  "files": [
    "lib"
  ],
  "peerDependencies": {
    "react": ">= 16.9.0",
    "react-dom": ">= 16.9.0"
  },
  "dependencies": {}
}


开发组件

// src/index.js

export { default as Button } from './Button';
// src/Button/index.jsx

import React from 'react';
import PropTypes from 'prop-types';
import './index.less';

const Button = ({ loading, primary, backgroundColor, size, label, ...props }) => {
  const mode = loading
    ? 'storybook-button--loading'
    : primary
      ? 'storybook-button--primary'
      : 'storybook-button--default';
  return (
    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={backgroundColor && { backgroundColor }}
      {...props}
    >
      {
        loading ? <span className='storybook-loading-icon'></span> : null
      }
      {label}
    </button>
  );
};

export default Button;

Button.propTypes = {
  /**
   * Is this the principal call to action on the page?
   */
  primary: PropTypes.bool,
  /**
   * Is something in loading?
   */
  loading: PropTypes.bool,
  /**
   * What background color to use
   */
  backgroundColor: PropTypes.string,
  /**
   * How large should the button be?
   */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /**
   * Button contents
   */
  label: PropTypes.string.isRequired,
  /**
   * Optional click handler
   */
  onClick: PropTypes.func,
};

Button.defaultProps = {
  backgroundColor: null,
  primary: false,
  size: 'medium',
  label: 'default button',
  onClick: undefined,
  loading: false
};

/* src/Button/index.less */

.storybook-button {
  font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  display: inline-block;
  line-height: 1;
  user-select: none;
  font-size: 14px;
}

.storybook-button--primary {
  color: white;
  background-color: #1ea7fd;

  &:hover {
    filter: opacity(.9);
  }
}

.storybook-button--default {
  color: #333;
  background-color: transparent;
  box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;

  &:hover {
    border: 1px solid #1ea7fd;
    color: #1ea7fd;
    box-shadow: none;
  }
}

.storybook-button--loading {
  background-color: #1ea7fd;
  cursor: default;
  color: #fff;
  box-shadow: 0 2px 0 rgb(5 145 255 / 10%);
  opacity: 0.65;
  color: #fff;
}

@keyframes circle {
  0% {}
  100% {transform: rotate(360deg);}
}
.storybook-loading-icon {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-top: 1px solid #fff;
  border-right: 1px solid #fff;
  border-radius: 50%;
  margin-right: 6px;
  animation: circle .7s linear infinite;
  vertical-align: middle;
}

.storybook-button--small {
  font-size: 12px;
  padding: 10px 16px;
}

.storybook-button--medium {
  font-size: 14px;
  padding: 11px 20px;
}

.storybook-button--large {
  font-size: 16px;
  padding: 12px 24px;
}

写MDX文档

stories/Button.stories.mdx中:

import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
import { Button } from '../src';

<Meta title="MDX/Button" component={Button} />

<style>
  {`
    h2 {
      color: rgba(0, 0, 0, 0.88);
      font-weight: 500;
      font-family: Avenir,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji',sans-serif;
      font-size: 24px;
      line-height: 32px;
    }
    ul, ol {
      list-style: none;
      padding: 0;
      margin: 0;
    }
    ul li {
      margin-left: 20px;
      padding-left: 4px;
      list-style-type: circle;
    }
  `}
</style>

# Button 按钮

按钮用于开始一个即时操作。

## 何时使用

标记了一个(或封装一组)操作命令,响应用户点击行为,触发相应的业务逻辑。

在 Storybook UI 中我们提供了两种按钮。

- 主按钮:用于主行动点,一个操作区域只能有一个主按钮。

- 默认按钮:用于没有主次之分的一组行动点。

以及两种状态属性与上面配合使用。

- 危险:删除/移动/修改权限等危险操作,一般需要二次确认。

- 加载中:用于异步操作等待反馈的时候,也可以避免多次提交。

## 代码演示

export const Template = (args) => <Button {...args} />

<Canvas>
  <Story name="default"
    args={{
      label: "default button"
    }}
  >
  {Template.bind({})}
  </Story>
</Canvas>

<Canvas>
  <Story name="primary"
    args={{
      primary: true,
      label: "primary button"
    }}
  >
  {Template.bind({})}
  </Story>
</Canvas>

<Canvas>
  <Story name="loading"
    args={{
      loading: true,
      label: "Loading"
    }}
  >
  {Template.bind({})}
  </Story>
</Canvas>

## API

通过设置 Button 的属性来产生不同的按钮样式,按钮的属性说明如下:

<ArgsTable of={Button} />

发布文档

执行开发环境打包指令:

npm run build-storybook

会生成一个storybook-static的目录。

代码上传到git,然后发布到githubPages或者giteePage

这里发布后,获取到文档地址,在package.json中添加:

"homepage": "你的线上文档地址"

发布组件

执行打包并发布的指令:

npm run pub

完成。

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/371076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vmware虚拟机无法联通主机解决方法二

昨天在遇到了VMware 虚拟机无法联通主机&#xff0c;导致我在CentOS-7 搭建的伪Hadoop3 服务&#xff0c;无法访问管理平台&#xff0c;使用将网络编辑器修改为“桥接”模式解决。今天在学习HBase 时&#xff0c;昨天的问题又重新了&#xff0c;我通过SSH 工具MobaXterm 都无法…

《第一行代码》 第八章:应用手机多媒体

一&#xff0c;使用通知 第一步&#xff0c;创建项目&#xff0c;书写布局 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"android:layout_he…

【数据结构趣味多】Map和Set

1.概念及场景 Map和set是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例化子类有关。 在此之前&#xff0c;我还接触过直接查询O(N)和二分查询O(logN)&#xff0c;这两个查询有很多不足之出&#xff0c;直接查询的速率太低&#xff0c;而二分查…

如何压缩RAR格式文件?

RAR是我们日常生活工作中经常用到的压缩文件格式之一&#xff0c;那么RAR文件如何压缩呢&#xff1f; 不管压缩哪种格式的压缩文件&#xff0c;我们都需要用到压缩软件。针对RAR格式&#xff0c;我们可以选择最常见的WinRAR&#xff0c;当然如果有同样适用于RAR格式的压缩软件…

不写注释就是垃圾

最近Linux6.2出来了增加了很多新的东西&#xff0c;有看点的是&#xff0c;Linux确实要可以在Apple M1上面运行了&#xff0c;这应该是一个很大的新闻&#xff0c;如果有这么稳定的硬件支持&#xff0c;那对于Linux来说相当于又打下了一大片的江山。其中关于Linux6.2的特性罗列…

cesium: 显示闪烁的点(004)

第004个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置闪烁的点。主要是介绍entity>point 相关的属性设置 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共107行)相关API参考:专栏目标示例效果 配…

一个阿里P6的说不会接口自动化测试,他不会是自己评的吧...

序 近期和一个阿里的测试工程师交流了一波&#xff0c;他竟然说我不会接口自动化测试&#xff0c;我当场就不服了我说你P6自己评级的吧&#xff0c;今天就带大家好好盘一盘接口自动化&#xff0c;本着以和大家交流如何实现高效的接口测试为出发点&#xff0c;本文包含了我在接…

27. 移除元素 26. 删除有序数组中的重复项 88. 合并两个有序数组(双指针遍历)

目录[27. 移除元素-力扣](https://leetcode.cn/problems/remove-element/description/?languageTagsc)[26. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)[88. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-…

Synchronized与锁升级

文章目录先从阿里及其他大厂面试题说起本章路线总纲Synchronized的性能变化synchronized锁种类及升级步骤JIT编译器对锁的优化小总结先从阿里及其他大厂面试题说起 谈谈你对synchronized的理解 synchronized锁升级你聊聊 本章路线总纲 说明&#xff1a; synchronized锁&a…

Java-排序链表问题

Java-排序链表问题题目题解方法&#xff1a;自顶向下归并排序算法题目 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 示例 2&#xff1a; 示例 3&#xff1a; 提示&#xff1a; *链表中节点的数目在范围 [0, 5 * 104]…

Linux 练习一(常用命令的练习)

文章目录一、Linux 用户管理及文件操作第一段练习记录&#xff1a;主要对用户进行删除添加设置密码等操作第二段练习记录&#xff1a;主要包括权限设置和查找命令第三段练习记录&#xff1a;关于文件的命令练习第四段练习记录&#xff1a;查找命令及查看内存命令的使用二、Linu…

ClickHouse高可用集群分片-副本实操(四)

目录 一、ClickHouse高可用之ReplicatedMergeTree引擎 二、 ClickHouse高可用架构准备-环境说明和ZK搭建 三、高可用集群架构-ClickHouse副本配置实操 四、ClickHouse高可用集群架构分片 4.1 ClickHouse高可用架构之两分片实操 4.2 ClickHouse高可用架构之两分片建表实操 一…

AXI协议

AXI 的英文全称是 Advanced eXtensible Interface&#xff0c;即高级可扩展接口&#xff0c;它是 ARM 公司所提出的AMBA&#xff08;Advanced Microcontroller Bus Architecture&#xff09;协议的一部分。AXI 协议就是描述了主设备和从设备之间的数据传输方式&#xff0c;在该…

理解redis的数据结构

redis为什么快&#xff1f; 首先可以想到内存读写数据本来就快&#xff0c;然后IO复用快&#xff0c;单线程没有静态消耗和锁机制快。 还有就是数据结构的设计快。这是因为&#xff0c;键值对是按一定的数据结构来组织的&#xff0c;操作键值对最终就是对数据结构进行增删改查操…

【CSS】CSS 层叠样式表 ② ( CSS 引入方式 - 内嵌样式 )

文章目录一、CSS 引入方式 - 内嵌样式1、内嵌样式语法2、内嵌样式示例3、内嵌样式完整代码示例4、内嵌样式运行效果一、CSS 引入方式 - 内嵌样式 1、内嵌样式语法 CSS 内嵌样式 , 一般将 CSS 样式写在 HTML 的 head 标签中 ; CSS 内嵌样式 语法如下 : <head><style …

2.25测试对象分类

一.按照测试对象划分1.界面测试又称UI测试,按照界面的需求(一般是ui设计稿)和界面的设计规则,对我们软件界面所展示的全部内容进行测试和检查.对于非软件来说:颜色,大小,材质,整体是否美观对于软件来说:输入框,按钮,文字,图片...的尺寸,颜色,形状,整体适配,清晰度等等,2.可靠性…

【AcWing-Python-786】第k个数/快速选择算法

题目&#xff1a;https://www.acwing.com/problem/content/788/对应视频讲解&#xff1a;https://www.acwing.com/video/228/题目描述回顾快排【AcWing-Python-785】快速排序 - CSDN博客&#xff08;一&#xff09;步骤找到分界点x&#xff1a;可以是区间最左端点、区间最右端点…

java String类(超详细,含常用方法、面试题,内存图,案例)

String类一、String类的特点二、String 类的常见构造方法三、String常见的面试题1.字符串常量池2.String s "abc"与String s new String("abc")区别3.字符拼接4.常量优化机制四、String常用方法1. 比较字符串内容2. 遍历字符串3.截取字符串4.替换字符串5…

Linux-常见命令

&#x1f69c;关注博主&#xff1a;翻斗花园代码手牛爷爷 &#x1f699;Gitee仓库&#xff1a;牛爷爷爱写代码 目录&#x1f692;xshell热键&#x1f697;Linux基本命令&#x1f697;ls指令&#x1f695;pwd指令&#x1f696;cd指令&#x1f68c;touch指令&#x1f68d;mkdir指…

C++11多线程编程 一:多线程概述

1.1 第一个线程代码示例-线程创建示例 多线程编程当中&#xff0c;每一个程序运行都至少会有一个线程&#xff0c;一般的main函数都作为主线程的入口&#xff0c;这里面是一个进程包含一个主线程&#xff0c;一个进程里面包含多个子线程&#xff0c;所以一般在主线程当中(也就是…