前端组件化实战之 Button

⚠️本文为掘金社区首发签约文章,未获受权禁止转载css

你们好,我是洛竹🎋,一只住在杭城的木系前端🧚🏻‍♀️,若是你喜欢个人文章📚,能够经过点赞帮我汇集灵力⭐️。前端

前言

《每一个前端都应该拥有本身的组件库,就像每一个夏天都有西瓜🍉》 一文中,洛竹带领小黑从零搭建了一个组件库项目,完成了项目结构、构建、测试、文档等基础工程化工做并完成了第一个组件 Icon。本期延续上期的组件工程化的主题,夏日炎热,点上一杯杨枝甘露,和洛竹赴一场 Button 开发之约吧。赴约后,你将会收获如下的内容:vue

PS:配合仓库组件库文档阅读本文效果更佳喲!node

Button 与设计心理学

做为前端工程师,入行至今接触最多的就是设计师了。耳濡目染下虽然说没学会什么设计工具,可是对设计与人的心理有了必定认识。react

洛竹认为任何事物都不可能凭空出现,自有其传承。使用普遍的基础界面元素 Button 也不例外,咱们生活中就有随处可见的按钮。举个栗子🌰,天天上班下班必然要按的电梯按钮、手机音量按钮、小米 9 鸡肋的小爱同窗唤起按钮。要搞清楚为何须要按钮,咱们有必要探究下生活中这些按钮的做用。ios

点一下按钮的快感

想象一下把键盘按键换成触摸屏,你最在意的必定是完美还原物理键的敲击感,像洛竹用手机虚拟键盘就喜欢设置按键震动和音效。经过打击(点击)得到快感是较为广泛的人性。按钮在按下、松开时有丰富的质感和交互感,完美知足了人们点一下的快感。git

现实的实用性

从 BB 机到诺基亚再到现在的智能机,实体按钮削减到只剩下音量键和开关机键。按键虽然光秃秃没有任何标识,但咱们就是知道它的功能。试想一下没有这个来自远古时代的开关键,你手里的手机就是一块板砖。github

疯狂暗示用户,达到不可告人目的

小米 9 单独唤起小爱同窗的按键常常会被误按,以前我还不理解这么蠢的设计的目的。在简单研究了点设计心理学我明白了。小爱的设计者为了 产品日活和 AI 训练就是故意这个设计的。web

小米 10 虽然移除了单独的唤起键,却把原来的电源键改为了一键多用。每次想要重启手机还得先唤起一下小爱同窗。不得不说,小爱同窗小米亲女儿。面试

吐槽归吐槽,小米这个按钮确实起到了培养用户习惯的任务。当用户知悉某个按钮能指向某个操做,或者获取某类信息后,久而久之用户就会造成使用习惯。若是某操做可以为用户和厂商持续带来价值,那就可让按钮的位置更加醒目,持续培养用户点击习惯。

指引用户操做

这个在 Web 开发中是最多见的使用场景,每一个可交互页面上都有这类按钮的出现,用来指引用户下一步该怎么作。好比表单的提交和重置。

虽然按钮也常做为表单元素,可是区别于其余表单元素,按钮因其自然地自说明性,不须要 Label 对其进行辅助说明,啰嗦这么多,掘友们应该在看到一个按钮时,应该也会有从设计上品鉴的意识了,欢迎将对下图的品鉴在评论区告诉洛竹。

组件主题化

在开始开发具体组件以前,咱们必须先约定好组件主题化的规范。以前 antd-mobile-rn 就由于设计问题,中途花费大力气重构。几乎全部的组件库都会将色彩、布局这些以 css 变量的形式提供给使用者和开发者为,React Native 不一样的是样式基于 CSS in JS,不过道理相通,参照 vant 的设计资源,咱们抽出了一套 JavaScript 常量:

