利用father build 开发前端组件库实战

我们的目标是建立一个前端组建库, 使用的技术栈是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. 建好目录结构(下图仅供参考)

file-structure.png

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, 有问题欢迎批评指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容