我们的目标是建立一个前端组建库, 使用的技术栈是father+docz,同时要支持typescript, 在build出来的es目录中要能够生成“.d.ts”后缀的类型声明文件,因为只有生成类型声明文件,我们在使用自己开发的组件库的时候才能获得更好的开发体验。
之所以写下这篇文章,是因为自己在使用father-build建设内部组件库的过程中,遇到了一些问题且难以找到相关文档,将自己的经验总结下来希望看到这篇文章的人能避开这些坑,更加顺利地搭建好自己的前端组件库。
1. 创建组建目录,npm init初始化package.json文件
mkdir my-component
npm init
2. 下载会使用到的依赖项
yarn add --peer react react-router-dom
yarn add --dev @types/vfile-message babel-plugin-import father typescript
yarn add antd classnames @ant-design/icons @babel/runtime classnames
3. 建好目录结构(下图仅供参考)
4. 创建各种配置文件,father, tsconfig, package.json
package.json
{
"name": "xui-components",
"version": "1.0.0",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
"types": "./es/index.d.ts",
"scripts": {
"start": "father doc dev",
"doc:build": "father doc build",
"doc:deploy": "father doc deploy",
"build": "father build",
"test": "father test",
"test:coverage": "father test --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/xitengfei/xui-components.git"
},
"files": [
"es",
"lib"
],
"author": "tengfei.xi",
"license": "MIT",
"homepage": "https://github.com/xitengfei/xui-components#xui-components",
"peerDependencies": {
"react": "^17.0.1",
"react-router-dom": "^5.2.0"
},
"devDependencies": {
"@types/vfile-message": "^2.0.0",
"babel-plugin-import": "^1.13.1",
"father": "^2.29.11",
"typescript": "^4.1.2"
},
"dependencies": {
"@ant-design/icons": "^4.3.0",
"@babel/runtime": "^7.12.5",
"antd": "^4.8.5",
"classnames": "^2.2.6"
}
}
其中,main指定了入口文件,module对应es module的输出,types对应你的typings文件,这样在组件在被使用的时候编辑器才能识别出你的组件类型声明
.fatherrc.js 配置father build打包方式, 具体详情可以参考 umijs/father
export default {
target: 'browser',
entry: 'src/index.ts',
esm: 'babel',
cjs: 'babel',
runtimeHelpers: true,
extraBabelPlugins: [
['babel-plugin-import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}]
],
autoprefixer: {
browsers: ['ie>9', 'Safari >= 6'],
},
doc: {
themeConfig: { mode: 'light' },
base: '/',
menu: []
},
};
tsconfig.json typescript的配置文件,注意只有declaration设置为true才能生成.d.ts后缀的文件
{
"compilerOptions": {
"target": "es6",
"module": "esnext",
"lib": ["es2018", "dom"],
"declaration": true,
"outDir": "./esm",
"rootDir": "./src",
"importHelpers": true,
"downlevelIteration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"typeRoots": ["typings", "node_modules/@types"],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"jsx": "react"
},
"exclude": ["node_modules", "examples"]
}
5. 创建第一个组件
接下来我们在components目录创建第一个自己的组件,下面是一个button的例子:
文件路径:src/components/button-demo/index.tsx
import React from 'react'
import classnames from 'classnames'
import './index.less'
/**
* @param {onClick} func 对外暴露的点击事件
* @param {className} string 自定义类名
* @param {type} string 按钮类型 primary | warning | info | default | pure
*/
export interface Props{
onClick?: (e: any) => void;
className?: string;
type?: string;
block?: boolean;
}
const Button: React.FC<Props> = (props) => {
let { children, onClick, className, type, block } = props
return <div className={classnames('xui-btn', 'ripple', type, block ? 'block' : '', className)} onClick={onClick}>
{ children }
</div>
}
export default Button;
样式文件可以直接使用less编写
路径:src/components/button-demo/index.less
.xui-btn {
box-sizing: border-box;
display: inline-block;
padding: 6px 1em;
line-height: 1.5em;
border-radius: 4px;
border: 1px solid transparent;
color: #fff;
font-family: inherit;
background-color: #999;
user-select:none;
cursor: pointer;
text-align: center;
&.primary {
background-color: #09f;
}
&.warning {
background-color: #F90;
}
&.info {
background-color: #C03;
}
&.common {
border: 1px solid #ccc;
color: rgba(0, 0, 0, 0.65);
background-color: #fff;
&::after {
background-image: radial-gradient(circle, #ccc 10%, transparent 11%);
}
}
&.block {
display: block;
}
}
然后在入口文件index.ts将其导出,暴漏给外部使用。
export {default as Button} from './components/button-demo';
6. 撰写使用文档
使用文档的后缀名为".mdx", 语法与markdown类似,更多详情参考docz的文档
---
name: Button
route: /button
order: 3
sidebar: true
---
import { Playground } from 'docz'
import Button from './index'
# Button
#### Basic Usage
<Playground>
<Button>button</Button><br />
<Button type="primary">primary</Button><br />
<Button type="warning">warning</Button><br />
<Button type="info">info</Button><br />
<Button type="common">按钮</Button><br />
</Playground>
#### Block Button
<Playground>
<div style={{width: '360px'}}>
<Button block>button</Button><br />
<Button type="primary" block>primary</Button><br />
<Button type="warning" block>warning</Button><br />
<Button type="info" block>info</Button><br />
<Button type="common" block>default</Button><br />
</div>
</Playground>
我们可以执行 yarn start
,来实时查看文档的效果。
7. build我们的组件
现在命令行执行 yarn build
即可对组件库进行打包了,注意我们在.fatherrc中配置了esm和cjs两种打包方式,对应会生成 es 和 lib两个目录,其中 esm对应的是 es目录,cjs对应lib目录。
8. 其他注意事项
正常情况下,在的es和lib目录下应该已经为我们写的ts源码自动生成了“.d.ts”后缀的类型声明文件,如果没有生成,请仔细对比.fatherrc.js和tsconfig.json这两个配置文件,另外还要注意的是,如果我们基于antd封装业务组件库的话,不要在组件库中使用css module,否则也会造成无法自动产生类型声明文件的问题。
文章未能详尽部分,可以参考这个demo的github源码地址:https://github.com/xitengfei/xui-components, 有问题欢迎批评指正。