// packages/themes
export interface Theme {
  'animation-duration-base': string;
  'animation-duration-fast': string;
  'animation-timing-function-enter': string;
  'animation-timing-function-leave': string;
  'font-size-xs': number;
  'font-size-sm': number;
  'font-size-md': number;
  'font-size-lg': number;
  'font-weight-bold': number;
  // 变量过多,这里仅展现部分变量
}
复制代码

有了这些 JS 常量,咱们就能够设计主题系统。基于 CSS in JS 的主题化设计通常是基于 React Context 实现,须要提供 ThemeProvider 传入主题上下文,ThemeConsumer、WithTheme(高阶类组件)、withTheme(高阶函数组件) 或 useTheme(React Hooks)做为消费者获取上下文。本身实现也不难,不过更文任务比较紧急,咱们先基于 cssinjs/theming 实现功能,后期有须要再回来造轮子也不迟。下面👇就是咱们基于 theming 的 createTheming 函数建立自定义主题上下文。

import { createTheming } from 'theming';
const context = React.createContext(defaultTheme);
const theming = createTheming(context);

export const { ThemeProvider, withTheme, useTheme } = theming;
复制代码

主题功能是通用的,所以我将主题相关的能力都放在 @vant-react-native/theme 包中发布。

Button 的实现

React Native 内置的 Button 组件的样式是固定的,只能进行一些简单的设置。且内置的 Button 组件在 Android 和 ios 两个平台上的表现并不一致。因此咱们须要根据更底层的组件进行封装。咱们对比 ant-design-mobile-rn 和 react-native-elements 后采用了前者使用的 TouchableHighlight 组件。因为继承自 TouchableHighlight,因此咱们组件的 Props 类型以下:

import { TouchableHighlightProps } from 'react-native';
interface ButtonProps extends TouchableHighlightProps {
}
复制代码

按钮类型

vant 的 Button 支持 defaultprimaryinfowarningdanger 五种类型,默认为 default。如今,组件的基本定义以下:

// ...
import React, { FunctionComponent } from 'react';
import { Text, View } from 'react-native';

interface ButtonProps {
  type?: 'default' | 'primary' | 'info' | 'warning' | 'danger';
}

const Button: FunctionComponent<ButtonProps> = props => {
  // ...
};
// ...
复制代码

咱们的组件为了适应主题化需求,样式不能是写死在组件里的,而是要经过上下文获取样式常量。咱们思路是首先使用 useTheme 从上下文中获取主题,而后因为样式定义较多,咱们为每一个组件编写一个 useStyle hook 放在单独的 style.ts 文件中:

import { StyleSheet } from 'react-native';
import { Theme, useTheme } from '@vant-react-native/theme';

export const useStyle = props => {
  const theme = useTheme<Theme>();

  const getBackgroundColor = () => {
    switch (props.type) {
      case 'primary':
        return theme['success-color'];
      case 'info':
        return theme['primary-color'];
      case 'warning':
        return theme['warning-color'];
      case 'danger':
        return theme['danger-color'];
      default:
        return theme.white;
    }
  };

  const getTextColor = () => {
    if (props.type === 'default') {
      return theme.black;
    } else {
      return theme.white;
    }
  };

  const getBorderRadius = () => {
    if (props.round) {
      return theme['border-radius-max'];
    }
    if (props.square) {
      return 0;
    }
    return theme['border-radius-sm'];
  };

  const styles = StyleSheet.create({
    container: {
      alignItems: 'center',
      backgroundColor: getBackgroundColor(),
      borderColor: getBorderColor(),
      borderRadius: theme['border-radius-sm'],
      borderWidth: theme['border-width-base'],
      flexDirection: 'row',
      flex: 1,
      justifyContent: 'center',
      opacity: 1,
      paddingHorizontal: 15,
    },
    indicator: {
      marginRight: theme['padding-xs'],
    },
    textStyle: {
      color: getTextColor(),
      fontSize: 14,
    },
    wrapper: {
      borderRadius: theme['border-radius-sm'],
      height: 44,
    },
  });
  return styles;
};
复制代码

