从零开始教你使用 storybook + rollup 搭建一个属于自己的 React UI 组件库

·  阅读 3505
从零开始教你使用 storybook + rollup 搭建一个属于自己的 React UI 组件库

从零开始教你使用 storybook + rollup 搭建一个属于自己的 React UI 组件库

前期介绍

最近有一个负责的前端项目被我拆成了微前端项目,由一个基座应用和几个可以独立运行的子应用组成,其中各个子应用间组件的使用经常需要来回复制,而不像单个项目一样可以方便地统一引用,所以为此搭了一个前端UI的内部组件库,各个子模块能够比较方便地引用组件。这个组件库其实还使用了lerna来允许批量管理npm包的发布,不过在本篇文章的话暂时可以不作为一个必要的选择,感兴趣小伙伴可以去看看 lerna 的使用。

不管是作为KPI项目还是自己使用,搭建一个自己的UI组件库相信是大部分前端开发同学最容易上手的一个方向,但是如何起步,如何有一个比较优良的开发体验,可能对大家是一个比较头疼的点,所以我准备提供一个构建UI组件库的思路供大家使用,这个是展示链接,最终我们会做到这个效果,且相关代码已上传github仓库,也希望能获得大家的一些star🌟、issue、建议及pull request。值得注意的是部分文件不会从一开始就将最后的结果写出来,而是根据思路去一步步优化,这样大家不会看着一大段配置而不是很明白为什么要这样做,这样做的好处是什么,能够有一个比较清晰的思路去从头到尾理解这个过程。

本篇文章我会教大家如何从零开始搭建一个属于自己的 React UI 组件库,该方案使用storybook作为UI文档工具,并使用rollup进行打包,支持less及Typescript。

默认环境

由于篇幅原因,本文默认大家已经存在以下环境及条件:

  1. Node环境 (v12.19.0)
  2. NPM (v6.14.8)
  3. 一个自己的github账号
  4. 一个自己的npm账号

当然Node和NPM的环境不会限制的这么死,这里只是告知我的开发环境便于大家选择使用

github 准备

在项目开始前,我们先在github上新建一个自己的Repositories

现在,你就可以在你的github项目中拿到一个ssh链接了,类似这个: git@github.com:yourname/basic-components.git

基础项目

那么我们接下来建立一个基础项目,新建一个新文件夹,命名为basic-components,cd进入,并使用git init命令生成一个.git文件夹

mkdir basic-components
cd basic-components
git init
复制代码

使用npm init将package.json初始化一下,各个命令都直接回车就可以,后续我们再修改其中的配置。

npm init
复制代码

除了package.json文件外,我们还需要新建一个README.md文件及一个.gitignore文件用于上传到github仓库

# .gitignore
node_modules
packages/*/lib
packages/*/legacy/dist
.vscode
.DS_Store
复制代码
# README.md
your own React component library using Rollup, TypeScript, Less and Storybook
复制代码

目前我们的项目应该是这样一个结构:

.git/
.gitignore
package.json
README.md
复制代码

连接你的仓库

目前有这几个文件够了,现在我们需要将我们本地的项目与线上项目关联上,我们使用以下命令将本地的basic-components与github仓库关联起来

# 将暂存存入
git add . 
# 做一次 commit 操作
git commit -m"component library first commit" 
# 关联远程repository,记得修改为你的ssh链接
git remote add origin git@github.com:yourname/basic-components.git 
# 推代码
git push -u origin master 
复制代码

刷新你的github仓库,应该可以看到你的本地代码已经推至线上了,新版的github的default分支应该是main,大家可以使用main或者是master作为自己的分支,都是OK的,这里我切成了master分支作为defualt分支使用

OK,现在来准备一下你的组件吧

运行以下命令来安装一下React及TypeScript依赖

npm i react@16.13.1 react-dom@16.13.1 typescript@3.8.3 @types/react@16.14.14 @types/react-dom@16.9.14 --save
复制代码

storybook

安装好后,现在我们来安装一下storybook及使用到的babel

