前言
俗话说:“麻雀虽小,五脏俱全”,搭建一个组件库,知之责难,行之不易,波及到的技术方方面面,犹如海面惊涛骇浪,实则暗礁险滩,处处惊险~
目前团队内曾经有较为成熟的 Vue 技术栈的 NutUI 组件库 [1] 和 React 技术栈的 yep-react 组件库[2]。然而这些组件库大都从零开始搭建,包含 Webpack 的繁冗配置,Markdown 文件转 Vue 文件性能的开发,单元测试性能的开发、按需加载的 Babel 插件开发等等,实现整个组件库我的项目实属不易,也是一个盛大的工程。如果咱们想疾速搭建一个组件库,大可不必如此消耗精力,能够借助业内业余的相干库,通过拼装调试,疾速实现一个组件库。
本篇文章就来给大家介绍一下应用 create-react-app 脚手架、docz 文档生成器、node-sass、联合 Netlify 部署我的项目的整个开发组件库的流程,本着包教包会,不会没有退费的准则,来一场手摸手式教学,话不多说,让咱们进入正题:
首先看一下组件库的最终成果:
本文将从以下步骤介绍如何搭建一个 React 组件库:
一、构建本地开发环境
开发一个组件库的首要步骤就是调试本地 React 环境,咱们间接应用 React 官网脚手架 create-react-app
,能够省去从底层配置 Webpack+TypeScript+React 的残害:
1、应用 create-react-app 初始化脚手架,并且装置 TypeScript
npx create-react-app myapp --typescript
留神应用 node 为较高版本 >10.15.0
2、配置 eslint 进行格式化
因为装置最新的 create-react-app 联合 VScode 编辑器即可反对 eslit,然而须要在我的项目根目录中要增加 .env 这个配置文件,设置 EXTEND_ESLINT=true
这样才会启用 eslint 检测,留神要 重启 vscode
3、组件库系统文件构造
新建 styles 文件夹,蕴含了根本款式文件,构造如下:
|-styles
| |-variables.scss // 各种变量以及可配置设置
| |-mixins.scss // 全局 mixins
| |-index.scss // 引入全副的 scss 文件,向外抛出款式入口
|-components
| |-Button
| |-button.scss // 组件的独自款式
| |-button.mdx // 组件的文档
| |-button.tsx // 组件的外围代码
| |-button.test.tsx // 组件的单元测试文件
| |-index.tsx // 组件对外入口
4、装置 node-sass 处理器
装置 node-sass 用来编译 SCSS 款式文件:npm i node-sass -D
这样最根本的 react 开发环境就实现了,能够开心的开发组件了。
二、组件库打包编译
本地调试完组件库之后,须要打包压缩编译代码,供其余用户应用,这里咱们用的 TypeScript 编写的代码,所以应用 Typescript 来编译我的项目:
首先在每个组件中新建 index.tsx 文件:
import Button from './button'
export default Button
批改 index.tsx 文件,导入导出各个模块
export {default as Button} from './components/Button'
在根目录新建 tsconfig.build.json,对 .tsx 文件进行编译:
{
"compilerOptions": {
"outDir": "dist",// 生成目录
"module": "esnext",// 格局
"target": "es5",// 版本
"declaration": true,// 为每一个 ts 文件生成 .d.ts 文件
"jsx": "react",
"moduleResolution":"Node",// 规定寻找引入文件的门路为 node 规范
"allowSyntheticDefaultImports": true,
},
"include": [// 要编译哪些文件
"src"
],
"exclude": [// 排除不须要编译的文件
"src/**/*.test.tsx",
"src/**/*.stories.tsx",
"src/setupTests.ts",
]
}
对于款式文件,应用 node-sass 编译 SCSS,抽取所有 SCSS 文件生成 CSS 文件:
"script":{"build-css": "node-sass ./src/styles/index.scss ./dist/index.css",}
并且批改 build 命令:
"script":{
"clean": "rimraf ./dist",// 跨平台的兼容
"build": "npm run clean && npm run build-ts && npm run build-css",
}
这样,执行 npm run build
之后,就能够生成对应的组件 JS 和 CSS 文件,为前面使用者按需加载和部署到 npm 上提供筹备。
三、本地调试组件库
本地实现组件库的开发之后,在公布到 npm 前,须要先在本地调试,防止带着问题上传到 npm 上。这时就须要应用 npm link 出马了。
什么是 npm link
在本地开发 npm 模块的时候,咱们能够应用 npm link 命令,将 npm 模块链接到对应的运行我的项目中去,不便地对模块进行调试和测试。
应用办法
假如组件库是 reactui 文件夹,要在本地的 demo 我的项目中应用组件。则在组件库中(要被 link 的中央)执行 npm link
,则生成从本机的 node_modules/reactui
到 组件库的门路 / reactui
中的映射关系。
而后在要应用组件库的文件夹 demo 中执行 npm link reactui
则生成以下对应链条:
在要应用组件的文件夹 demo 中 -[映射到]—> 本机的 node_modules/reactui
—[映射到]-> 开发组件库 reactui 的文件夹 /reactui
须要批改组件库的 package.json 文件来设置入口:
{
"name": "reactui",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
}
而后在要应用组件的 demo 我的项目的依赖中增加:
"dependencies":{"reactui":"0.0.1"}
留神,此时并不必装置依赖,之所以写上该依赖,是为了不便在我的项目中应用的时候能够有代码提醒性能。
而后在 demo 我的项目中应用:
import {Button} from 'reactui'
在 index.tsx 中引入 CSS 文件
import 'reactui/build/index.css'
正当认为功败垂成的时候,上面这个报错犹如一盆冷水从天而降:
通过各种问题排查,在 react 官方网站[3] 上查到以下说法:
???? Do not call Hooks in class components.
???? Do not call in event handlers.
???? Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.
说的很明确:
起因 1: React 和 React DOM 的版本不一样的问题
起因 2: 可能突破了 Hooks 的规定
起因 3: 在同一个我的项目中应用了多个版本的 React
官网很贴心,给出了解决办法:
This problem can also come up when you use npm link or an equivalent. In that case, your bundler might“see”two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.
核心思想在组件库中应用 npm link
形式,引到 demo 我的项目中的 react;所以在组件库中执行:npm link ../demo/node_modules/react
具体步骤如下:
- 在代码库 reactui 中执行
npm link
- 在代码库 reactui 中执行
npm link ../../demo/node_modules/react
- 在我的项目 demo 中执行
npm link reactui
如此能够解决下面 react 抵触问题;于是能够在本地一边高兴的调试组件库,一边高兴的在应用组件的我的项目中看到最终成果了。
四、组件库公布到 npm
该过程肯定要留神应用的是 npm 源!![十分重要]
首先确定本人是否曾经登录了 npm:
npm adduser
// 填入用户名;明码;email
npm whoami // 查看以后登录名
批改组件库的 package.json,留神 files 配置;以及 dependencies 文件的化简:
react 依赖本来是要放在 dependencies 中的,然而可能会和用户装置的 react 版本抵触,所以放在了 devDependencies 中,然而这样话用户如果没有装置 react 则无奈应用组件库,所以要在 peerDependencies 中定义前置依赖 peerDependencies,通知用户 react 和 react-dom 是必要的前置依赖:
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [ // 把哪些文件上传到 npm
"dist"
],
"dependencies": { // 执行 npm i 的时候会装置这些依赖到 node_modules 中
"axios": "^0.19.1",// 发送申请
"classnames": "^2.2.6",//
"react-transition-group": "^4.3.0"
},
"peerDependencies": { // 重要!!,揭示使用者,组件库的外围依赖,必须先装置这些依赖能力应用
"react": ">=16.8.0", // 在 16.8 之后 才引入了 hooks
"react-dom": ">=16.8.0"
}
好了,整个组件库通过上述过程,基本上各个性能曾经有了,提及一句:因为组件库应用的是 create-react-app 脚手架,最新的版本曾经集成了单元测试性能。还有配置 husky 等标准代码提交,在这里不在做赘述,读者能够自行配置。
五、生成阐明文档
目前生成阐明文档较好的工具有 storybook[4]、docz[5] 等工具,两者都是很优良的文档生成工具,然而尺有所短,寸有所长,通过认真调研比拟,最终抉择了 docz。
工具名称 | 区别一 | 区别二 |
---|---|---|
storybook | 应用特有的 API 开发文档阐明,能够引入 markdown 文件 | 生成文档的界面带有 storybook 的痕迹较多一些 |
docz | 完满的联合了 react 和 markdown 语法开发文档 | 生成的文档界面是惯例的文档界面 |
1、确定选型
1)storybook 的罕用编译文档标准绝对 docz 而言,略有繁琐
storybook 的编译文档标准如下所示:
// 省略 import 引入的代码
storiesOf('Buttons', module)
.addDecorator(storyFn => <div style={{ textAlign: 'center'}}>{storyFn()}</div>)
.add('with text', () => (<Button onClick={action('clicked')}>Hello Button111</Button>
),{notes:{markdown} // 将会渲染 markdown 内容
})
比照 docz 的开发文档:
# Button 组件
应用形式如下所示:import {Playground, Props} from 'docz';
import Button from './index.tsx';
## 按钮组件
<Playground>
<Button btnWidth="100"> 我是按钮 </Button>
</Playground>
** 根本属性 **
| 属性名称 | 阐明 | 默认值 |
|--|--|--|
|btnType | 按钮类型 |--|
家喻户晓,Markdown 是一种轻量级标记语言,它容许人们应用易读易写的纯文本格式编写文档。团队成员在开发文档时,纯熟应用 markdown 语法,开发 docz 文档的 mdx 文件,联合了 Markdown 和 React 语法,相比 storybook 要应用很多的 API 来编写文档的形式,无疑缩小了很多的学习 storybook 语法的老本。
2)docz 生成的文档款式更加合乎集体审美
storybook 生成的文档款式,带有 storybook 的痕迹更为严重一些,其生成文档界面如下所示:
docz 生成的文档图如下所示:
由上图比照能够看出,docz 生成的界面更加简介,较为惯例。
综上,联合默认文档开发习惯和界面风格,我抉择了 docz,当然仁者见仁、智者见智,读者也能够应用同为优良的 storybook 尝试,这都不是事儿~
2、应用 docz 开发
确定了 docz 进行开发后,依据官网介绍,在 create-react-app 生成的组件库中进行了装置配置:
npm install docz
装置胜利后,就会向 package.json 文件中增加如下配置
{
"scripts": {
"docz:dev": "docz dev",
"docz:build": "docz build",
"docz:serve": "docz build && docz serve"
}
}
这时还须要在我的项目的根目录下新建 doczrc.js 文件,对 docz 进行配置:
export default {files: ['./src/components/**/*.mdx','./src/*.mdx'],
dest: 'docsite', // 打包 docz 文档到哪个文件夹下
title: '组件库左上角题目', // 设置文档的题目
typescript: true, // 反对 typescript 语法
themesDir: 'theme', // 主题款式放在哪个文件夹下,前面会讲
menu: ['疾速上手', '业务组件'] // 生成文档的左侧菜单分类
}
其中 files 规定了 docz 去对哪些文件进行编译生成文档,如果不做限度,会搜寻我的项目中所有的 md、mdx 为后缀的文件生成文档,因而我在该文件中做了范畴限度,防止一些 README.md
文件也被生成到文档中。
此外还须要留神到两点:
1、menu: ['疾速上手', '业务组件']
对应着组件库左侧的菜单栏分类,比方在 mdx 文档中在最下面设置组件所属的菜单 menu: 业务组件
, 则 Button 组件属于 “ 业务组件 ” 的分类:
---
name: Button
route: /button
menu: 业务组件
---
在 src 中新建欢送页,路由为跟门路,所属菜单为“疾速上手”;
---
name: 疾速上手
route: /
---
执行 npm run docz:dev
,就能够关上
介绍到这里,预计有小伙伴会有疑难了,这样生成的网站千篇一律,是否得心应手的自定义网站的款式和性能呢?当初我也有这种疑难,通过屡次尝试,皇天不负苦心人,终于摸索出如下办法:
1、批改 docz 文档自身的款式
依据 docz 官网文档中减少 logo 的办法[6],能够通过自定义组件笼罩原有组件的模式:
Example: If you’re using our gatsby-theme-docz which has a Header component located at src/components/Header/index.js you can override the component by creating src/gatsby-theme-docz/components/Header/index.js. Cool right?
所以依据 docz 源代码主题局部代码:https://github.com/doczjs/docz/tree/master/core/gatsby-theme-docz/src
,找到对应的文档组件的代码构造,在组件库我的项目根目录新建同名称的文件夹:
|-theme
| |-gatsby-theme-docz
| |-components
| |-Header
| |-index.js // 在这里批改自定义的文档组件
| |-styles.js // 在这里批改生成的款式文件
这样在执行 npm run docz:dev
的时候,就会把自定义的代码笼罩原有款式,实现文档的多样化。
2、批改 markdown 文档款式
事件到这里就完结了吗?不!咱们的指标不仅如此,因为我发现主动生成的 markdown 格局,并不合乎我的审美,比方生成的表格文字居左对齐,并且整个表格款式繁多,然而这里属于 markdown 款式的领域,批改上述文档组件中并不包含这里的代码,那么如何批改 markdown 生成文档的款式呢?
通过我眉头一皱; 计上心来又一动,发现既然在下面批改文档组件款式的时候,重写了 component/Header/styles.js 文件,是否能够在该文件中引入自定义的款式呢?文件构造如下:
|-theme
| |-gatsby-theme-docz
| |-components
| |-Header
| |-index.js // 在这里批改自定义的文档组件
| |-styles.js // 在这里批改生成的款式文件
| |-base.css // 这里批改 markdown 生成文档的款式
这样批改后的表格款式如下:
接下来各位小主能够依据本人的审美或者视觉设计的要求自定义文档的款式了。
六、部署文档到服务器
生成的组件库文档只在本地显示是没有意义的,所以须要部署到服务器上,于是第一工夫想到的是放在 github 进行托管,关上 github 中的 setting 设置选项,GitHub Pages 设置配置的分支:
这时默认关上的首页门路为:
https://plusui.github.io/plusReact/
但实际上页面无效的拜访地址是带有文件夹 docsite 门路的:
https://plusui.github.io/plusReact/docsite/button/index.html
此外,页面引入的其余资源门路,都是绝对路径,如下图资源门路所示:
所以间接把打包后的资源放在 github 上是无法访问各种资源的。
这时咱们只好把网站部署到云服务器上了,思考到服务器配置的繁琐,这里给大家提供一个简便的部署网站:Netlify[7]
Netlify 是一个提供动态网站托管的服务,提供 CI 服务,可能将托管 GitHub,GitLab 等网站上的 Jekyll,Hexo,Hugo 等动态网站。
部署我的项目的过程也很简略,傻瓜式的点击抉择 github 网站中代码门路,以及配置文件夹跟门路,如下图所示:
而后就能够点击生成的网站 url,拜访到部署的网站了:
而且很不便的是,一旦实现部署之后,之后再次向代码库中提交代码,Netlify 会自动更新网站。
此外,如果想自定义 url,那么就只能去申请域名了,在本人的云服务器上,解析域名即可。上面简略说一下配置步骤:
1)首先在 Netlify 网站上,抉择组件库对应的 Domain settings 下 Custom domains,减少本人的域名:
2)而后关上云服务器中的域名解析中的解析设置,将该域名指向 Netlify:
3)最初关上设置的网址,就能够拜访到组件库了:
七、组件按需加载
好了,通过下面的流程,能够在 demo 我的项目中应用组件库了,然而在 demo 我的项目中,执行 npm run build
,就会发现生成的动态资源中即便只应用了一个组件,也会把 reactui 组件库中所有的组件打包进来。
所以如何进行按需加载呢?
按需加载首先映入脑海的是应用 babel-plugin-import
插件, 该插件能够在 Babel 配置中针对组件库进行按需加载.
用户须要装置 babel-plugin-impor
插件,而后在 plugins 中退出配置:
"plugins": [
[
"import",
{
"libraryName": "reactui", // 转换组件库的名字
"libraryDirectory": "dist/components", // 转换的门路
"camel2DashComponentName":false, // 设置为 false 来阻止组件名称的转换
"style":true
}
]
]
这样在 demo 我的项目中应用如下形式:
import {Button} from 'reactui';
就会在 babel 中编译成:
import {Button} from 'reactui/dist/components/Button';
require('reactui/dist/components/Button/style');
然而这样还有些弊病:
1、用户在应用组件库的时候还须要装置 babel-plugin-import
,并做相干 plugins 配置;
2、开发组件库的时候组件对应的款式文件还须要放在 style 文件夹下;
那有没有更为简略的办法呢?在 ant-design 中寻找答案,发现这样一句话“antd 的 JS 代码默认反对基于 ES modules 的 tree shaking”。对呀!还能够应用 webpack 的新技术“tree shaking”。
什么是 tree shaking?AST 对 JS 代码进行语法分析后得出的语法树 (Abstract Syntax Tree)。AST 语法树能够把一段 JS 代码的每一个语句都转化为树中的一个节点。DCE Dead Code Elimination,在放弃代码运行后果不变的前提下,去除无用的代码。
webpack 4x 中曾经应用了 tree shaking 技术,咱们只须要在 package.json 文件中配置参数 "sideEffects": false
,来通知 webpack 打包的时候能够大胆的去掉没有用到的模块即可。这时用户在 demo 我的项目中应用组件库的时候不须要做任何解决,就能够按需援用 JS 资源了。
不晓得大家在看到这里时,是否发现这样配置还是有问题的:即 sideEffects 配置成 false 是有问题的。
因为依照上述配置,就会发现组件的款式不见了!!
通过排查,起因是引入 CSS 款式的代码:import './button.scss'
,能够看到相当于只是引入了款式,并不像其余 JS 模块前面做了调用,在 tree shaking 的时候,会把 css 款式去掉。所以在配置 sideEffects 就要把 CSS 文件排除掉:
"sideEffects": ["*.scss"]
通过上述 tree shaking 的办法,能够实现组件库的按需加载性能,打包的文件去除了没有用到的组件代码,同时省去了用户的配置。
八、款式按需加载
通常来说,组件库的 JS 是按需加载的,然而款式文件个别只输入一个文件,即把组件库中的所有文件打包编译成一个 index.css 文件,用户在我的项目中引入即可;然而如果就是想做按需加载组件的款式文件,该如何去做呢?
这里我提供一种思路,因为 .tsx 文件是由 TS 编译器打包编译的,并没有解决 SCSS,所以我应用了 node-sass 来编译 SCSS 文件,如果须要按需加载 SCSS 文件,则每个组件的 index.tsx 文件中就须要引入对应的 SCSS 文件:
import Button from './button';
import './button.scss';
export default Button;
生成的 SCSS 文件也须要打包到每个组件中,而不是生成到一个文件中:
所以应用了 node-sass 中的 sass.render 函数,抽取每个文件中的款式文件,并打包编译到对应的文件中,代码如下所示:
// 省略 import 引入, 外围代码如下
function createCss(name){const lowerName = name.toLowerCase();
sass.render({ // 调用 node-sass 函数办法,编译指定的 scss 文件到指定的门路下
file: currPath(`../src/components/${name}/${lowerName}.scss`),
outputStyle: 'compressed', // 进行压缩
sourceMap: true,
},(err,result)=>{if(err){console.log(err);
}
const stylePath = `../dist/components/${name}/`;
fs.writeFile(currPath(stylePath+`/${lowerName}.scss`), result.css, function(err){if(err){console.log(err);
}
});
});
}
这样就在生成的 dist 文件中的每个组件中减少了 SCSS 文件,用户通过“按需加载大节”中的办法在引入组件的时候,会调用对应的 index 文件,在 index.js 文件中就会调用对应的 SCSS 文件,从而也实现了款式文件的按需加载。
然而这样还有一个问题,就是在开发组件库的时候每个组件中的 index.tsx 文件中引入的是 SCSS 文件 import './button.scss';
,所以 node-sass 编译后的文件须要是 SCSS 后缀的文件(尽管曾经是 CSS 格局),如果生成的是 CSS 文件,则用户在应用组件的时候就会因找不到 SCSS 文件而报错,也就是用户在应用组件的时候,也须要装置 node-sass 插件。
不知大家有没有更好的方法,在组件库开发的时候应用的是 SCSS 文件,编译后生成的是 CSS 后缀的文件,在用户应用组件的中调用的也是 CSS 文件呢?欢送在文末留言探讨~
结语
以上就是整个搭建组件库的过程,从一开始决定应用现有的 create-react-app 脚手架和 docz 来形成外围性能,到文档的网站部署和 npm 资源的公布,最后感觉应该可能疾速实现整个组件库的搭建,实际上如果要想改变这些现有的库来实现本人想要的成果,还是经验了一些摸索,不过整个摸索过程也是一种播种和乐趣所在,愿走过路过的小伙伴能有所播种~
参考文章
[1] NutUI 组件库: http://nutui.jd.com/#/index
[2] yep-react 组件库: http://yep-react.jd.com
[3] react 官方网站: https://reactjs.org/warnings/…
[4] storybook: https://storybook.js.org/
[5] docz: https://www.docz.site/
[6] docz 官网文档: https://www.docz.site/docs/ga…
[7] Netlify: https://app.netlify.com/teams…
[8]基于 Storybook 5 打造组件库开发与文档站建设小结: http://jelly.jd.com/article/5…