基于 useStyle 咱们即可完成一个支持多类型的 Button 组件:

const Button: FunctionComponent<ButtonProps> = props => {
  const styles = useStyle(props);
  const { style, ...restProps } = props;
  return (
    <TouchableHighlight style={[styles.wrapper, style]} {...restProps}> <View style={styles.container}> {typeof props.children === 'string' ? ( <Text style={styles.textStyle}>{props.children}</Text> ) : ( props.children )} </View> </TouchableHighlight>
  );
};
复制代码

注意:子组件多是字符串,也多是组件,因此须要判断类型。

实现效果以下:

朴素按钮

朴素按钮的文字为按钮颜色,背景为白色,咱们经过 plain 属性将按钮设置为朴素按钮。调研了 antd 和 react-native-elements 发现它们都是定义了不少样式,而后在组件内经过逻辑判断计算具体样式的值。我的很不喜欢这种方式,不是完全的 CSS in JS,个人处理方式是将全部有关样式计算的都封装在每一个组件的 useStyle 钩子中,好比当引入朴素按钮属性时,相对于普通按钮改变的有容器背景色、容器边框和字体颜色。因此咱们将这三个属性的值都经过一个单独的函数计算。对比 antd 的源码,会发现不只代码更易读,甚至代码量也少了。

const getBackgroundColor = () => {
  if (props.plain) {
    return theme.white;
  }
  // ...
};

const getTextColor = () => {
  if (props.plain) {
    switch (props.type) {
      case 'primary':
        return theme['success-color'];
      case 'info':
        return theme['primary-color'];
      case 'warning':
        return theme['warning-color'];
      case 'danger':
        return theme['danger-color'];
      default:
        return theme['gray-3'];
    }
  } else if (props.type === 'default') {
    return theme.black;
  } else {
    return theme.white;
  }
};
复制代码

实现效果以下:

细边框

vant 实现细边框是经过设置 hairline 属性能够展现 0.5px 的细边框。可是手机上因为分辨率的影响,贸然设置 0.5 会致使边框不显示的兼容问题。好在 React Native 为咱们提供了 StyleSheet.hairlineWidth 常量来兼容最细边框问题,下面是官方对它的定义:

hairlineWidth 这一常量始终是一个整数的像素值(线看起来会像头发丝同样细),并会尽可能符合当前平台最细的线的标准。能够用做边框或是两个元素间的分隔线。然而,你不能把它“视为一个常量”,由于不一样的平台和不一样的屏幕像素密度会致使不一样的结果。

若是模拟器缩放过,可能会看不到这么细的线。

因为 hairline 只影响了容器 borderWidth 属性,咱们不须要编写单独的样式计算函数:

const styles = StyleSheet.create({
  // ...
  container: {
    // ...
    borderWidth: props.hairline ? theme['border-width-hairline'] : theme['border-width-base'],
  },
});
复制代码

实现效果以下:

禁用状态

表单元素或者说可触摸可点击的元素通常都有禁用状态,vant 中是经过 disabled 属性来禁用按钮,禁用状态下按钮不可点击。TouchableHighlight 继承地有 disabled 属性,咱们只须要设置一些禁用状态下的按钮样式就能够,查看 vant 源码咱们发现只须要修改透明度为 0.5 便可:

const styles = StyleSheet.create({
  container: {
    // ...
    opacity: props.disabled ? 0.5 : 1,
    // ...
  },
});
复制代码

实现效果以下:

加载状态

vant 是经过 loading 属性设置按钮为加载状态,加载状态下默认会隐藏按钮文字,能够经过 loading-text 设置加载状态下的文字。咱们借助 React Native 的 ActivityIndicator 组件能够轻松实现这个特性:

// ...
<TouchableHighlight {...restProps}>
  <View style={styles.contentWrapper}> {props.loading ? ( <> <ActivityIndicator size="small" color={indicatorColor} style={styles.indicator} /> {props.loadingText ? <Text style={styles.textStyle}>{props.loadingText}</Text> : null} </>
    ) : null}
  </View>
