使用react搭建组件库:react+typescript+storybook

前期准备

1. 初始化项目

npx create-react-app react-components --template typescript

2. 安装依赖

  • 使用哪种打包方案:webpack/rollup
**webpack**
* 代码分割:可以将打包后的代码分割成多个*.chunk.js,这些分块可以在用户使用过程中按需加载,这意味着用户可以有更好的交互体验。
* 静态资源导入:图片、CSS 等静态资源可以直接导入到你的 app 中,就和其它的模块、节点一样能够进行依赖管理。因此,我们再也不用小心翼翼地将各个静态文件放在特定的文件夹中,然后再去用脚本给文件 URL 加上哈希串了。
**rollup**
* Tree Shaking:这是rollup提出的一个特性,利用的es6模块的静态特性对导入的模块进行分析,只抽取使用到的方法,从而减小打包体积。
* 配置使用简便,生成的代码相对于Webpack更简洁。
* 可以指定生成生产中使用的各种不同的模块(amd,commonjs,es,umd)。
**异同**
* Webpack更适用于我们实际业务项目的打包,将代码和静态资源进行打包、分割、动态引入无需我们手动处理。
* rollup小巧使用配置方便可以对lib类库进行打包,代码运行效率更高。
* Webpack打包配置相对繁琐,打包出的代码体积相对较大,不同依赖之间执行会有依赖查找的情况,效率不如rollup。
* 简单来说:对于日常单页应用来说有代码和各种静态资源Webpack更适合,对于一些纯js/ts类库项目rollup更适合,rollup打包输入可以指定不同的格式(amd,commonjs,es,umd)应对各个场景引入使用。
  最终选定使用rollup进行打包,并安装rollup以及相关依赖
  `yarn add -D rollup rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-replace  @rollup/plugin-image rollup-plugin-terser rollup-plugin-postcss postcss cssnano postcss-nested postcss-simple-vars`
  
  安装好rollup后还需要配置babel,也需要安装babel配置需要的相关插件依赖
  `yarn add -D @babel/cli @babel/core @babel/plugin-external-helpers @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-proposal-object-rest-spread @babel/plugin-transform-react-jsx @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/preset-typescript`
  • storybook:用于组件库组件预览、组件测试和文档说明
    快速搭建 storybook react环境
    npx -p @storybook/cli sb init --type react
    Component Story Format (CSF,用于组件测试)
    为了支持typescript,还需要单独安装相关插件依赖
    yarn add -D @storybook/preset-create-react-app @storybook/react

  • eslint和prettier:用于规范代码
    代码检查借助Prettier以及ESLint的扩展,eslint-config-prettier将关闭所有不必要的或可能与Prettier冲突的规则。eslint-plugin-prettier则是添加Prettier格式设置规则的插件。
    yarn add -D eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react

  • husky和lint staged:用于规范commit
    Husky 与 Lint Staged来确保每次提交代码的正确性
    yarn add -D husky lint-staged

  • plop:用于组件模板创建
    With plop, you have your "best practice" method of creating any given pattern in CODE. Code that can easily be run from the terminal by typing plop. Not only does this save you from hunting around in your codebase for the right files to copy, but it also turns "the right way" into "the easiest way" to make new files.
    yarn add -D plop

3. 配置环境

  • 打包配置
    在根目录下创建rollup.config.js
// rollup.config.js
// Rollup plugins
// babel插件用于处理es6代码的转换,使转换出来的代码可以用于不支持es6的环境使用
import babel from 'rollup-plugin-babel';
// resolve将我们编写的源码与依赖的第三方库进行合并
import resolve from 'rollup-plugin-node-resolve';
// 解决rollup.js无法识别CommonJS模块
import commonjs from 'rollup-plugin-commonjs';
// 全局替换变量比如process.env
import replace from 'rollup-plugin-replace';
// 使rollup可以使用postCss处理样式文件less、css等
import postcss from 'rollup-plugin-postcss';
// 可以处理组件中import图片的方式,将图片转换成base64格式,但会增加打包体积,适用于小图标
import image from '@rollup/plugin-image';
// 压缩打包代码
import { terser } from 'rollup-plugin-terser';
// PostCSS plugins
// 处理css定义的变量
import simplevars from 'postcss-simple-vars';
// 处理less嵌套样式写法
import nested from 'postcss-nested';
// 可以提前适用最新css特性
import postcssPresetEnv from 'postcss-preset-env';
// css代码压缩
import cssnano from 'cssnano';
// 支持typescript
import typescript from 'rollup-plugin-typescript2';
// 用于打包生成*.d.ts文件
import dts from 'rollup-plugin-dts';
// 引入package
import pkg from './package.json';