storybook本身是一个用于独立开发UI组件的开源工具,支持React、 Vue和Angular,每个组件都有一个独立开发调试环境,且运行在主应用程序之外,所以不依赖具体的开发环境及依赖等限制。

以下为官方描述:

Storybook is an open source tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation. Storybook provides a sandbox to build UIs in isolation so you can develop hard-to-reach states and edge cases. Stories are a pragmatic, reproducible way to keep track of UI edge cases. Write stories once then reuse them to power automated tests. Stories show how UIs actually work not just how they're supposed to work. That makes gathering feedback and reproductions easy.

本项目目前使用的是 storybook6,与storybook5相比没有什么太大的变动,所以如果你想使用版本5的storybook也是OK的。

这里不准备用npx sb init命令去直接init一个storybook初始项目。 我们从无组件开始创建。

npm i @storybook/react@6.3.7 babel-loader@8.2.2 less@4.1.2 less-loader@5.0.0 style-loader@1.3.0 ts-loader@6.0.4 babel-preset-react-app@10.0.0 -D
复制代码

依赖安装后,在根目录下新建一个.storybook文件夹,新建两个文件:main.js、preview.js

// .storybook/main.js
// 这是storybook的配置文件,loader、entry file等都会在此进行配置
const path = require("path");

module.exports = {
  // storybook文档的目标文件
  stories: ["../packages/**/*.stories.tsx"],
  // 插件依赖,后面我们会使用
  addons: [],
  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.less$/,
      use: ["style-loader", "css-loader", "less-loader"],
      include: path.resolve(__dirname, "../")
    });
    config.module.rules.push({
      test: /\.(ts|tsx)$/,
      loader: require.resolve("babel-loader"),
      options: {
        presets: [["react-app", { flow: false, typescript: true }]]
      }
    });
    config.resolve.extensions.push(".ts", ".tsx");

    return config;
  }
};
复制代码
// .storybook/preview.js

复制代码

再在根目录下新建一个babel的配置文件:babel.config.js

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
    '@babel/preset-typescript',
    '@babel/preset-react'
  ]
};
复制代码

在package中添加两行storybook的相关脚本

{
  // ...other old
  "scripts": {
    "sb": "start-storybook -p 6006",
    "build:sb": "build-storybook -c .storybook -o docs"
  },
  // ...other old
}
复制代码

这个部分需要安装的依赖比较多,所以这里贴出目前的package.json文件,大家可以直接复制到自己的package.json文件中。可在根目录下使用npm i命令安装全部依赖。

{
  "name": "basic-components",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "sb": "start-storybook -p 6006",
    "build:sb": "build-storybook -c .storybook -o docs"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/react": "^16.14.14",
    "@types/react-dom": "^16.9.14",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "typescript": "^3.8.3"
  },
  "devDependencies": {
    "@storybook/react": "^6.3.7",
    "babel-loader": "^8.2.2",
    "babel-preset-react-app": "^10.0.0",
    "less": "^4.1.2",
    "less-loader": "^5.0.0",
    "style-loader": "^1.3.0",
    "ts-loader": "^6.0.4"
  }
}
复制代码

示例组件

现在我们来准备几个demo组件,我们在根目录下新建一个新文件夹,在其中新建一个TextView文件夹,新建三个文件:index.tsx、index.less、index.stories.tsx

index.stories.tsx 是用于storybook预览的文件

// packages/TextView/index.tsx
import React from 'react';
import './index.less'

interface TextViewProps {
  children?: React.ReactNode
}

const TextView = ({ children }: TextViewProps) => {
  return <div className='text-view'>
    {children}
  </div>
}

export default TextView;
复制代码
/* packages/TextView/index.less */
.text-view {
  font-size: 24px;
  line-height: 40px;
  font-family: 'Times New Roman', Times, serif;
}
复制代码
// packages/TextView/index.stories.tsx
import React from "react";
import TextView from "./index";

export default {
  title: "TextView"
};