</TouchableHighlight>
// ...
复制代码

样式以下:

export const useIndicatorColor = (props: ButtonProps): string => {
  const theme = useTheme<Theme>();
  if (props.plain) {
    switch (props.type) {
      case 'primary':
        return theme['success-color'];
      case 'info':
        return theme['primary-color'];
      case 'warning':
        return theme['warning-color'];
      case 'danger':
        return theme['danger-color'];
      default:
        return theme.black;
    }
  } else if (props.type === 'default') {
    return theme.black;
  } else {
    return theme.white;
  }
};
复制代码

实现效果以下:

按钮形状

默认的按钮有值为 2 的圆角,vant 中经过 square 设置方形按钮,经过 round 设置圆形按钮。按例,咱们经过判断设置样式:

const getBorderRadius = () => {
  if (props.round) {
    return theme['border-radius-max'];
  }
  if (props.square) {
    return 0;
  }
  return theme['border-radius-sm'];
};
const styles = StyleSheet.create({
  container: {
    borderColor: getBorderColor(),
  },
  wrapper: {
    borderRadius: getBorderRadius(),
  },
});
复制代码

实现效果以下:

按钮尺寸

Antd RN 只提供了 large、small 两个尺寸,而在 vant 中支持 large、normal、small、mini 四种尺寸,默认为 normal。虽然写到这里已经很疲倦了,杨枝甘露也早喝完了,可是为了完整复原,仍是续上一杯咖啡继续肝。根据 vant 设计稿咱们新增三个样式获取函数并动态化指定样式:

const getSizeHeight = () => {
  switch (props.size) {
    case 'large':
      return 50;
    case 'small':
      return 32;
    case 'mini':
      return 24;
    default:
      return 44;
  }
};
const getSizePadding = () => {
  switch (props.size) {
    case 'small':
      return 8;
    case 'mini':
      return 4;
    default:
      return 15;
  }
};
const getSizeFontSize = () => {
  switch (props.size) {
    case 'large':
      return 16;
    case 'small':
      return 12;
    case 'mini':
      return 10;
    default:
      return 14;
  }
};

const styles = StyleSheet.create({
  container: {
    paddingHorizontal: getSizePadding(),
  },
  textStyle: {
    fontSize: getSizeFontSize(),
  },
  wrapper: {
    height: getSizeHeight(),
  },
});
复制代码

实现效果以下:

自定义颜色

若是不是本身亲自复刻 Vant,是没想到一个 Button 能玩出这么多花,支持特性这么多耐心和代码管理都是一个挑战。固然了,洛竹采起的样式管理方式比较偏激,你们有好的方式也能够在评论区讨论。

经过 color 属性自定义按钮的颜色。咱们能够得出需求,无论 type 是什么,color 属性需始终覆盖原有样式,color 能影响的就是背景色、字体颜色和边框颜色,因此咱们修改 getBackgroundColorgetTextColorgetBorderColor 样式函数在合适的地方加上如下代码便可:

if (props.color) {
  return props.color;
}
复制代码

实现效果以下:

双击事件的实现

咱们从 React Native 内置的 TouchableHighlight 组件继承了不少事件,其中 onPress、onLongPress 分别表明单击和长按。但惟独“双击 666”的双击事件没有姓名。以前在实际业务曾经封装过双击事件,此次咱们就直接就内置了。

实现思路是延时执行单击事件(默认 200 毫秒),而后记录点击次数和两次时间间隔,当识别为第二次点击且时间间隔小于单击延时时间。那么就取消单击事件延时,并当即执行双击事件。完整代码以下:

let lastTime = 0;
let clickCount = 1;
let timeout = null;
const _onPress = (event: GestureResponderEvent) => {
  const now = Date.now();
  if (timeout) {
    clearTimeout(timeout);
  }
  timeout = setTimeout(() => {
    props.onPress(event);
    clickCount = 1;
    lastTime = 0;
  }, props.delayDoublePress);
  if (clickCount === 2 && now - lastTime <= props.delayDoublePress) {
    clearTimeout(timeout);
    clickCount = 1;
    lastTime = 0;
    props.onDoublePress(event);
  } else {
    clickCount++;
    lastTime = now;
  }
};
复制代码

你们会发现这里的实现糅合了函数防抖、节流以及计数器的原理,有兴趣的小伙伴能够自行复习下原理,这里就不展开了。

API 文档

一个组件的文档,除了 Demo,还须要展现出来可用的 Props,Dumi 内置的 <API></API> 组件能够根据组件自动生成 API 文档。首先咱们像下面同样编写 Props 注释:

interface ButtonProps extends TouchableHighlightProps {
  /** * @description Can be set to primary、info、warning、danger * @description.zh-CN 类型,可选值为 primary、info、warning、danger */
  type?: 'default' | 'primary' | 'info' | 'warning' | 'danger';
  /** * @description Can be set to large、small、mini * @description.zh-CN 尺寸,可选值为 */
  size?: 'large' | 'normal' | 'small' | 'mini';
}
复制代码

而后在 Markdown 中引入 API 组件便可:

<API src="./index.tsx"></API>
复制代码

内置组件 API 没有处理继承的状况,咱们后续会自定义一个 API 组件,这里就不展开了,浏览 Button 文档 能够查看如今的效果:

工程化串讲

因为很难在一篇文章中将组件开发相关的工程化讲完,咱们须要在每篇实战中串讲一下。

组件建立脚手架

小黑:洛竹,lerna create 命令建立出来的模块并非咱们想要的,之后要建立不少不少组件,咱们能够写一个建立组件模块的脚手架吗?

lerna 使用起来是有很多痛点的,lerna create 命令没办法指定模板,考虑到以后的几十上百个组件每次建立都要进行项目结构、Typescript 配置、单元测试配置、Babel 配置等等工做步骤,咱们有必要写一个脚手架。

模板解析

说到模板解析,相信你们和我同样想到的是 vue-cli 的 template 解析。经过阅读 vue-cli@2.9.6 generate.js 源码,咱们能够分析出尤大是基于 metalsmith、handlebars、consolidate 这三个包来实现模板解析能力的。让人不安的是其中 metalsmith 库有长达 5 年没有维护了,洛竹挑选开源项目通常对维护度很敏感,本着轮子要用本身造的原则,我翻看了 Metalsmith 的 Readme 发现这个插件无非是经过递归读文件的方式渲染模板,而且它的静态网站生成的能力对咱们模板解析的需求也是多余的。

说干就干,在和 @林小帅 同窗简单沟通后,我动手造了 handlebars-template-compiler 这个轮子,其主要原理以下:

  1. 使用 recursive-readdir 递归获取全部文件路径
const files = await recursive(rootDir);
复制代码
  1. 使用 handlebars.compile 方法使用元数据对模板进行渲染
const content = fs.readFileSync(file).toString();
const result = handlebars.compile(content)(meta);
复制代码
  1. 使用 fs.writeFileSync API 重写文件