const env = process.env.NODE_ENV;

const config = [
  {
    // 入口文件我这里在components下统一导出所有自定义的组件
    input: 'src/components/index.tsx',
    // 输出文件夹,可以是个数组输出不同格式(umd,cjs,es...)通过env是否是生产环境打包来决定文件命名是否是.min
    output: [
      {
        file: `dist/index.umd${env === 'production' ? '.min' : ''}.js`,
        format: 'umd',
        name: 'wcpnts',
      },
      {
        file: `dist/index.esm${env === 'production' ? '.min' : ''}.js`,
        format: 'esm',
      },
      {
        file: `lib/index${env === 'production' ? '.min' : ''}.js`,
        format: 'cjs',
      },
    ],
    // 注入全局变量比如jQuery的$这里只是尝试 并未启用
    // globals: {
    //   react: 'React',                                         // 这跟external 是配套使用的,指明global.React即是外部依赖react
    //   antd: 'antd'
    // },
    // 自定义警告事件,这里由于会报THIS_IS_UNDEFINED警告,这里手动过滤掉
    onwarn: function (warning) {
      if (warning.code === 'THIS_IS_UNDEFINED') {
        return;
      }
    },
    // 将模块视为外部模块,不会打包在库中
    external: ['react', 'react-dom'],
    // 插件
    plugins: [
      typescript(),
      image(),
      postcss({
        plugins: [
          simplevars(),
          nested(),
          // cssnext({ warnForDuplicates: false, }),
          postcssPresetEnv(),
          cssnano(),
        ],
        // 处理.css和.less文件
        extensions: ['.css', '.less'],
      }),
      // 告诉 Rollup 如何查找外部模块并安装它
      resolve(),
      // babel处理不包含node_modules文件的所有js
      babel({
        exclude: 'node_modules/**',
        runtimeHelpers: true,
        plugins: ['@babel/plugin-external-helpers'],
        extensions: ['.js', '.ts', 'tsx'],
      }),
      // 将 CommonJS 转换成 ES2015 模块
      // 这里有些引入使用某个库的api但报未导出该api通过namedExports来手动导出
      commonjs({
        namedExports: {
          'node_modules/react-is/index.js': ['isFragment'],
          'node_modules/react/index.js': [
            'Fragment',
            'cloneElement',
            'isValidElement',
            'Children',
            'createContext',
            'Component',
            'useRef',
            'useImperativeHandle',
            'forwardRef',
            'useState',
            'useEffect',
            'useMemo',
          ],
          'node_modules/react-dom/index.js': [
            'render',
            'unmountComponentAtNode',
            'findDOMNode',
          ],
          'node_modules/gojs/release/go.js': [
            'Diagram',
            'GraphLinksModel',
            'Overview',
            'Spot',
          ],
        },
      }),
      // 全局替换NODE_ENV,exclude表示不包含某些文件夹下的文件
      replace({
        // exclude: 'node_modules/**',
        'process.env.NODE_ENV': JSON.stringify(env || 'development'),
      }),
      // 生产环境执行terser压缩代码
      env === 'production' && terser(),
    ],
  },
  {
    // 入口文件我这里在components下统一导出所有自定义的组件
    input: 'src/components/index.tsx',
    // 输出文件夹,可以是个数组输出不同格式(umd,cjs,es...)通过env是否是生产环境打包来决定文件命名是否是.min
    output: [
      {
        file: `${pkg.types}`,
        format: 'esm',
      },
    ],
    plugins: [
      dts(),
      postcss({
        plugins: [
          simplevars(),
          nested(),
          // cssnext({ warnForDuplicates: false, }),
          postcssPresetEnv(),
          cssnano(),
        ],
        // 处理.css和.less文件
        extensions: ['.css', '.less'],
      }),
    ],
  },
];