export const WithTextView = () => (
  <TextView>
    This is my test component
  </TextView>
);
复制代码

建好后,在根目录下运行以下命令来查看你的TextView组件

npm run sb
复制代码

如果到此步骤没有遗漏,目前你的文件夹应该是如下结构,如果你运行npm run sb失败了,很可能是之前有什么文件或者配置遗漏了,可以翻回文章上部重新检查。

.git/
.storybook/
  main.js
  preview.js
node_modules/
packages/
  TextView/
    index.less
    index.stories.tsx
    index.tsx
.gitignore
babel.config.js
package-lock.json
package.json
README.md
复制代码

如果启动成功,你应该能够看到一个这样的本地网页

运行npm run build:sb,可以看到一个静态网页文件夹docs/,用浏览器运行其中的index.html文件,应该可以看到与单独运行一致的页面,这个页面后续是有用的,但是现在我们可以暂时无视他。等待上传到github。

rollup打包

我们的组件库是需要发到npm作为一个库使用的,那么在我们发布之前,需要为我们的组件库增加打包功能。

这里我们使用的打包工具是rollup,他是一个模块打包工具,以下是官方描述

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解决方案,如 CommonJS 和 AMD。ES6 模块可以使你自由、无缝地使用你最喜爱的 library 中那些最有用独立函数,而你的项目不必携带其他未使用的代码。ES6 模块最终还是要由浏览器原生实现,但当前 Rollup 可以使你提前体验。

rollup可以帮助你从一个入口文件开始,将所有使用到的模块文件都打包到一个最终的发布文件中,所以非常适合构建一个工具库。现在让我们多加一个组件吧

// packages/Mp4Player/index.tsx
import React from 'react'

export interface Mp4PlayerProps extends React.MediaHTMLAttributes<HTMLVideoElement> {
  url: string
}

const Mp4Player = ({ url, ...props }: Mp4PlayerProps) => {
  return (
    <video src={url} autoPlay controls {...props}>
      <source src={url} type="video/mp4" />
      <track src={url} kind="captions" label="english_captions" />
    </video>
  )
}

export default Mp4Player
复制代码
// packages/Mp4Player/index.stories.tsx
import React from "react";
import Mp4Player from "./index";

export default {
  title: "Mp4Player"
};

export const WithMp4Player = () => (
  <Mp4Player
      controls={true}
      autoPlay={false}
      url="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo-720p.mp4"
      style={{
        width: "100%",
      }}
    />
);
复制代码

在packages文件夹下新建一个index.ts文件,将组件分别引入出来

// packages/index.ts
export type { TextViewProps } from './TextView';
export { default as TextView } from "./TextView";
export type { Mp4PlayerProps } from './Mp4Player';
export { default as Mp4Player } from './Mp4Player';
复制代码

值得注意的是,如果你的原项目不支持这种写法: export type { TextViewProps } from './TextView' 原因是因为typescript的版本太低了,在成本不大的情况下可以考虑升级typescript版本,成本大的话,可以用以下写法导出非default参数,都是OK的 export { TextViewProps } from './TextView'

在你的根目录下新建一个rollup.config.js文件,这个文件是一个ES6模块,会对外暴露一个对象,其中包含了一些Rollup需要的配置选项

// rollup.config.js
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import postcss from "rollup-plugin-postcss";
import json from "@rollup/plugin-json";
const { babel } = require("@rollup/plugin-babel");
const packageJson = require("./package.json");
const less = require("less");

const isProd = process.env.NODE_ENV === "production";

const babelOptions = {
  presets: ["@babel/preset-env"],
  extensions: ['.js', '.jsx', '.ts', '.tsx', '.less'],
  exclude: "**/node_modules/**"
};

const processLess = function(context, payload) {
  return new Promise((resolve, reject) => {
    less.render(
      {
        file: context
      },
      function(err, result) {
        if (!err) {
          resolve(result);
        } else {
          reject(err);
        }
      }
    );
    less.render(context, {}).then(
      function(output) {
        if (output && output.css) {
          resolve(output.css);
        } else {
          reject({});
        }
      },
      function(err) {
        reject(err);
      }
    );
  });
};

