前言
时至今日,前端组件库/函数库开发是前端进阶必备技能之一,也出现了诸如Ant Design、ElementUI、iview等广为人知的UI框架。不得不说,UI框架确实极大地简化了开发人员的前端工作。而且,很多大中型公司已具备组件库或者有定制化组件库的开发需求。本文讲述如何从零搭建一个React组件库,希望对感兴趣的小伙伴有所帮助!
正文
1 构建工具选型
关于构建工具,国内开发的共识是:开发组件库采用webpack,函数库首选rollup。热度比较高的组件库 Ant Design、ElementUI、iview 也都选择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 国际惯例
欢迎访问原文链接