另外,经过引入 glob 模式匹配实现了 exclude 配置以及只处理指定后缀(默认 **/*.tpl.*)的文件来避免没必要要的渲染。(PS:NPM 一周有了 300 多下载,有须要的掘友值得一试😄)

Node CLI(@vant-react-native/scripts)搭建

这里洛竹尝试用最简洁的语言为你们描述一个脚手架的诞生,源码在 packages/scripts 目录下,没有接触过 CLI 的掘友请相信我,Node CLI 很容易上手的。接触过的同窗也能够查漏补缺借鉴一二。

  1. package.json 文件的 bin 字段是咱们脚手架的入口
// 指定可执行文件的位置以及别名
"bin": {
  "vant": "./bin/cli.js"
},
复制代码
  1. 定义 ./bin/cli.js 为可执行文件并调用 init 方法。
// 因为咱们的脚本是 Node 编写的,因此须要指定 node 所在位置
#!/usr/bin/env node
const { init } = require('../lib');
// 这个地方参考了 create-react-native 的设计
// 本文点赞过 300,下一篇洛竹带小黑为你们带来《基于 TypeScript 重构 create-react-native》
init();
复制代码
  1. 而后在 src/index.ts 中初始化 commander 这个久负盛名的命令行框架
const init = (): void => {
  const packageJson = require('../package.json');
  program.version(packageJson.version).description(packageJson.description);
  // ...
  program.parse(process.argv);
};
复制代码
  1. 为了方便管理命令,咱们将命令都放置在 src/commands 目录下并经过 fs.readdirSync API 动态扫描注册。
const init = (): void => {
  // 这段代码借鉴自 NeteaseCloudMusicApi 项目,做者的代码颇有设计感,推荐阅读。
  fs.readdirSync(path.join(__dirname, 'commands')).forEach((file: string) => {
    if (!file.endsWith('.js')) return;
    require(path.join(__dirname, 'commands', file));
  });
  // ...
};
复制代码
  1. 最后在 commands 目录下新建一个 create.ts 文件编写命令
import { program } from 'commander';
program
  .command('create <name> [loc]')
  .description('Create a new vant-react-native package')
  .action((name,loc) => {
    console.log('Hello Luozhu');
  })
复制代码

脚手架实现

上一小结,咱们初始化了 CLI 并添加了 create 命令,这一小节咱们就来实现一下脚手架功能。

咱们首先在 packages/scripts 目录下建立组件模板

.
├── README.tpl.md # tpl 后缀在生成组件模板的时候会被 handlebars-template-compiler 自动去掉。
├── package.tpl.json
├── src
│   └── index.ts # 没有 tpl 后缀则不会被编译,模板很大时能够节省时间。
└── tsconfig.json
复制代码

而后咱们明确咱们的模板元数据的数据结构,我这里的数据结构是:

interface IMeta {
  name: string;
  version: string;
  description: string;
  author: string;
  email: string;
  url: string;
  directory: string;
}
复制代码

有了数据结构,咱们就可使用 inquirer 模块引导用户输入信息。

import inquirer from 'inquirer';
// ...
// getQuestions 过长,感兴趣的同窗能够查看:http://tny.im/UFbg
const answer: IMeta = await inquirer.prompt(getQuestions(name));
// ...
复制代码

下一步,咱们使用 tmp-promise 模块建立一个系统临时文件夹,并将前文提到的 template 文件夹的内容拷贝进去:

import tmp from 'tmp-promise';
import fs from 'fs-extra';
import path from 'path';
// ...
const tmpdir = await tmp.dir({ unsafeCleanup: true });
fs.copySync(path.join(__dirname, '../../template'), tmpdir.path);
复制代码

最后,咱们对临时文件夹的内容进行编译再拷贝到指定位置便可:

import htc from 'handlebars-template-compiler';
// ...
await htc<IMeta>(answer, tmpdir.path);
fs.copySync(tmpdir.path, `${process.cwd()}/packages/${locPath}`);
// ...
复制代码

折腾这一顿,让咱们来看下成果吧:

Github CODEOWENERS

大型的开源项目最难的不是技术问题,技术大咖永远不会缺。最难的实际上是协做和后期维护。试想一下一个成百上千人参与的项目当有新的 pr 时,正常人根本无力去快速检索出须要谁去 review 代码。咱们的 vant-react-native 因为是将每一个组件单独发包维护,当参与的小伙伴多了也会产生这个困扰。

而 GitHub CODEOWNERS(代码全部者)就是为了解决这个问题的,在 5000+ 贡献者参与的 DefinitelyTyped 项目中咱们就能够看到它的身影。官方对代码全部者定义以下:

你可使用 CODEOWNERS 文件定义负责仓库代码的我的或团队。当有人修改代码并打开一个 pull request 时,将自动请求代码全部者进行审查。

CODEOWNERS 文件使用遵循 gitignore 文件中所用大多数规则的模式,CODEOWNERS 文件位置通常位于 .github/ 目录下。

在 vant-react-native,洛竹是仓库的最终负责人,因此是指望每一个 pr 均可以分配给本身审查一下的。那么咱们这就来实验一下吧,新建一个 .github/CODEOWNERS 文件并写入如下内容:

# This is a comment.
# Each line is a file pattern followed by one or more owners.

# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# @youngjuning will be requested for review when someone opens a pull request.
*       @youngjuning

# In this example, @doctocat owns any files in the build/logs
# directory at the root of the repository and any of its
# subdirectories.
/packages/ @luozhu1994
复制代码

通常若是文件具备代码全部者,则在打开拉取请求以前能够看到代码全部者是谁。在仓库中,你能够找到文件并悬停于一个锁图标上,悬浮以后会告诉你该文件全部者是谁:

而后咱们提交一个 pr 看看效果:

NPM 发包自动化

发包权限通常只有仓库全部者一我的拥有,可是 owner 同时维护好几个 NPM 帐号,或者是 owner 突然很忙将发布权限交给其余人管理员可是不便告知 NPM 帐号该怎么办呢?答案是将 NPM 发包 CD(持续部署)化,公司通常会基于 Gitlab 或自建平台实现该功能。做为开源项目,咱们固然是使用 GitHub Action。

正常的单包项目,使用 npm-publishnpm-publish-action 这两个 GitHub Action,这并无好讲的。可是基于 lerna 的多包单体仓库并无现成的插件能够用,照例,咱们来看下本身实现的步骤:

  1. 判断 commit message 是否以 chore(release): 开头

    经过 GitHub Action startsWith(github.event.head_commit.message, 'chore(release):') 实现

  2. 经过 NPM publish token 认证登陆

    经过 npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} 认证

  3. 执行 lerna publish from-package --yes 发布

    须要本地先执行 lerna version 系列命令提高版本

完整 GitHub Action 实现以下:

name: npm-publish

on:
  push:
    branches:
      - main

jobs:
  npm-publish:
    runs-on: ubuntu-latest
    if: startsWith(github.event.head_commit.message, 'chore(release):')
    steps:
      - uses: actions/checkout@v2
      - uses: c-hive/gha-yarn-cache@v2 # 缓存 node_modules 加快构建速度
      - name: Install Packages
        run: yarn install --registry=https://registry.npmjs.org/
      - name: Authenticate with Registry
        run: | npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN}         env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
      - name: Publish package
        run: lerna publish from-package --yes
复制代码

为了在发布后及时获取通知,洛竹使用了 peter-evans/commit-comment 插件在发布失败或成功后对相应 commit 进行评论,这样咱们就能够收到邮件和站内通知。

- name: Create commit comment after publish successfully
  if: ${{ success() }}
  uses: peter-evans/commit-comment@v1
  with:
    body: | Hello Dear @youngjuning. This commit has been publish to NPM successfully. > Created by [commit-comment][1] 
      [1]: https://github.com/peter-evans/commit-comment
- name: Create commit comment after publish unsuccessfully
  if: ${{ failure() }}
  uses: peter-evans/commit-comment@v1
  with:
    body: | Hello Dear @youngjuning. This commit has been publish to NPM unsuccessfully. > Created by [commit-comment][1] 
      [1]: https://github.com/peter-evans/commit-comment
复制代码

致谢

截止发稿时,每一个前端都值得拥有本身的组件库,就像每一个夏天都拥有西瓜🍉 已得到近 1600 赞、超 4 万阅读📖,再次再次感谢掘友的支持、编辑 Zoe 的鞭策,月影大佬的转载、朋友的转发以及本身的坚持。

近期好文

本文首发于「掘金专栏」,同步于公众号「程序人生」和「洛竹的官方网站」。

相关文章
相关标签/搜索