export default {
  input: "packages/index.ts",
  output: [
    {
      file: packageJson.main,
      format: "cjs"
    },
    {
      file: packageJson.module,
      format: "es"
    }
  ],
  plugins: [
    peerDepsExternal({ includeDependencies: !isProd }),
    resolve(),
    commonjs({ sourceMap: !isProd }),
    typescript({ useTsconfigDeclarationDir: true }),
    postcss({
      extract: true,
      process: processLess
    }),
    babel(babelOptions),
    json()
  ],
};
复制代码

对Rollup配置感兴趣的同学可以访问 Rollup配置

在根目录下创建一个tsconfig.json文件

{
  "compilerOptions": {
    "rootDir": ".",
    "declaration": true,
    "declarationDir": "./build/types",
    "module": "esnext",
    "target": "es5",
    "lib": ["es6", "dom", "es2016", "es2017"],
    "sourceMap": true,
    "jsx": "react",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  },
  "include": ["packages/**/*"],
  "exclude": [
    "node_modules",
    "dist",
    "docs",
    "packages/**/*.stories.tsx",
    "packages/**/*.test.tsx"
  ]
}

复制代码

为你的package.json做一下配置,如script、main等,这里把这个步骤的package贴上,相关的rollup配置放置在devDependencies中,在根目录下运行npm i即可安装完毕。

{
  "name": "jobsofferings-ui-components",
  "version": "0.0.1",
  "private": false,
  "author": "jobsofferngs",
  "description": "your own React component library using Rollup, TypeScript, Less and Storybook",
  "main": "build/index.js",
  "module": "build/index.esm.js",
  "typings": "build/types/packages/index.d.ts",
  "files": [
    "build"
  ],
  "types": "build/index.d.ts",
  "scripts": {
    "sb": "start-storybook -p 6006",
    "build": "rimraf build/* && rollup -c",
    "build:sb": "build-storybook -c .storybook -o docs"
  },
  "peerDependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "typescript": "3.8.3"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "typescript": "3.8.3"
  },
  "devDependencies": {
    "@types/react": "^16.14.14",
    "@types/react-dom": "^16.9.14",
    "@storybook/react": "^6.3.7",
    "@rollup/plugin-babel": "^5.3.0",
    "@rollup/plugin-commonjs": "^17.1.0",
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^11.2.1",
    "rollup": "^2.56.3",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-copy": "^3.4.0",
    "rollup-plugin-eslint": "^7.0.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^3.1.8",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.29.0",
    "rimraf": "^3.0.0",
    "babel-loader": "^8.2.2",
    "babel-preset-react-app": "^10.0.0",
    "less": "^4.1.2",
    "less-loader": "^5.0.0",
    "style-loader": "^1.3.0",
    "ts-loader": "^6.0.4"
  }
}
复制代码

运行成功后,可以看到一个build文件夹,下面是打包后的文件,这个时候大家可以使用npm pack制作一个临时npm包,放到自己的项目中或者在根目录建立一个测试项目来测试使用。

目前我们的项目应该是如下这个结构,已去除不会被提交到github的文件及文件夹

.storybook/
  main.js
  preview.js
build/
  types/
    ...
  index.css
  index.esm.css
  index.esm.js
  index.js
node_modules/
packages/
  TextView/
    index.less
    index.stories.tsx
    index.tsx
  Mp4Player/
    index.stories.tsx
    index.tsx
docs/
  ...
.gitignore
babel.config.js
package-lock.json
package.json
README.md
rollup.config.js
tsconfig.json
复制代码

发布NPM包

由于是React的组件库,所以我们需要将这个项目推到npm上,这样才可以让别人能够在npm上使用,记得修改一个你自己的名字,这里我使用jobsofferings-ui-components这个包名来作为发布包的name,若发布成功,则可以通过npm i jobsofferings-ui-components --save来下载到这个包,大家可以修改一个自己想要的name,记得不要存在重名冲突