export default config;

  在package.json中添加一下代码
{
"main": "lib/index.js",
  "module": "dist/index.esm.js",
  "browser": "dist/index.umd.js",
  "types": "lib/index.d.ts",
"scripts": {
    "rollup-build": "cross-env BABEL_ENV=rollup rollup -c",
    "rollup-production-build": "cross-env NODE_ENV=production rollup -c",
  },
"files": [
    "dist",
    "lib"
  ],
}
  • storybook配置
    安装成功后,会自动在package.json 中添加下面语句:
"scripts": {
...
      "storybook": "start-storybook -p 6006",
      "build-storybook": "build-storybook",
}
  为了支持tsx文件,需要在.storybook/main.js中增加配置项和根据自己项目进行部分调整(.storybook目录是运行完storybook init后自动创建出来的)
module.exports = {
  "stories": [
    "../src/stories/*.stories.mdx",
    "../src/stories/*.stories.tsx"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/preset-create-react-app",  // 用来支持tsx文件
  ]
}
  • eslint配置
  • prettier配置
  • husky配置:在package.json 中添加下面语句:
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
  • lint staged配置:在package.json 中添加下面语句:
"lint-staged": {
    "src/**/*.{css,scss,less,json,html,md,markdown}": [
      "prettier --write",
      "git add"
    ],
    "src/**/*.{js,jsx,mjs,ts,tsx}": [
      "prettier --write",
      "eslint --fix",
      "git add"
    ]
  }
  • plop配置:创建plopfile.js,并创建模板文件(模板文件和要生成的文件路由和名字都可以自定义)
module.exports = function (plop) {
  // create your generators here
  plop.setGenerator('basics', {
    description: 'this is a component',
    prompts: [  // 提示问题,可以是多问题,以数组的形式表示
      {  // 可以根据输入的name动态创建组件的相关文件
        type: 'input',
        name: 'name',
        message: 'please input component name',
      },
    ],
    actions: [  // 命令完成对应的操作,可以是多操作,以数组的形式表示
      {
        type: 'add', // 创建文件
        path: 'src/components/{{name}}/index.tsx',  // 生成的文件
        templateFile: 'templates/index.tsx',  // 模板文件
      },
      {
        type: 'add',
        path: 'src/components/{{name}}/index.less',
        templateFile: 'templates/index.less',
      },
      {
        type: 'add',
        path: 'src/stories/{{name}}.stories.tsx',
        templateFile: 'templates/index.stories.tsx',
      },
    ],
  });
};
  • 我的模板文件

  • 执行plop命令的过程,根据提示问题输入组件名称Modal

  • 命令执行完成后,plop会自动创建在配置中设定好的目录

组件开发

1. 组件设计思路及规范

  • 初始化组件:创建组件模板plop
  • 打包版本号升级

2. 项目目录设计

所有组件都放到components下,每个组件一个文件夹,首字母大写,在components下创建一个index.tsx文件,用于导出所有组件

3. 组件编写

PR 标题规则:[ bug fix / breaking change / new feature ] 组件名字:修改内容描述

前面方括号用来区分 PR / issue 的类型:bug fix - 组件 bug 修复;breaking change - 不兼容的改动;new feature - 新功能
修改内容尽可能言简意赅,总结 PR 的改动或者描述 issue
描述请用中文
组件名字请用英文,首字母大写

4. 组件测试

组件维护

1. 组件发布

// 将npm源设置为你自己私有库的地址
npm config set registry ****
// 登录npm私有库,根据提示输入用户名、密码、邮箱,如果没有账号需要先在私有库上添加账号
npm login
// 发布包到自己的私有库上
npm publish

2. 组件说明文档

完善CHANGELOG.md

3. 组件变更记录

安装conventional-changelog-cli和cz-conventional-changelog依赖,可以根据commit的信息自动生成CHANGELOG.md
在package.json中添加以下配置

"scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
  },

遇到的问题

运行storybook时样式文件没有生效

TODO

发布storybook,支持组件在线预览

posted @ 2020-12-28 09:38  shellon  阅读(868)  评论(0编辑  收藏  举报