React UI组件库完全开发指南

1,755 阅读5分钟

前言

    时至今日,前端组件库/函数库开发是前端进阶必备技能之一,也出现了诸如Ant Design、ElementUI、iview等广为人知的UI框架。不得不说,UI框架确实极大地简化了开发人员的前端工作。而且,很多大中型公司已具备组件库或者有定制化组件库的开发需求。本文讲述如何从零搭建一个React组件库,希望对感兴趣的小伙伴有所帮助!

正文

1 构建工具选型

    关于构建工具,国内开发的共识是:开发组件库采用webpack,函数库首选rollup。热度比较高的组件库 Ant DesignElementUIiview 也都选择webpack作为构建工具。博主利用webpack做了一番尝试,发现以下问题:

  • webpack5开发的组件库无法在webpack4环境下(如cra,umijs等)使用,会出现类似 __webpack_modules__[moduleId] is undefined 的问题, 详见 Issue11277 ,根据成员回复可知是webpack4和webpack5运行时不兼容。

  • webpack4不支持打包为es module,为了便于tree-shaking,需要额外处理打包文件目录与开发目录保持一致,利用babel-import-plugin插件可以实现;webpack5虽然支持es module,但目前BUG很多,可以查看 webpack仓库

  • webpack4不支持最新的css-loader、postcss-loader、less-loader、sass-loader、style-loader,会出现类似 this.getOptions is not a function For xxx的错误,因此需要降级相关loader版本。

    基于以上问题,博主也尝试了rollup,发现除了rollup相关插件star比较少外,其他均满足需求。原生支持打包es module,有类似output.preserveModules的选项可以保证输出目录和开发目录保持一致,而且打包出的文件在umijs和cra下表现良好。因此,本文采用Rollup作为组件库的构建工具。

2 实现步骤

2.1 初始化项目

    首先新建目录并执行npm init初始化项目,接下来依次为项目添加依赖:

(1) rollup

    执行yarn add -D rollup,并添加启动脚本"build": "rollup -c" 到package.json scripts下,并添加rollup配置文件rollup.config.js到项目根目录下。

(2) typesript

    鉴于typescript已经广泛应用于react、angular开发,本文采用typescript作为项目开发语言,同时利用typescript输出声明文件。     依次执行yarn add -D typescript;yarn tsc --init生成tsconfig.json配置文件,将下面内容替换配置文件内容:

{
  "compilerOptions": {
      "strict": true,
      "target": "es6",
      "lib": ["ESNext", "DOM", "WebWorker"],
      "declaration": true,
      "outDir": "./dist",
      "baseUrl": "./",
      "allowJs": true,
      "jsx": "react",
      "moduleResolution": "node",
      "isolatedModules": true,
      "esModuleInterop": true,
      "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

    相关配置可以到官网自行查看

(3)添加rollup常用插件,添加es module输出配置

import nodeResolvePlugin from '@rollup/plugin-node-resolve'
import stylesPlugin from "rollup-plugin-styles"
import jsonPlugin from '@rollup/plugin-json'
import typescriptPlugin from '@rollup/plugin-typescript'
import pkg from './package.json'

const entry = 'src/index.ts'
const peerDeps = Object.keys(pkg.peerDependencies)

const esModule = {
    input: entry,
    external: peerDeps,
    plugins: [
        jsonPlugin(),
        stylesPlugin({
            include: 'src/**/*',
            extensions: ['.css'],
            autoModules: true,
        }),
        typescriptPlugin(),
        nodeResolvePlugin(),
    ].filter(Boolean),
    output: {
        format: 'es',
        dir: 'es',
        preserveModules: true,
        preserveModulesRoot: 'src',
    },
}

export default [ esModule]

    如你所见,配置相当简单

(4)添加react, react-dom,并配置react, react-dom为peerDependencies

    执行yarn add -D react react-dom @types/react @types/react-dom,并在pakage.json中添加

"peerDependencies": {
    "react": ">=16.8",
    "react-dom": ">=16.8"
  }

    注意,第三步已经将peerDependencies下的依赖设置为external dependencies

2.2 编写组件

    本文编写了两个组件Comp1、Comp2如下:

// src/componets/Comp1/index.tsx
import React from 'react'
import styles from './index.module.css'

const Comp1 = () => {
    return <button className={styles.comp1}>按钮一</button>
}

export default Comp1

// src/componets/Comp1/index.module.css
.comp1{
    padding: 10px 15px;
    background-color: red;
    color: white;
}

// src/componets/Comp2/index.tsx
import React from 'react'
import styles from './index.module.css'

const Comp2 = () => {
    return <button className={styles.comp1}>按钮二</button>
}

export default Comp2

// src/componets/Comp2/index.module.css
.comp1{
    padding: 10px 15px;
    background-color: red;
    color: white;
}

// src/index.ts
export { default as Comp1 } from './components/Comp1'
export { default as Comp2 } from './components/Comp2'

2.3 测试

    执行yarn build进行项目构建,在package.json中加入模块化入口文件"module": "es/index.js"。执行yarn link将组件库链接到全局仓库下,新建cra项目,执行yarn link react-ui-lib链接该库,接下来编写测试用例。

(1)全局引入

import { Comp1 } from  'react-ui-lib'
// import Comp1 from  'react-ui-lib/es/components/Comp1'

function App() {
  return <Comp1></Comp1>
}

export default App;

得到效果图如下: 输入图片说明 从上图可以看出,2个组件都被引入了!

(2)局部引入     改为如下代码,重启cra应用,一定要重启才能看到效果!!!

// import { Comp1 } from  'react-ui-lib'
import Comp1 from  'react-ui-lib/es/components/Comp1'

function App() {
  return <Comp1></Comp1>
}

export default App;

得到效果图如下: 输入图片说明 从上图可以看出,这次只引入了Comp1

3 storybook

3.1 storybook配置

    为了便于开发测试,本文案例加配了storybook,项目根目录执行npx storybook init即可。修改根目录.storybook下main.js,因为storybook默认基于webpack4开发,因此需要安装低版本css-loader处理我们的css,这里使用的是 css-loader": "^5.2.7", main.js如下:

const path = require('path')
module.exports = {
    stories: [
        {
            directory: './src',
            titlePrefix: 'react-ui-lib',
            files: '*.stories.*',
        },
    ],
    addons: [
        '@storybook/addon-links',
        '@storybook/addon-essentials',
    ],
    framework: '@storybook/react',
    webpackFinal: async (config) => {
        return {
            ...config,
            module: { ...config.module, rules: [
                {
                    test: /\.(js|jsx|ts|tsx)$/,
                    exclude: /node_modules/,
                    use: {
                      loader: require.resolve('babel-loader'),
                      options: {
                        presets: [
                            '@babel/preset-env',
                            '@babel/preset-typescript',
                            '@babel/preset-react',
                        ],
                      },
                    },
                },
                {
                    test: /\.css$/,
                    include: path.resolve(__dirname, '../src'),
                    use: [
                        'style-loader',
                        {
                            loader: 'css-loader',
                            options: {
                                modules: {
                                    auto: /\.module\.css$/,
                                    localIdentName: '[local]--[hash:base64:5]',
                                },
                            },
                        },
                    ],
                },
            ]},
        }
    },
}

    这里将story目录设置到了.storybook/src下,减少rollup打包排除配置!同时配置了 babel-loader(storybook官方处理不了tsx!详见Issue 1493),css-loader。

3.2 运行storybook

    执行脚本yarn storybook,则会看到运行结果: 输入图片说明

4 源码贡献

    欢迎访问源码,欢迎star,issue!

5 国际惯例

    欢迎访问原文链接