{
  "name": "jobsofferings-ui-components",
  "version": "0.0.1",
  // ...其他配置
}
复制代码

这里默认大家是第一次发包,那么在根目录下,运行npm adduser,输入你的账号密码及email,如果开启了OTP验证则需要输入一次你的OTP密码。如果大家不是第一次发包,比如是跟着本次教程修改的自己的库,则可选择运行npm login命令

登录成功,输入命令npm publish进行包的发布,这样我们就能在npm库中看到我们刚才发上去的npm包了

就这?

大家可以想想我们还缺少什么,这个组件库真的到了能直接开发的情况吗?

  • 也许,我是说也许,你需要一个能够展示的静态页面,来给组件的使用者一个比较清晰的描述与支持呢?
  • 除了代码的展示,我们还能够提供什么给到使用者?markdown文档够吗?
  • 使用该库进行开发,怎么切换不同属性来让自己调试更加方便?
  • 增加代码prettier功能怎么样?
  • 一次发布,居然需要运行这么多命令?
    git add .
    git commit -m"your commit info"
    npm run build
    npm version patch # 为你的包升级版本
    npm publish
    git push
    复制代码

附加优化

prettier with lint-staged

为我们的组件库添加一些东西吧,输入以下命令来安装storybook的插件及代码prettier相关库

npm i @storybook/addon-actions@6.4.13 @storybook/addon-docs@6.4.14 @storybook/addon-info@5.3.21 @storybook/addon-knobs@6.4.0 prettier@2.2.1 lint-staged@10.5.3 husky@4.3.0 -D
复制代码

为你的package.json增加几个script和配置

{
  // ...other old
  "scripts": {
    "prettier": "prettier --write 'packages/**/*.{ts,tsx,less}'",
    "_____comment": "npm run git -- 'commit-msg'  so you can push and update by a auto way",
    "git": "git add . && git commit -m",
    "postgit": "npm run build && npm version patch && npm publish && git push",
    "sb": "start-storybook -p 6006",
    "build": "rimraf build/* && rollup -c",
    "build:sb": "build-storybook -c .storybook -o docs"
  },
  // ...other old
  "lint-staged": {
    "packages/**/*.{ts,tsx,less}": [
      "prettier --write"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}
复制代码

在根目录新建一个文件.prettierrc

{
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "none",
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "endOfLine": "auto",
  "htmlWhitespaceSensitivity": "css",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false,
  "printWidth": 80
}
复制代码
// .prettierrc的意义解释,无需复制编辑
{
  "tabWidth": 2, // 缩进空格个数
  "useTabs": false, // 使用空格代替tab缩进
  "semi": true, // 句末使用分号
  "singleQuote": false, // 是否使用单引号
  "trailingComma": "none", // 多行时尽可能打印尾随逗号
  "arrowParens": "avoid", // 单参数箭头函数参数周围使用圆括号
  "bracketSpacing": true, // 是否在对象前后添加空格
  "endOfLine": "auto", // 结束行形式
  "htmlWhitespaceSensitivity": "css", // 对HTML全局空白不敏感
  "jsxBracketSameLine": false, // 多属性html标签的‘>’折行放置
  "jsxSingleQuote": false, // jsx中使用单引号
  "printWidth": 80 // 单行长度
}
复制代码

这是prettier的配置文件,具体的配置大家可以查询链接,这里已经将部分配置的意思备注,这里参考,可自行增删修改,配置完毕后可以尝试做一次commit,观察是否配置成功。

值得注意的是,npm run prettier命令只是用来当部分文件没被prettier修改,或者prettier配置被修改后,整体做一次prettier --write的方法。这里的prettier --write会在执行git commit命令后,在 commit 作用前执行

当然,这里也提供一份当前的packages.json文件以供大家方便下载

{
  "name": "jobsofferings-ui-components",
  "version": "0.0.1",
  "private": false,
  "author": "jobsofferngs",
  "description": "your own React component library using Rollup, TypeScript, Less and Storybook",
  "main": "build/index.js",
  "module": "build/index.esm.js",
  "typings": "build/types/packages/index.d.ts",
  "files": [
    "build"
  ],
  "types": "build/index.d.ts",
  "scripts": {
    "prettier": "prettier --write 'packages/**/*.{ts,tsx,less}'",
    "_____comment": "npm run git -- 'commit-msg'  so you can push and update by a auto way",
    "git": "git add . && git commit -m",
    "postgit": "npm run build && npm version patch && npm publish && git push",
    "sb": "start-storybook -p 6006",
    "build": "rimraf build/* && rollup -c",
    "build:sb": "build-storybook -c .storybook -o docs"
  },
  "peerDependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "typescript": "3.8.3"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "typescript": "3.8.3"
  },
  "devDependencies": {
    "@rollup/plugin-babel": "^5.3.0",
    "@rollup/plugin-commonjs": "^17.1.0",
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^11.2.1",
    "@storybook/addon-actions": "^6.4.13",
    "@storybook/addon-docs": "^6.4.14",
    "@storybook/addon-info": "^5.3.21",
    "@storybook/addon-knobs": "^6.4.0",
    "@storybook/react": "^6.3.7",
    "@types/react": "^16.14.14",
    "@types/react-dom": "^16.9.14",
    "babel-loader": "^8.2.2",
    "babel-preset-react-app": "^10.0.0",
    "husky": "^4.3.0",
    "less": "^4.1.2",
    "less-loader": "^5.0.0",
    "lint-staged": "^10.5.3",
    "prettier": "^2.2.1",
    "rimraf": "^3.0.0",
    "rollup": "^2.56.3",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-copy": "^3.4.0",
    "rollup-plugin-eslint": "^7.0.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^3.1.8",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.29.0",
    "style-loader": "^1.3.0",
    "ts-loader": "^6.0.4"
  },
  "lint-staged": {
    "packages/**/*.{ts,tsx,less}": [
      "prettier --write"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}
复制代码
开发命令

在这里提供了一个通用的命令脚本,大家可以尝试运行npm run git -- "feat: add dev script"来进行一键到位的commit、build、publish、push过程,也可以根据自己的所需来进行相关的修改

addon-actions & addon-knobs

在.storybook文件夹下新增一个文件addons.js来导入这两个插件

// .storybook/addons.js
import '@storybook/addon-actions/register';
import '@storybook/addon-knobs';
复制代码

然后我们在main.js的addons中注册一下knobs及docs插件

// .storybook/main.js
// 这是storybook的配置文件,loader、entry file等都会在此进行配置
const path = require("path");

module.exports = {
  // storybook文档的目标文件
  stories: ["../packages/**/*.stories.(tsx|mdx)"],
  // 插件依赖
  addons: ['@storybook/addon-knobs', '@storybook/addon-docs'],
  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.less$/,
      use: ["style-loader", "css-loader", "less-loader"],
      include: path.resolve(__dirname, "../")
    });
    config.module.rules.push({
      test: /\.(ts|tsx)$/,
      loader: require.resolve("babel-loader"),
      options: {
        presets: [["react-app", { flow: false, typescript: true }]]
      }
    });
    config.resolve.extensions.push(".ts", ".tsx");

    return config;
  }
};
复制代码

最后修改一下我们的preview.js文件

// .storybook/preview.js
import { addDecorator } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
 
addDecorator(
  withInfo({
    header: false,
    inline: true
  })
); 
复制代码

这三个storybook配置文件修改完毕后,我们就可以为我们的组件文档添加一些不一样的东西了!

我们开始对 TextView 做一些修改,现在我们修改一下packages/TextView/index.stories.tsx文件

// packages/TextView/index.stories.tsx
import React from "react";
import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
import { boolean, number, select, text } from '@storybook/addon-knobs';
import TextView from "./index";

export default {
  title: "TextView"
};

export const WithTextView = () => (
  <TextView>This is my test component</TextView>
);

// 使用@storybook/addon-actions记录事件
export const TestAddonActions = () => (
  <Button
    onClick={action('clicked')}
  >
    <span role="img" aria-label="so cool">
      😀 😎 👍 💯
    </span>
  </Button>
);

// 使用@storybook/addon-knobs动态修改属性
export const TestAddonKnobs = () => (
  <TextView
    children={text('children', 'children_value')}
  />
);

// 更多的示例
export const TestAddonKnobsMore = () => (
  <input
    onClick={action('onClick')}
    onFocus={action('onFocus')}
    placeholder={text('placeholder', 'placeholder_value')}
    type={select('type', { password: "password", number: 'number', text: 'text' }, 'number')}
    disabled={boolean('disabled', false)}
    size={number('size', 24)}
  />
);
复制代码

预览后我们可以看到,点击TestAddonActions组件按钮,能够打印出点按的事件参数,而切换到TestAddonKnobs组件,可以在下方的Knobs项中,修改children的值,达到动态更新的目的。

你可以用这种方式来测试你的组件,如输入超长字符或空字符等,查看你的组件UI、交互或者内置请求是否正常工作 提供了一个简单的 TestAddonKnobsMore 组件以供举例

虽然目前可以看到,在组件的下方有Story Source可以明确地看到当前展示的组件的参数使用方式,但是,我们能不能有一种方案,能够既让使用者看到调用的方式,还可以使用markdown文档来进行组件的历史、封装背景、使用技术等信息的描述?

MDX 来了

MDX是一种能够在 Markdown 文档中写 JSX 的格式,使用这种文件来编写stories,能够增强我们组件文档的灵活性,而写法本身其实和 Markdown非常相似,极大地降低了我们的学习难度

Markdown 是什么? MDX 官方示例

我们在Mp4Player文件夹下新增一个index.stories.mdx文件

<!-- packages/Mp4Player/index.stories.mdx -->
import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks";
import Mp4Player from "./index.tsx";

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

## Mp4Player

### Markdown原功能

# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

一级标题
=================
二级标题
-----------------

### 基础使用如下

<Canvas>
  <Story name="Mp4Player">
    <Mp4Player
      controls={true}
      autoPlay={false}
      url="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo-720p.mp4"
      style={{
        width: "100%",
      }}
    />
  </Story>
</Canvas>
复制代码

在预览中我们可以看到,除了可以引入tsx文件外,正常的Markdown文档都是可以正常使用并解析的,这对我们的文档开发体验、用户组件使用体验都是一个极高的提升,搭配storybook自带的HMR功能,简直是书写文档的一把好手。

组件文档页面

最后,使用以下命令将你的当前暂存提交到你的github上吧,如果出现了上传失败,如Permission denied等错误,可简单参考参考链接

git add .
git commit -m"feat: init my ui components"
git push
复制代码

现在回到我们的github仓库,点击setting,移到下方找到github page

修改分支为branch,修改文件夹为docs,点击save,静候几分钟后,你就能看到以https://yourname.github.io打头的域名能够访问成功,并展示之前的storybook静态页面了。

值得注意的是,如果您不需要这个demo网站,可以将docs文件忽略,移入.gitignore中

尾声

至此,我们已经完成了一个简单的UI组件库,这个组件库:

  1. 支持React、Less、TypeScript
  2. 支持Rollup打包
  3. 支持storybook作为文档工具
  4. 支持组件代码修改后 hot reload (使用storybook文档工具开发组件的情况下)
  5. 支持编写XMD文档
  6. 支持发布npm
  7. 支持 prettier with lint-staged
  8. 支持输出storybook静态文件
  9. 可放置于github page上进行查看
  10. 通过npm i ${your-components-name} --save方式下载。

相关链接:

github地址 组件库预览地址 storybook文档 Rollup配置 lerna中文文档 prettier配置文档 Prettier配置指南 — 掘金 Git Permission denied 处理 Markdown 是什么? MDX 官方示例

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改