Git Product home page Git Product logo

blog's People

Contributors

levy9527 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

📦vue组件发布npm最佳实践

前言

我们经常使用组件,二次封装或开发新组件,在团队内部使用; 可当我们想通过npm分享组件时,却没了之前的得心应手,本文旨在帮助大家在可以更轻松地发布组件

首先,把vue组件发布到npm这件事可以拆分成两个部分:

  1. 在npm上发布一个包
  2. 将vue组件打包

npm发包

有人说,发包不是一行命令就搞定了么

npm publish

是的,可是还忽略了以下几点:

  1. 首先你要在npmjs上注册一个账号
  2. 查看你的.npmrc设置,确保你的registry是https://www.npmjs.com/, 而不是淘宝源
  3. 在终端npm login,登录你的账号

做好以上三点,才可以通过npm publish简单地发布一个包。若要遵循最佳实践,还有一些准备工作要做好,下面将为你讲述

完善基本信息

package.json的以下字段需要注意

  • name
  • version
  • description
  • keywords
  • author
  • license
  • repository
  • main
  • unpkg
  • module
  • scripts
  • engines

name就是发布到npm上的包名,也即别人安装时输入的名字yarn add ${name}, 包名应该是kebab-case, 即英文单词全小写,中划线分割(lower case and dash-separated)

version是语义化的,major.minor.patch. 如果是major变动,通常意味着不兼容的修改; 如果是minor,意味着添加向后兼容的新功能,如果是patch, 意味着bug的修复。详情见semver.org

description是对包的描述,在npmjs.com上搜索时会显示,有助于用户在搜索时进行筛选

keywords 同样也是帮助用户查找到你的包

author的格式一般是${your name} ${email}, 当然也可以是一个github地址

license可能很多人会忽略,最好也写上去。至于用哪个,vue的官方项目全是MIT,因此我也是MIT,不纠结

repository的格式参考如下:

"repository": {
  "type": "git",
  "url": "https://github.com/FEMessage/el-data-table.git"
}

这样在npm包页面就有会个github的入口

main定义了包的入口文件,在NodeJs环境,语句import pkg from 'package-name'时,其实导入的就是main定义的文件,它可以是CommonJs格式的, 也可以是umd格式

需要注意的是,当你把一个包发布到npm上时,它同时也可以在unpkg上获取到。也就是说,你的代码既可能在NodeJs环境也可能浏览器环境执行。为此你需要用umd格式打包,并且在package.json定义unpkg字段,一般而言此时它的命名为name.min.js

最后,别忘了定义modulejsnext:main字段,它表示用ES6模块格式打包,给Webpack 2+或Rollup等先进的构建工具来处理。

我们来看一下三个字段的示例:

"main": "dist/el-data-table.js",
"unpkg": "dist/el-data-table.min.js",
"module": "dist/el-data-table.esm.js"

scripts 为了防止出现发包前忘记构建的乌龙事件,定义一下发布前的脚本, 这样每次执行npm publish前都会先执行npm run build

"prepublishOnly": "npm run build"

engines 可以告诉用户运行你的包对NodeJs版本的要求,这是非常重要的,不然你使用了NodeJs新版本特性,却没有定义该字段,导致低版本NodeJs用户运行报错,让人摸不着头脑。

定义依赖

当你开发一个项目时,比如一个静态网站或一个单面应用,dependenciesdevDependencies并没有太多区别,因为你npm installyarn时,这些依赖都会下载下来,因为你是在开发。

但对于发布到npm的包则不同:

dependencies 是运行你的包必须安装的依赖,即当用户yarn add my-awesome-package时,这些依赖也会下载。

devDependencies 是开发你的包时需要安装的依赖,比如eslint, jest等开发工具,当用户yarn add my-awesome-package 时,这些依赖并不会下载!

peerDependencies 一般用于开发插件的场景,它要求用户必须预先安装了某些依赖。比如开发webpack的插件,如果你把对webpack的依赖定义成dependencies, 如果用户安装的webpackdependencies里的minor版本不一致, 则用户yarn add my-webpack-plugin时会把dependencies定义的webpack也下载下来,也即用户会安装两个major版本相同的webpack, 这就不合适了。

所以说,定义好你的包的依赖,可以让用户安装使用你的包时少点困惑,多些愉悦。

忽略文件

如果有 .gitignore文件,则发布时会忽略 .gitignore中定义的文件;  也即这些文件不需要在.npmignore重新定义。如果用.gitignore忽略了dist目录,但发包时又需要发布dist目录,此时可以在package.json定义files字段,这是一个白名单,里面的文件都会被发布出去

"files": [
  "dist"
]

需要注意的是,子文件夹.gitignore.npmignore同样有效,而它们会覆盖files字段

另外,有些文件无论如何设置,都不会发布出去:

  • node_modules
  • .git(包括.gitignore)

README.md

别忘了这个文件,写下与包相关的更具体的信息,告诉用户这个包有哪些功能,如何使用。这很重要,用户不会使用一个没有文档说明的包!

发布

一个版本只能发布一次,为了避免每次手动修改package.json, 可以使用npm version [major | minor | patch]命令来更新package.json里的版本

打标签

假设你的包最新版本是1.0.0, 当用户yarn add my-awesome-packageyarn add [email protected]时,其实是相当于yarn add my-awesome-package@latest, 即不指定标签安装时,默认安装latest版本。

假设你正在开发2.0.0版本,它还不稳定,你想发布它让用户测试一波,此时又不能让它变成latest版本,不然用户yarn add my-awesome-package时就安装了2.0.0了,那将让用户崩溃。这时该怎么办呢?标签就用上场了。

可以这样发布

npm publish --tag beta

则用户yarn add my-awesome-package安装的是1.0.0版本, yarn add my-awesome-package@beta时,安装的是2.0.0版本,不影响老用户,棒!🎉

记住,你只能对一个版本打一个标签,使用npm dist-tag ls 可以查看npm包一共打了几个标签

打包Vue

脚手架

经过一番折腾,在Vue Conf上找到一个vue组件的打包脚手架(vue官方文档也有说明),进行“本土化”修改完善后,已在github开源:https://github.com/FEMessage/vue-sfc-cli

说明

我们以开源组件el-data-table为例,解释目录结构及文件

├── README.md
├── build
│   └── rollup.config.js
├── dist
│   ├── el-data-table.esm.js
│   ├── el-data-table.min.js
│   └── el-data-table.umd.js
├── docs
│   ├── build
│   └── index.html
├── package.json
├── src
│   ├── el-data-table.vue
│   └── index.js
├── styleguide.config.js
├── test
│   └── index.test.js
└── yarn.lock

先来看三个文件:

  • README.md
  • package.json
  • yarn.lock

README.mdpackage.json大家都懂,有yarn.lock因为是我们鼓励大家使用yarn,  它比npm更快。虽然npm 6.0号称提速17倍(可以想象6之前是得有多慢😂),但经测试,还是不如yarn

接下来看build, dist, src 目录

├── build
│   └── rollup.config.js
├── dist
│   ├── el-data-table.esm.js
│   ├── el-data-table.min.js
│   └── el-data-table.umd.js
├── src
│   ├── el-data-table.vue
│   └── index.js

build 目录下放编译时的配置文件,这个跟vue-cli 2.x生成的模板build目录作用是一样的,只不过这里放置的是rollup.config.js。至于为什么用Rollup, 一是因为配置更简单,二是因为它更适合打包类库,当源文件中有import lib from 'awesome-lib'类似的代码时,Rollup并不会把awesome-lib捆绑输出,这正是开发类库或组件需要的特性

dist是输出目录,也有叫lib的,我也纠结了好久。看了一些优秀的开源项目,发现叫dist的比较多,而webpack4默认的输出目录也是dist, 因此决定用dist。至于dist目录下会有三个文件,前文已说过原因。而命名为何不是camelcase, 而是kebab-case, 后面风格指南会说到

src是输入目录。把index.js放在src目录,也是经过一番考虑。也想把index.jspackage.json同级,最终参考了webpack4, 它默认输入是src/index.js, 那就跟主流保持一致。该文件主要工作是把src目录下的vue文件设置成vue的插件。同样,vue文件的命名后面风格指南会说到

├── test
│   └── index.test.js

test目录下是基于jestvue/test-utils的单元测试文件,具体教程可参考官方文档

├── docs
│   ├── build
│   └── index.html
├── styleguide.config.js

docs存放的是组件的api文档,包含props, slot, event等内容的说明,使用的是vue-styleguidist作为vue组件文档生成工具。为啥叫 docs呢,因为Github Pages支持从master分支的docs目录读取文件,在仓库Settings里选择Github PagesSource即可, 具体看官方文档

风格指南

vue组件把template/script/style都放在一个vue文件里,这个称之为单文件组件,Single File Component,缩写为SFC, 这就是vue-sfc-cli中sfc的寓意

通读vue官方风格指南,  由于我们是kebab-case的重度用户,因此我们更看重的是在多个项目中保持相同的大小写规则,以下是摘取的适用于我们团队协作习惯的指南:

这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的

我们选择使用kebab-case

好例子:

components/
|- my-component.vue

好例子:

<!-- template -->
<my-component></my-component>
  • [JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串](JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串)

这里我们选择使用PascalCase, 因为在编程语言里,kebab-case并不是最佳实践。 也即,在非编程语言的范围,我们能用kebab-case就用

好例子:

Vue.component('MyComponent', {
  // ...
})

import MyComponent from './my-component.vue'

export default {
  name: 'MyComponent',
  // ...
}

综上所述,就可以明白前文中el-data-table的文件命名风格为kebab-case的原因了

参考

  1. How to publish your package on npm
  2. module-best-practices
  3. npm-style-guide
  4. semver.org
  5. unpkg
  6. umd
  7. npm scripts
  8. .npmignore
  9. 2018 Vue Conf
  10. Vue Cookbook
  11. Vue Style Guide
  12. npm-vs-yarn
  13. Github Pages

本文最早发布在掘金,现迁移至github。

👨🏻‍💻我曾经是怎么做面试官的

阅读提示

更新于2019年2月3日:本文过于注重技巧,很多内容笔者已不再使用。这是成长的必经过程,正如独孤求败的剑术精进之路,先注重技巧,再内化,变得不拘泥于形式。

纵然本文内容已不完全与笔者真正的面试风格吻合,考虑到可能有开阔视野,启发思路的作用,对新人仍有参考价值,故分享出来。读者可结合自身实践,批判性地看待本文内容,不需要全盘接受。

形势分析

在线上沟通的时候,就可以分析双方的形式,即我方究竟是处于优势,还是处于劣势。这将为线下的面试打下了一个基调,不同的形势下,需要采取不同的打法。

优势

我方处于优势,即我方占上风,此时是买方市场。

其中的可能的表现为:

  • 候选人主动发消息沟通,主动发送简历

  • 称呼叫“您”,而不是“你”

  • 主动提出面试

  • 担心不符合岗位要求

劣势

我方处于劣势,即我方占下风,此时是卖方市场,其本质是吸引力不足。

其中的可能的表现为:

  • 候选人评价质疑我方的能力,点评我方不足

  • 消息“已读”,不回复

很多在劣势情况下, 我们是换一个目标。力挽狂澜的案例还是比较稀少的,性价比也不高。

平局

平局也即所谓“五五开”,互相有需求,互相有筛选,那么,这种要靠线下的发挥,让我方扩大优势,增强吸引力。

实战

优势打法

重点是:少犯错,保持优势。

可以采取以下手段:

  • 保持时间紧凑

  • “例行公事”

  • “喜厌不形于色”

保持时间紧凑

因为在线上已经做了许多准备工作,所以现场并不需要聊太多,个人经验而言,保持在半小时内即可。

我犯过的错误就是,最后问“你还有什么想问的”,对方问一些问题; 我还问“还有呢”,不断地给对方提问的机会,忽略了时间,结果说得越多,“出错”的可能性就越大。

为什么说“出错”?因为如果对方问的是不利我方的问题,此时如实回答,就会降低我方身价; 如果想修饰一下,临场发挥不一定能达到令人满意的水平。只要是回答不好,那就让我方的优势有所丢失。

所以应该控制时间,有效的办法是开场前用手机定个闹钟,则到点后自动提醒,时间到了就得结束谈话,不拖泥带水。

“例行公事”

“例行公事”就是让人感觉流程化,结合时间紧凑,在整个过程中,就不会让对方有多余的思考,一切是我方在带领节奏,我方主导,一切在掌握之中。

如果对不同的人采取不同的流程,虽然灵活,但难免会发意外情况,如果应对不好, 那就减分了,因此还是使用一套自己熟悉的流程,求稳为上。

“喜厌不形于色”

脸上面无表情,不过多地表露内心的情绪。

我犯过的错误就是,当对方表现出闪光点时,我脸上表现得略显惊喜,眼里都是满意,结果被对方当场看出来了。

这有什么不妥呢,这会让增长对方的自信,加重自己的筹码。

所以,纵然自己觉得对方不错,达到我方的标准,也不能表现出“你就是我想要的人”形态。不能让对方觉得我方很需要TA,那就成卖方市场了。

这里有个技巧,就是言语,以及态度中,表现出我方的选择很多,处于买方市场的情形。潜意识中向对方表达,我方的候选人很多,多你不多,少你不少。

指出缺点

该方法适用于想要控制主导权的场景,比如候选人有点自大,或不礼貌,或试图控制局面(当然这种情况很少)时,就可以使用。

注:该方法要求使用者需要一定的控场能力,因为指出缺点会导致气氛变僵,所以必须有后续措施能把气氛缓和回来; 另外指出缺点有可能让对方自尊心受损,所以必须注意形式,不激起对方的反抗心理。

价值观

技术面(或称专业面)很容易出现的一个问题就是,只聊专业能力,而忽略了其他。

这会造成一个问题,可能在最后待遇环节出现问题,候选人只比较薪资,而不考虑其他。于是可能发了offer,候选人觉得别的公司价钱更高,于是没来。

其实可以在面试的时候,输出我方的价值观,给对方“洗脑”,让对方能看到钱以外的东西。

所以进行专业面的时候,有必要在最后了解一下候选人的人物性格,个人想法,以及生活方面的内容,侧面加深对候选人的了解,同时趁此机会输出一波价值观,让他能看到更高维度的东西。

平台与项目

之前犯了个错误,为了抬高平台,我采用了打压项目的方式,想以此有个对比,来突出平台的优越感。但这样会让人对项目有所抵触。

更高明的做法是“双赢”,而不是“此消彼长”。也即说明平台与项目是相辅相成的。做项目也没啥不好的,我们的都是百万级甚至千万级别的项目,简历上有这样的经验,那也是很添金的。

加班

一个人经常加班,很可能是工作方式不当,缺乏技巧,工作效率低,工作能力不行; 一群人经常加班,一定是leader能力不行,一将无能,累死三军。

交流

合格的面试应该是有交流的,而不是完全是一问一答的方式。

在面试的最后,可以有个总结。

所谓交流,其关键点有:

  • 换位思考

  • 表达感受

  • 提供价值

如能触类旁通,就会明白,以上三点各类谈话场景中都是适用的。

我以前犯的错误在于,我的态度是强筛选的,但却一路强势到底,没有站在对方的角度,表示理解; 也没有表达感受,肯定对方的能力; 对我方的信息也没有透露过多,没有让对方看到别的价值。

所以这就导致候选人看不到一个更大的平台,更好的成长空间,这才会斤斤计较眼前的既得利益。

当然,这样做也是有历史原因,我曾因为太友好却没有产生好的结果而耿耿于怀。但现在想来,我这是因噎废食,所以我反思调整了过来。

总结一下,以上三点是面试进阶点,需要好好琢磨,反复实践,在实战中摸索出一套适合自己的操作。

查找

背景

做一个程序:

  • 输入:一本英文小说
  • 输出:出现次数最多的单词及出现次数

当然小说字数变多时,如何改进数据结构与算法,才能让程序性能令人满意?

分析

这里用到了符号表(symbol table):ST<word, count>

主要是用到了两个API:

  • put
  • get

也及涉及到了查找及修改,换句话说,需要一种支持快速查找与修改的数据结构,才能较好地解决问题。

实现

线性结构

链表可以快速插入(添加一个尾指针),但查找的效率是O(N),不能令人满意。
image.png

数组(平行数组,一个keys[],另一个vals[]),有序地插入时,可以利用二分查找,让查找效率为O(lgN)。但其插入效率为O(2N),不能令人满意。
image.png

二叉查找树(BST)

BST较好地结合了链表与数组的优点:有序,且支持二分查找,插入与查找的效率都是对数级别。

此外,还较好地支持了:rank、select、delete、range query的操作。

BST的效率取决于树的高度,在极端情况下,会退化成链表,而正是这一潜在的问题,促使进一步的研究。

image.png

2-3 树

同时包含2叉节点与3叉节点(中间子节点的值介于父节点两个值之间),自底向上构建,能够自我平衡。

image.png

插入后的变化操作如下:(右边的部分省略了对4叉节点的再次分裂操作)
image.png
2-3树的难点在于,实现起来比较费力(与红黑树相比),出于工程实践考虑,促使了进一步的研究。

红黑树(Red-Black BST)

基于BST对2-3树的巧妙实现(只需要修改BST的put与delete的代码),如果把红线水平放置,视觉上就更容易看出就是2-3树。
image.png

特点有:

  1. 根节点一定是黑的
  2. 红色要在左孩子节点(这里讨论的是偏左的红黑树)
  3. 不能相邻两条边连续红色
  4. 是平衡的(每一层左右子树的高度差不超过1)

小测试:答案是3、4(1不平衡,2无序)
image.png

每次插入新节点,都要为新节点染上红色;此时如果违背了上述规则,则需要进行操作

  1. 左旋
  2. 右旋
  3. 反向染色

image.png

记录一些细节,可点击查看视频

  1. 递增序列插入,红黑树的高度单调递增(注意与严格单调递增区别开来
  2. 递减序列插入,上述结论不成立

散列表

基于数组可以随机访问的特点,提出设想:如果可以把ST<key, value>的 key 转换成整数,则可以用数组的下标作为 key,从而实现能快速查找与修改的符号表。这样实现的符号表就是散列表。

设散列表数组大小为M,则

核心步骤有两个:

  1. 实现散列函数:把 key 转换成整数。关键在于,尽可能均匀(可利用对质数取余计算)
  2. 解决散列冲突
    1. separate chaining(数组里装载的是链表)
    2. linear probing(两个平行数组,删除比较棘手,index递增方向挨着的有值的位置,需要重新put)

当然,工程实践上,还要考虑数组的容量扩缩容,以及 load factor 的大小。

image.png

image.png

linear probing 冲突且走到数组尾总时,会重新在开头查找

练习

  1. How many empty lists do you expect to see when you insert N keys into a hash table with SeparateChainingHashST, for N=10, 102, 103, 104, 105, and 106? 思路是利用公式,得出有多少不同的值,则会有多少不同的链表,则总的减去非空的即为答案
  2. public int hashCode() { return 17; } 这样的散列函数是合法的(整数)
  3. 拉链法使用红黑树
  4. bad hash function

public int hashCode()
{
	int hash = 0;
	int skip = Math.max(1, length()/8);
  // this is bad,increase possibility of hash collision
	for (int i = 0; i < length(); i += skip) 
	hash = (hash * 37) + charAt(i);
	return hash;
}

  1. 用ST实现Set(dummy value)
  2. 如果HashST允许重复key:则put永远新增,不会覆盖
  3. 如果RedBlackBST允许重复key:因为旋转的存在,左右子节点都可能是重复的key(BST只会有一边是重复key)
  4. 反向对照索引(输入对照索引 concordance——记录每个单词出现的位置,输出原始文本)。我的思路如下:
    1. 位置是关键信息,需要有序。
    2. 则遍历 map<word, pos[]>,拿出所有位置信息,重新 put<pos, word> 进红黑树中
    3. 最后把红黑树中的key由小到大遍历,输出相应的value
    4. 点评:对“索引”的理解不够到位。pos 与字符一一对应,则拿出所有的 pos ,在遍历中设置result[pos] = word 就可以还原原始文本了
  5. 不重叠区间的查找。分析如下:
    1. 先对区间进行由小到大排序(因已知不重叠,故比较右边区间即可),记为 partition[]
    2. 遍历 partition[],判断是否同时满足 partition.min<= input <= partition.right 即可
    3. 为何好像并不涉及ST?
    4. 点评:对“有序时使用二分查找”理解不够深刻。纵然是比较端点,依然可以使用二分查找,所以底层数据结构是红黑树——对有序数据从左至右地扫描,实在是太低效啦。
  6. 日程安排。分析如下:
  7. 使用ST<teacher,time[]>
  8. 判断 time[] 中的某时间段有值时,说明有课,不能再安排课程,否则修改 time[].get(index), put(teacher, time[])
  9. LRU缓存。实现步骤如下:
  10. 双向链表,有一定容量;
  11. 插入前,先查找
    1. 如果未找到,则在头部插入
    2. 如果找到了,则把该节点放到头部
  12. 检查是否超出容量
    1. 是,则删除尾部节点
  13. 点评:散列表+双向链表 理解不够深刻啊!
    1. 在头部插入,以及尾部删除,这两点理解地没有问题,都是O(1)
    2. 关键是,命中的查找,如何O(1)?如果纯双向链表,是不可能完成这个任务的。所以需要借助散列表来协助查找。
      1. 这里有个陷阱:以为查出 Node 还要用链表再查找一次。其实散列表里存储的是引用,因而可以直接操作节点!
      2. 当然,这就要求,链表直接使用Node来操作,而不是使用集合类如 LinkedList

📱我曾经是怎么使用招聘软件的

阅读提示

更新于2019年2月3日:本文内容已经过时。

考虑到可能有开阔视野,启发思路的作用,对新人仍有参考价值,故分享出来。读者可结合自身实践,批判性地看待本文内容,不需要全盘接受。

介绍

公司缺人,又没认识的人可以推荐,怎么办? 此时就要用到互联网的招聘工具了。

本文将以“Boss直聘”为例,分享在上面进行 IT行业 前端工程师岗位 招聘的心得与技巧。

本文写于2018年12月,纵然读者所在的行业/岗位不同,使用的软件也有变化,年代也不相同,但其基本原理是不变的,希望大家能触类旁通,举一反三,在工作实践中,提高招聘效率。

分析

要招什么样的人

招聘前,一定要想清楚,自己想要的是什么样的人。所以才会有岗位要求,工作内容说明等。

如果这些随便写,没有个性化的内容,都是copy别人的,那么,你一定遇到很多你不想要的人。这就浪费了时间。

所以,一定要把要求写清楚,越详细越好,这样可以提前把一些不符合要求的人“吓跑”,提高筛选效率。

我在招高级的时候,就会强调,一定要有个人博客,或github仓库,把不合格的人提前筛选掉。

TA是什么样的人

“知己知彼,百战不殆”。一定要了解对方,找到对方的兴趣点,对方想要的是什么,看到的是哪些点,从而找到双方的契合点。就算对方是在职状态,会上这个软件,就说明对方想要的当前公司并没有完全满足,所以才会骑驴找马。

如果快速了解对方呢?

一般是分以下步骤:

  1. 个人简历

  2. 个人博客

  3. github地址

如果2跟3没在简历体现,可以找候选人要。基本这三步一走,就能确定候选人的基本能力了。

如果2跟3实在没有,那就问一下,“你觉得你的优势在哪里”

后面就可以问,“你对公司有啥想了解的”,进行需求点的挖掘。

如果对方在职,那可以问对方看中所在公司的什么,寻找契合点。

下面是招聘高级前端的示例。

示例一:直接“吓跑”不合格者

示例二:再给一次机会,让对方陈述自己的优势。但最终TA没说出来,因而被淘汰了。

常见问题

下面列举一些候选人常见的重点关心的问题,可能带有互联网行业技术人特有的色彩,仅供参考:

  1. 公司做什么的

  2. 团队规模多大

  3. 技术栈是怎样的

  4. 入职后负责什么内容

  5. 公司发展前景

  6. 个人成长空间

  7. 是否双休

下面我来逐一分析解答上面的问题,同样带有互联网行业技术人特有的色彩,仅供参考:

  1. 公司做什么的,在IT行业里,基本是等于在问,“做产品还是做外包”。所以回答的要点是:做产品,不用驻场,不用出差,本地开发。

  2. 很多小公司的人渴望一个更大的团队,有更多的同行交流,学习更多东西,所以如果团队比较大,可以在招聘说明上重点强调。

  3. 技术栈的契合,一方面候选人入职成本低,另一方面也代表技术品味,技术习惯上的契合,所以这个在招聘说明上也需要强调。

  4. 在技术栈吻合的情况下,候选人会对工作内容有要求,比如有的喜欢独当一面,有的喜欢做移动端的开发,有的喜欢处理复杂的业务逻辑,此时要“对症下药”。

  5. 公司发展前景意味着在接下来的几个月里,公司能否按时发工资。候选人不想入职后,没多久又要面临裁员危机,再一次跳槽。所以,可以强调公司已经有的客户案例,说明公司发展稳定,不用担心朝不保夕。

  6. 有想法的候选人,除了在技术上追求,还会希望有其他方面的成长与收获,比如视野,社会资源,影响力等等,所以此时需要说明,除了钱与技术,公司还能给候选人什么。这里个人补充一句,如果候选人的要求只是钱,那太容易搞定了,那么我对他的定位也很简单,他就是个拿钱做事的人,也就一个劳动力,绝不是核心人员。

  7. 对于加班,或者说996,这个是IT界的常态。所以有些直率的候选人,自己会说,“钱给到位了,加不加班无所谓的”。但不能因此对那些在意是否双休的候选人有偏见,毕竟有的人效率高,也许他5天能做别人6天都做不完的事,所以他根本不用加班; 而且技术人也的确需要有给自己充电的时间,沉淀一些东西。所以我也表示理解。所以,这里就如实回答。

上面的这些问题,因为很常见,所以可以放到招聘说明里。一般软件对招聘说明(岗位描述)的字数限制是2000字以内,所以放这些内容是绰绰有余的。

在这种情况下,其实候选人在软件上提的问题并不会多。在确定能力基本OK,有个基本沟通后,就可以发出面试邀请了。

实战

搜集信息

BOSS直聘除了可以看简历信息,还有动态,以及问答,可以多方采集候选人的信息。

个人动态


图中信息重点是:21岁,4年经验。这怎么可能?

注意到名字旁边的图标,说明有个人动态,可以点进去看看。

已解答疑惑。

TA的问答

点击个人头像,除了显示个人简历外,在最下面,还有可能看到候选人对一些问题的回答(如果TA回答过的话)

可以点击阅读全部回答,进一步了解候选人。

沟通话术

开场白

点击“立即沟通”,BOSS直聘会自动发送一条信息给对方。

在这之后,我们可以手动打字,补充一下开场白。

要点是结合候选人的信息,一针见血地指出TA的亮点,或我方的亮点,其本质是,找到双方契合的点。

示例一:

示例二:

示例三:

追发

如果信息未读,其状态会显示“送达”。

如果信息已读,其状态会显示“已读”。

如果发出开场白后,信息变成“已读”,但在较长的一段时间内却未得到回复,说明候选人心里有一定的顾虑。

可以使用以下话术,追发信息。

  • 目前没有换工作的意愿吗

  • 有什么顾虑吗,可以尽管说

目前没有换工作的意愿吗

这句话是针对在职的人说的。考虑到时间因素(笔者进行招聘活动时是在年末,这个时间点大部分人只是在观望状态,要等到明年初拿了年终奖才会真正去跳槽),所以用这句话试探一下,候选人到底是下面三种情况的哪一种:

  • 已经在职了没有更新BOSS直聘上的工作状态

  • 打算明年初再换工作

  • 觉得我方不够有吸引力

有什么顾虑吗,可以尽管说

这句话比上面那句话更自然一点,起投石问路之用,可以引出对方的心声。

有时候选人不回复,或查看了公司的招聘资料后不主动发送信息,可能是候选人觉得“自己配不上”。

如果觉得候选人其实可以的,则此时需要循循善诱,给对方信心。

示例一:

示例二:

如果发送了上述话术对方还不回,那就不需要在此人身上浪费时间了,寻找下一个即可。

展示自己

当找到双方契合点,并且对候选人有一定的了解,通过简历,个人博客,github等方面确认对方通过初步筛选后,可以给对方提供一个问我方问题的机会。

我一般都是这么说的:你对公司方面有啥想了解的

这既是给对方增加安全感,也是放大吸引,同时增进对对方了解的一个方式。

示例如下:

案例分析

开场

互动

吸引力不足


这里我走入误区,用自己的思路去代入,“人多不好管理”,“人多自己只是螺丝钉”,思考方向偏差了。

及时调整


这里我换位思考了一下,给自己个台阶下,再及时调整方向抓住Code Reivew这个关键点,因为这是候选人的个人简历上说的,“想找一个Code Review的团队”,而这也是让对TA刮目相看的一点,所以我才这么坚持

转折


这里我强调我方的优势后,再次挖掘对方兴趣点,TA会使用这个软件,一定是所在公司有给不了TA的东西。

换个战场


因为我对TA另眼相看,所以我决定转战微信。

拉勾网

拉勾网的玩法会有些不同。上面的人不会聊太多,更多的是直接把简历发到邮箱,则此时应该根据简历中的邮箱地址,直接把招聘试题通过邮件发送经给候选人。

🐳从零开始Docker化Node.js应用

背景

给你一台新买的服务器(CentOS),相关账户及密码,一个基于Node.js开发的web应用源码包(zip),要求你在新机器上使用Docker的方式把应用部署起来。此时的你,并没有搞清楚什么是容器/镜像,也没记住几个相关的Linux命令,该怎么办?本文将帮助你摆脱困境

方案

流程

为达到最终目的,先来梳理一波流程:

  1. 把源码zip包上传至服务器
  2. 登录服务器
  3. 解压zip包
  4. 安装最新Docker
  5. 设置国内镜像加速器
  6. 编写Dockerfile
  7. 构建镜像
  8. 编写启动容器脚本
  9. 执行脚本,检查部署情况

下面将详细描述如何操作

文中服务器操作系统为CentOS 7,如果你的服务器不相符,操作细节可能会略有不同,需要另行查阅相关资料

rsync传输

假设:

  • 服务器地址为${ip}
  • 帐户为${user}
  • 密码为${pass}
  • 源码包为${zip}
  • zip包放到服务器的目录为${path}

则在本机源码包同级目录下,使用scp命令,把zip包传输至服务器的示例如下

rsync -avzP ./${zip} ${user}@${ip}:${path}

# 后面会提示输入密码

ssh登录

承接上文,ssh登录服务器示例如下

ssh ${user}@${ip}

# 后面会提示输入密码
# 第一次登录会提示保存ssh信息,输入yes即可

如果不想每次都输入地址/帐户/密码,可以写一个简单的自动登录脚本ssh.sh

# 创建文件
touch ssh.sh
# 赋予脚本可执行权力
chmod +x ssh.sh

ssh.sh内容如下,记得把${pass}, ${user},${ip}替换为真实数据

#!/usr/bin/expect
set timeout 30
set password ${pass}
spawn ssh ${user}@${ip}
expect "*assword:"
send "$password\r"
interact

执行脚本即可登录服务器😄

./ssh.sh

unzip解压

如上所说,源码包名为${zip},解压命令如下

unzip ${zip}

附带一句, 如果要用命令生成zip包,假设源文件目录为dist,要生成的zip包为dist.zip, 其命令如下

zip -r dist.zip dist
# 或简写为
zip -r dist dist

安装Docker-CE

CE意为Community Edition, 即社区版,免费; 与之对应的是EE,Enterprise Edition,  企业版,强调安全,付费使用。

  • 卸载旧版本的Docker

如果新机器上没有docker,跳至下一步,直接安装Docker的依赖

sudo yum remove docker \
                  docker-common \
                  container-selinux \
                  docker-selinux \
                  docker-engine \
                  docker-engine-selinux
  • 安装Docker的依赖
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
  • 安装Docker官方仓库
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
  • 更新仓库源
sudo yum makecache fast
  • 从仓库安装Docker-CE
sudo yum install docker-ce

配置加速器

使用 Docker 的时候,需要经常从官方获取镜像,但是由于显而易见的网络原因,拉取镜像的过程非常耗时,严重影响使用 Docker 的体验。

推荐使用DaoCloud的加速器

Dockerfile

所有环境配置已准备完毕,可以根据Node.js应用编写Dockerfile了

假设Node.js应用的启动命令为npm start, 监听端口为${app_port}

  • 创建Dockerfile
touch Dockerfile

Dockerfile内容如下

# 可以指定依赖的node镜像的版本 node:<version>,如果不指定,就会是最新的
FROM node:8

# 创建工作目录,对应的是应用代码存放在容器内的路径
WORKDIR /usr/src/app

# 把 package.json,package-lock.json(npm@5+) 或 yarn.lock 复制到工作目录(相对路径)
COPY package.json *.lock .

# 只安装dependencies依赖
# node镜像自带yarn
RUN yarn --only=prod --registry=https://registry.npm.taobao.org

# 把其他源文件复制到工作目录
COPY . .

# 替换成应用实际的端口号
EXPOSE ${app_port}

# 这里根据实际起动命令做修改
CMD [ "npm", "start" ]
  • 补充.dockerignore
touch .dockerignore

.dockerignore内容如下

node_modules
npm-debug.log

构建镜像

写好Dockerfile,就可以在Dockerfile所在目录构建镜像了。

命令如下。-t是为了给镜像加个标签,这样方便使用docker images命令时检索到

# ${your_name} 可以省略
# ${tag} 省略时为 latest
docker build -t ${your_name}/${image_name}:${tag} .

# 省略版本
docker build -t ${image_name} .

查看镜像

docker images

# 示例输出
REPOSITORY                      TAG        ID              CREATED
node                            8          1934b0b038d1    5 days ago
${your_name}/${image_name}    latest     d64d3505b0d2    1 minute ago

启动容器脚本

touch start.sh
chmod +x start.sh

start.sh会根据镜像新建一个容器并启动,内容如下

#!/usr/bin/env bash

container=${container_name}
image=${image_name || image_id}

docker run \
--rm \
-d \
-p ${host_port}:${app_port} \
--name $container \
$image

${host_port}是外网访问部署好的应用时对应的端口

${app_port}是容器内Node.js应用监听的端口

${image_name}是前面构建出的镜像的名字,可用docker images查看

${container_name}是给容器赋予的名字,方便docker ps命令时检索

--rm 容器退出后随之将其删除

-d 后台运行

-p 指定端口映射,前者是服务器器端口,也即外界访问你部署好的web应用的端口; 后者是DockerfileEXPOSE的端口

--name 指定容器名字

测试

访问刚刚启动的容器里的应用

curl -i localhost:${host_port}

# 示例输出
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
...

恭喜,部署成功👏

常用命令

  • 查看运行中的容器
docker ps
# 或使用新命令
docker container ls

# 示例输出
ID            IMAGE                                COMMAND    ...   PORTS
ecce33b30ebf  ${your_name}/${image_name}:latest  npm start  ...   49160->8080
  • 查看所有容器(包括已终止的)
docker ps -a
# 或使用新命令
docker container ls -a
  • 查看某容器内日志
docker logs -f ${container_id}
  • 进入某容器,并有shell执行环境
# 进入容器
# -i表示:交互式操作,-t表示:终端
docker exec -it ${container_id} bash
# 可通过输入 exit 退出
  • 停止容器
docker container stop ${container_id}
  • 启动已终止的容器
docker container start ${container_id}
  • 删除容器
docker container rm ${container_name || container_id}
  • 查看镜像
docker images
# 或使用新命令
docker image ls
  • 删除镜像
docker image rm ${image_id}

FAQ

  • 不会vi,不懂怎么在服务器编辑Dockerfile等文件,怎么办?

可以参考本文,把文件在本地创建好,再通过scp把创建的文件跟源码包一起上传到服务器

  • 为什么使用yarn?

因为yarn的速度比npm更快,而docker里的node镜像自带了yarn, 正好鼓励大家使用yarn

  • 如果项目有全局依赖,如bower,Dockerfile怎么写?

可以先安装其他全局依赖,再使用yarn安装,记得命令写在一个RUN指令里, 下面是部分示例

RUN yarn config set registry https://registry.npm.taobao.org \
    && yarn global install bower \
    && bower i --allow-root \
    yarn

参考

本文最早发布在掘金,现迁移至github。

🕸捕获与改写HTTPS请求

前言

本文站在 macOS 用户的角度下,分享一下对 HTTPS 进行请求拦截、对响应进行修改的经验。

要注意的是,本文介绍的工具虽然一定程度上对 Windows 用户也适用 ,但并非所有工具都是免费的。

Proxyman

Proxyman可以免费使用,在安卓/IOS手机上也有相应的解决方案,如果只是监测请求,查看 API 请求头及响应体,这个足够了。

Charles

Charles 是收费的,而且要安装 Java 环境,但它厉害的地方在于,可以改写网络(如修改响应头),因此值得一买。

唯一的缺点就是,官方文档不太好友,界面有一定上手难度,好在文本已有图文并茂的说明。

安装与设置

  • 安装SSL证书

  • 点击安装后,在界面搜索 Charles,找到刚刚安装的证书,点击 总是信任

  • SSL代理设置


改写网络

下面的例子展示了如何改写 HTTPS 请求的响应头。

  • 点击左上角,Structure
  • 找到想改写的请求,右键,点击 Breakpoints

  • Breakpoints Settings

  • 双击编辑详情

  • 取消 Request 的勾选

  • 刷新页面,请求将会被拦截,处理 Pending 状态

  • 此时可以编辑响应

  • 最终,客户端收到的是被改写后的响应

参考

🔒免费开启HTTPS

前言

本文将分享几种为站点启用HTTPS的方法,内容包括:

  1. 免费证书的获取及安装
  2. 为Node.js应用开启HTTPS
  3. 为Nginx开启HTTPS

前提准备

  1. 一台拥有外网IP的服务器(假设操作系统为: CentOS 7.4)
  2. 一个域名(解析到上述服务器的IP)(假设域名为: www.example.com)
  3. SSL证书

证书

Symantec

阿里云可以购买有效期一年的免费证书,审核时间大概10分钟,优点是域名不用备案。强烈推荐。
image.png

购买后,点击下载
image.png
即可获得证书

letsencrypt

虽然是免费的,但安装比较费时间,如果网络不太好,会很捉急。

其证书获取方式后文会讲。

Node.js应用

下面以koa2构建node.js应用 + Symantec为例,说明如何为接口开启https。

在项目根目录新建文件夹

mkdir ssh

把Symantec的证书文件放到该目录下。

假设两个文件的名称分别为: server.key  server.pem 

新建index.js文件

touch index.js

编写以下代码

const koa = require('koa');
const app = new koa();
const port = process.env.PORT || 3000

const https = require('https')
const fs = require('fs')
const sslOptions = {
  key: fs.readFileSync('./ssl/server.key'),
  cert: fs.readFileSync('./ssl/server.pem')
}

https.createServer(sslOptions, app.callback()).listen(port, () => {
  console.log('server start up at https://localhost:' + port)
})

启动应用即可看到效果。

node index.js

Nginx代理静态资源

下面讲解如何在CentOS机器上,使用Nginx + letsencrypt为静态网站开启HTTPS。

注: Ubuntu版本参考这里

1.首先更新yum仓库(需要等待一段时间)

yum update

2.安装Nginx

yum install nginx

可以先跑一下Nginx

nginx

然后在浏览器访问www.example.com, 如果看到Nginx的欢迎页, 说明安装成功

3.安装letsencrypt以获取免费证书

yum install letsencrypt

修改Nginx配置:

vi /etc/nginx/nginx.conf

找到80关键字,强制对80端口的请求重定向到HTTPS协议

server {
  listen 80;
  location / {
    # 补充下面一行
	  return 302 https://$host$request_uri;
   }
}

找到443关键字, 把注释打开, 即把#去掉

# HTTPS server
# 把下面的注释全部打开
server {
  listen       443 ssl http2 default_server;
  server_name  localhost;

  ssl_certificate      cert.pem;
  ssl_certificate_key  cert.key;

  ssl_session_cache    shared:SSL:1m;
  ssl_session_timeout  5m;

  ssl_ciphers  HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers  on;

  location / {
    root   html;
    index  index.html index.htm;
  }
}

假设静态文件的根路径为 /path/dir, 把它写在Nginx配置文件里

location / {
  # 修改下面一行
  root   /path/dir;
  index  index.html index.htm;
}

根据路径以及域名,生成证书及密钥

sudo letsencrypt certonly -a webroot --webroot-path=/path/dir -d www.example.com

成功后, 控制后会打印出生成文件的路径, 把它们写在Nginx配置文件里

ssl_certificate "/etc/letsencrypt/live/www.example.com/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/www.example.com/privkey.pem";

4.查检openssl版本

openssl version

如果版本大于等于1.02, 那就是ok的, 否则, 需要升级openssl.

如何升级? 如果使用最新内核的Linux操作系统, 是不会遇到这种问题的, 真的遇到了, 自己google吧😄

5.重启Nginx

nginx -s reload

重新访问你的站点, 发现地址栏多了一把绿色的锁, 恭喜, 这正是使用HTTPS协议的标志!

6.定期更新证书

因为Let’s Encrypt 的证书90天就过期了, 所以可以写个定时任务

crontab -e
0 0 * * 1 /usr/bin/letsencrypt renew >> /var/log/letsencrypt-renew.log
5 0 * * 1 /bin/systemctl reload nginx

每星期1的0点0分执行更新操作,0点5分执行Nginx 重启

📝2019年中总结

前言

有两个月没写总结了,不管原因如何,总要把欠了的补上。这个周末抽出时间,好好整理一下前两个月的工作内容以及感想,内容涉及:

  1. Github
  2. Flutter
  3. 产品思维与用户意识
  4. 新的技术观
  5. 新的团队观
  6. 自我认可

Github

具体技术点,已写在文章中:🤖自动化的Github Workflow

自做开源组件以来,文档、预览、构建、发布这几个环节,一直都是很繁琐,手工操作多,易出错,十分不得要领。

于是参考vue/nuxt/element, 一点一点地摸索,反复地尝试,终于找到了适合我们的github wokflow。

于是我们就对所有组件进行升级改造,相关脑图如下:
image.png

Flutter

滴雀APP(意为滴普语雀)下载地址

这个星期,我们由Flutter开发的APP终于登录App Store了。这是我们第一款由Flutter开发的应用,我也经历了第一次从0到1完整的上架的过程。

我在语雀官方论坛上发布相关信息,也获得了官方人员的认可。
image.png

我感触比较深的是两点:

  1. 增长了信心
  2. 实践才是王道

信心

年初的时候,与某团队进行技术交流,听说他们不但做了小程序,还做了APP,甚至他们已经完成了从RN到Flutter的转型。我们当时只涉及H5,并没有过APP方面的积累,因此虽然表面不动声色,其实心里大吃一惊,不免有些没底气。

时至今日,我们开发的APP已被当初那个团队的人使用着,我心里已不虚。

实践

如今很多社区、论坛、公众号,都在报道Flutter的相关信息,各种比较Flutter与其他技术的优缺点; 身边很多人也早就跃跃欲试,却一直说没时间而停滞不前,或因为做不出最佳选择而犹豫不决。

而我们已经在实践,并且上生产了。要想真正了解一件事,唯有通过亲身实践。所以,如果真的想尝试,赶紧行动起来吧,不用再犹豫了,也不需要把没时间当借口。

产品思维与用户意识

通过实践,我认为做产品有两点很重要:

  1. 搞清楚用户最想要解决的是什么问题
  2. 自己用起来

MVP

第1点也就是mvp,但这点理论上说起来简单,做起来却难。

首先,用户的声音不一定能听到,就算听到了,也可能因为沟通表达的问题,理解有偏差。

其次,很多时候产品规划都是很宏大的,功能都是很齐全的,从0到1的过程中,很容易在这种规划中迷失。

同时,优先解决核心问题,先实现最小功能闭环,思路是没错,但这样的产物可能过于简陋,因此有人可能会觉得这不像是产品,而持否定意见。

吃自己的狗粮

所以,第2点就很重要。自己做的产品,自己用起来,也即“吃自己的狗粮”。

自己去动手前,想一想,这个东西做出来自己会不会用?如果答案是否定的,那就先别动手,重新再想一想。

自己作为用户,才能真正地培养用户意识。

最后还要注意的是,产品越早投入使用越好,这样可以根据用户反馈,快速调整与完善。

新的技术观

对于技术,我新增了以下方面来观察与思考:

  1. 全局
  2. 金钱
  3. 用户
  4. 开源

全局

全局意味着要有整体思维,从团队、从整条生产链路来思考。

现在市面上很多媒体,过分强调个人或单个环节,导致人们普遍过分关注局部,而忽略了整体。

比如有很多开发技术方面的“练级攻略”,给人一种错觉,似乎掌握了这些技术,就能横扫江湖,走向巅峰。但其实开发只是软件工程的一个环节罢了,如果不能掌握整体,在局部再登峰造极,也会有捉襟见肘的时候。

再比如某些教程,说是“教你从0到1开发一个APP”,但其实也只是聚焦在开发领域,也只是能在自己的机器上构建一个APP,安装在自己手机里而已。真正要上线到生产,让别人也能安装使用,同时保持后续的更新迭代,前前后后还有很多内容要涉及,并不是学了教程,开发好了,就完事了。

使用全局的思维来看待这些事情,这样才能看得更清楚、更透彻。

金钱

其实很多技术是可以用金钱来买的。这个交易的本质是:用金钱来买时间。

下面举些例子:

  1. 我们没有相关新技术的知识,我们可以自己去预研、去实践,然后自己再写一份教程,也可以直接买付费的教程,让大家快速进入系统的学习
  2. 我们要想一个功能,目前没有,可以自己去实现,也可以买第三方服务
  3. 我们缺少具备某些经验的人员,可以内部花时间培养,也可以直接从外部招聘有相关经验的人员

上面的例子都是常见的待决策的情况,前者都是自己亲力亲为,花费的是时间; 后者则是做资源整合,付出的是金钱。值得一提的是,时间也是金钱,也是成本,因为员工是要发工资的,这一点千万不要忽略了。

遇到上述情况如何决策,需要具体情况具体分析。

只不过,在筛选简历时,每每遇到一些人描述自己“全栈工程师”,但其实只是泛泛而谈时,我都在想,我为何不直接招一个专职的前端、一个专职的后端?这对于企业而言,一个月增加的成本,微不足道。这种类似的**,池建强在《Flutter 要全平台制霸?我看悬》一文也提到了。

用户

没有愚蠢的用户

产品上线后,就会收到用户的反馈。有可能产品的第一个界面,就让用户困惑了。此时技术人员常见的反应是:这都不懂,真笨!

请尽早把用户愚蠢的想法抛弃掉。用户遇到了任何问题,首先应该想想,是不是产品有需要优化的地方。技术是用来解决问题,提升效率的,不是用来彰显自己高端、与众不同的,做技术的千万不能曲高和寡。

用户第一

很多时候技术人员评估的事情的优先级,是按照技术难易程度、有趣程度而排列的,但其实上线后,最重要的,是倾向用户的声音,根据反馈进行开发调整。

也许你觉得做个炫酷的动画是很有挑战性、很有趣的事情,但用户也许更关心产品首页的错别字。这种情况下,请优先改正错别字。

开源

总有一些技术团队在讲自己是设计这个系统的,怎么实现的,讲了一大堆,即没有附上代码地址,说什么与内部业务联系太紧密,不方便开源。

我也写过一些文章,所以我清楚,很多时候文字并不能完整地表达意图,更别说一个大型系统的设计。也许文章作者觉得自己写出关键点了,但读者的知识背景参差不齐,难保文章没写出来的,才是读者更需要的。因此,软件设计没有附上相关的可执行产物,我认为做的就是不到位。

很多时候,大家重复造轮子,就是因为资源不共享,前人的成果没法复用。

vue、k8s都开源了,把我们的实践成果开个源,其实也什么大不了的。如果涉及商业机密,把相关内容替换成demo就好了。说到底,分享的意愿不够罢了。

如果我们实践了想分享,一定会开源,不搞虚的。

新的团队观

去年,我的团队理念是,基于团队现有成员,把每个人打造出我想象中的样子。

当时,我看人主要还是看技术能力; 对于一些技术能力并不符合预期,但态度好的人,我也愿意招聘进来,因为可以培养。

今年,我更强调的是适者生存,也即,团队是有淘汰的,跟不上步伐的人,只能离开。

我现在招聘更愿意招已经技术能力过关,不太需要进行技术培养的人,也即宁缺勿滥,纵然这样很可能一个月也招不到一个人。

另外,我还会注重对软实力的考察,比如热情、目标规划、自我定位、思维方式等,我希望团队成员是自我驱动的,最终能成为自己想要的样子。

一句话总结:去年我想让团队的所有人变得合适; 现在我只想要合适的人留在团队里。

自我认可

在去年北京的项目实战之后,我隐约觉得,我已经获得了足够的外界认可。

回来广州的大半年,我愈发确定,接下来我寻求的,是自我认可。

这是一套自己给自己的评判标准以及打分机制:

  • 也许外界觉得这样差不多了,但我觉得不够
  • 也许外界觉得这样做不对,但我相信自己

最后,来点鸡汤:愿你不受外界环境摆布,坚定自己的意志,坚持做你最初想做的事。

⚡️Github集成Netlify:快速预览PR

前言

本文将介绍使用Netlify为Github中的开源项目进行持续部署:

每当项目发生一个Pull Request时,Netlify会构建相应的代码,并自动回复,在回复内容提供一个在线链接,可供开发者快速预览此次改动的效果。

开始使用

image.png

  • 选择一个仓库

image.png

  • 配置构建信息

image.png

这里注意,选择了 dev 分支,则后面 preview deploy 只会对 dev 分支进行的 pull request 有效。

  • 还可以配置环境变量

image.png
则在Node.js应用中,可以通过 process.env.PUBLIC_PATH 获取值

修改名字

第一次部署时,默认的名称是随机生成的,可以修改,方便识别并访问
image.png

image.png
image.png
名字即修改成功,这样访问的域名就更好记了。

设置通知

image.png
选择事件为“部署成功后”
image.png
以后项目有Pull Request时,Netlify就是自动构建,并在成功后自动给Pull Request添加回复👏
image.png

集成Gitlab

Netlify也可以集成Gitlab上的私密仓库,其操作与本文对Github的描述别无二致,唯一不同的是,部署成功进行一个评论时,Gitlab需要access token
image

可以登录Gitlab后,点击头像 -> 用户设置 -> 访问令牌里获取,范围勾选api
image

生成后复制到Netlify即可

原文地址

排序

指引

分析排序算法的性能要点,考虑三种情况下的时间复杂度:

  1. 最好情况
  2. 最坏情况
  3. 平均情况

如果是基于比较的算法,还可以考虑比较与交换的次数。

除了性能,评判排序算法还要考虑:空间复杂度、稳定性

排序算法在实现的基础上,还需要额外考虑以下情况:

  1. 有序的输入如何处理(最好的情况)
  2. 如何避免最糟糕的情况
  3. 有重复主键的情况

以上是做算法优化时的基本思路。

相关资料

  1. https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
  2. 算法4练习题参考答案

初级排序

主要比较:冒泡、选择、插入

编码要点:

  • 冒泡排序:可以设置哨兵,如果当前不发生交换,说明已经有序(处理有序输入)
  • 选择排序:查找最小元素时,记录数组下标即可,不需要保存具体的值,这是数组的特点
  • 插入排序:提前保存待排序元素的值,可以直接进行元素移动,减少交换次数

三种排序算法的时间复杂度都是O(n2),但实际上,插入排序更为优秀:

  1. 同冒泡排序一样,可以有效地处理有序输入,这点选择排序无法做到
  2. 比冒泡排序更少的交换次数
  3. 稳定的

因而,插入排序是小数据规模时的首选排序算法,熟练掌握它可以为后续学习高级排序算法打下基础。

希尔排序

希尔排序是在插入排序上改进的。插入排序只是对相邻的元素一个个地比较,而希尔排序就是在这里作文章:每次间隔 h 个元素进行比较

编码要点:构建 h 序列,再依次减少 h 直至为 1,其余结构参考插入排序。

其数量增长级别是介于 线性对数 与 平方 之间的。

归并排序

编码要点:

  1. 整体思路是分而治之,划分左右,递归调用。从代码形式上讲,同二叉树的后序遍历
  2. 重点在于 merge 函数,如何把数组中两个有序的部分合并在一起

性能要点:

  1. 稳定的
  2. 与输入无关
  3. 需求与输入规模相同的辅助空间(也即内存消耗大)

优化思路:

  1. 处理有序输入
  2. 当分治到小数组(长度为 < 15)时,使用插入排序
  3. 避免重复的创建临时数组
  4. 减少数组的复制

额外:还有自底向上的写法(迭代写法)。

快速排序

编码要点:

  1. 整体思路同样是分而治之。先进行分区,再左右递归分区。从代码形式上讲,同二叉树的先序遍历
  2. 重点在于分区函数 partition,使得 数组左边 <= pivot <= 数组右边
  3. 与归并排序不同之处在于,pivot 所在的位置认为是有序的,因而递归调用时不应该包含 pivot 所在位置

易错点:

  1. 只进行一次交换(必须左右交叉时才退出外层循环)
  2. 最后返回的索引位置不对

性能要点:

  1. 重点在于切分数组尽可能地平衡(最好的情况是每次等分)
  2. 重复值的处理

优化思路:

  1. 先随机打乱数据,减少出现极端分区的可能
  2. 三向切分,处理相同的 key 的情况,具体代码实现
  3. 小数组时使用插入排序

强化理解:

堆排序

背景:

  • 堆排序是基于优先队列的实践而发明的排序算法,而优先队列是为了解决大规模输入下TopK问题而创造的数据结构
  • 场景:大规模N(接近无限)个输入时,求TopK,无非两种算法
    • 算法一:对N排序,输出TopK
      • 这种算法要先存储N,对空间要求过高
    • 算法二:保留K个输入,不断与后面新的输入进行比较,再做删除与插入操作,更新TopK
      • 如果是普通的线性数据结构,插入与删除,必有一个操作时间复杂度是O(K)
        • 随机插入,每次删除最值,则删除时间复杂度为O(K)
        • 有序插入(单调栈),原地删除最值,则插入时间复杂度为O(K)
      • 而基于堆的优先队列则可以做到插入与删除操作时间复杂度为O(lgK)

那么它是怎么实现呢?

首先,它的数据结构是基于数组实现的二叉堆:

  • 大顶堆:任意根节点大于等于左右子节点(小顶堆则任意根节点小于等于左右子节点)
  • 数组第一个空间不存储值,则节点的索引关系为
    • 左子节点:2 * i
    • 右子节点:2 * i + 1
    • 父节点:i / 2

其次,优先队列的插入与删除操作实现算法:

  • 既然底层是数组,为了减少元素的移动,最好拿首尾元素做文章
  • 插入是放到数组末尾
    • 这有可能破坏堆原来的有序性,故插入元素可能需要“上浮”
  • 删除是释放数组首位(下标为1),并把放尾元素放到首位
    • 这会破坏堆原来的有序性,则新的首位元素需要“下沉”

所谓堆排序,就是构造基于数组二叉堆的优先队列,然后再逐个输出最值。

编码要点:

  1. 自底向上调用 sink
  2. 只需要对有子节点的节点调用 sink
  3. 堆有序后,再销毁堆,逐个把最值与末尾元素交换——这样就不用额外的辅助空间

易错点:

  1. 忘记了数组下标0不存储,对数据的长度判断不对
  2. 忽略判断了对右子节点是否存在,数组溢出

性能要点:

  1. 最优地利用了时间与空间
  2. 对缓存不友好(因为数组很少和相邻的元素进行比较,故缓存命中率低——CPU局部性原理)


sink-based heap construction uses at most 2_n_ compares and at most n exchanges

练习题:

  1. 动态中位数。关键在于,两个堆的长度要保持动态平衡。

应用

排序如此有用的一个主要原因是,在一个有序的数组中查找一个元素要比在一个无序的数组中查找简单得多。

快速排序是最快的通用排序算法。它之所以最快是因为它的内循环中的指令很少(而且它还能利用缓存,因为它总是顺序地访问数据)。因此,在大多数实际情况中,快速排序是优先考虑的选择。

问题的归约:每次你在使用解决问题A的方法来解决问题B时,你都是在将B归约为A。这是在应用算法时很通用的**。

经典:

  1. 三数之和:很关键的一点在于,要去重,思路是对排序后的数组,跟前一个元素判断。
  2. quick select
  3. 任务调度:N个任务,要求平均完成时间最短。使用最小优先队列,优先处理最短时间的任务。
  4. 负载均衡:M个任务N个处理器,要求完成所有任务的时间最少。使用最小优先队列,优先把最长时间的任务分配给任务用时最少的处理器。
  5. Kendall tau距离
  6. A*

🤖如何写一个GithubApp

温馨提示

在继续正文前,先澄清一下:

  1. Github App 是需要部署的,Github 并没有提供托管服务。
  2. 在 Github Marketplace 上架并非使用 Github App 的必要条件。也即,没有在上面展示,你一样可以安装使用自己的 Github App。

快速开始

  1. npx create-probot-app your-app
  2. cd your-app
  3. yarn
  4. yarn dev
  5. register a Github App
  6. install the Github App

实际开发并已上架的App,用于给 pull request 自动打标签:https://github.com/marketplace/auto-add-label

开源地址:https://github.com/levy9527/auto-add-label

安装应用

进入 https://github.com/settings/apps

image

点击安装

image

image

修改权限

进入https://github.com/settings/apps

image

image.png

别忘了,检查下是否订阅了相关事件
image.png
如果在初始化后,并且已经有用户(比如自己)安装了 Github App,再更改权限,则需重新获得用户的授权

重新授权入口如下
image.png

进入后会有提示
image.png

重新授权
image.png

webhooks

可以在 Github App 的 Advanced 页面中查看 webhooks 接收情况
image.png

当然也可以使用 https://smee.io 查看
image.png

异常

400

image.png

打开项目根目录下的 .env ,复制 WEBHOOK_SECRET 的值

进入 Github App 设置页
image.png

修改 Webhook secret 后保存
image.png

根据经验,每次修改 Webhook 时,最好同时再编辑一次 Webhook secret(即使值没有发生变化),以避免以上情况的发生。
image.png

部署

推荐使用 Heroku:

需要指出的一点是,使用 Heroku 时网络体验稍差,不知道是否是我的网络问题,表现在两个方面:

  1. 我的翻墙工具需要切换为 蓝灯,否则 Heroku Dashboard 几乎无法打开
  2. 在 iTerm2 里使用 Heroku CLI 命令时,很容易出现报错 ENOTFOUND: getaddrinfo ENOTFOUND api.heroku.com api.heroku.com:443 ,需要多次尝试,靠运气成功

可以放心的是,部署后的应用本身,是很稳定的。

参考

✅使用jest进行测试驱动开发

前言

本文将使用jest进行测试驱动开发的示例,源码在github。重点说明在开发中引入单元测试后开发过程,以及测试先行的开发思路。

本文的重点是过程以及思维方法,框架以及用法不是重点。

本文使用的编程语言是javascript,思路对其他语言也是适用的。

本文主要以函数作为测试对象。

环境搭建

假设项目结构为

.
├── README.md
├── package.json
├── src
├── test
└── yarn.lock
  • 安装依赖
yarn add --dev jest
  • 打开package.json, 修改scripts字段
"scripts": {
  "test": "jest"
}

之后把测试文件放在test文件夹下,使用yarn test 即可查看测试结果

开发

现在要开发一个函数,根据传入的文件名判断是否为shell文件。

先做好约定:

  1. shell文件应该以 .sh 结尾
  2. shell文件不以 . 开头
  3. 函数为名 isShellFile

下面来看下开发步骤是怎么样的。

文件初始化

在src目录下新建 isShellFile.js

touch isShellFile.js

然后一行代码也不写,在test目录下新建 isShellFile.test.js

可以注意到,测试文件的名与源文件名类似,只是中间多了个 .test

touch isShellFile.test.js

第一个用例

打开测试文件 test/isShellFile.test.js ,编写第一个用例,也是最普通的一个: bash.sh

const isShellFile = require('../src/isShellFile')

test('isShellFile', () => {
  // 调用函数,期望它返回值为 true
  expect(isShellFile('bash.sh')).toBeTruthy()
})

运行 yarn test , 结果如下:

 FAIL  test/isShellFile.test.js
  ✕ isShellFile (2ms)

  ● isShellFile

    TypeError: isShellFile is not a function
    ^^^

      3 | test('isShellFile', () => {
      4 |
    > 5 |   expect(isShellFile('bash.sh')).toBeTruthy()
        |          ^
      6 | })

失败是意料之中的,因为 src/isShellFile.js 一行代码也没写,所以测试代码中第5行 isShellFile 无法进行函数调用。

完善源文件src/isShellFile.js

module.exports = function(filename) {

}

这样 isShellFile 就可以作为函数被调用了。

再运行 yarn test

 FAIL  test/isShellFile.test.js
  ✕ isShellFile (7ms)

  ● isShellFile

    expect(received).toBeTruthy()
    ^^^
    Received: undefined

      3 | test('isShellFile', () => {
      4 |
    > 5 |   expect(isShellFile('bash.sh')).toBeTruthy()
        |                                  ^
      6 | })

又报错了,但这次报错原因跟上次不同,说明有进步。

这次报错原因是,期望函数调用返回值为真 , 但实际没有返回真 。

这是当然的,因为在源文件中,根本没有写返回语句。

为了让测试通过,修改 src/isShellFile.js

 module.exports = function(filename) {
+  return true
 }

运行 yarn test , 测试通过了!

 PASS  test/isShellFile.test.js
  ✓ isShellFile (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.548s
Ran all test suites.

把上述修改,提交到版本控制系统中。

git add package.json yarn.lock src test
git commit -m 'feat: init jest test case'

第二个用例

观察我们的测试用例,发现太简单了,只有正面的用例,没有反面的、异常的用例

test('isShellFile', () => {
  expect(isShellFile('bash.sh')).toBeTruthy()
})

test/isShellFile.test.js 添加一个反面的用例

 test('isShellFile', () => {
   expect(isShellFile('bash.sh')).toBeTruthy()
+  expect(isShellFile('bash.txt')).toBeFalsy()
 })

运行 yarn test

(可以发现,在开发过程中需要反复执行上述命令,有个偷懒的办法,执行yarn test --watch,即可监听文件变化,自动执行测试用例)

 FAIL  test/isShellFile.test.js
  ✕ isShellFile (6ms)

  ● isShellFile

    expect(received).toBeFalsy()
    ^^^
    Received: true

      4 |
      5 |   expect(isShellFile('bash.sh')).toBeTruthy()
    > 6 |   expect(isShellFile('bash.txt')).toBeFalsy()
        |                                   ^
      7 | })

报错了,期望返回假,但函数返回的是真。这是因为,源文件中, isShellFile 函数永远返回真!

完善 src/isShellFile.js 逻辑

 module.exports = function(filename) {
-  return true;
+  return filename.indexOf('.sh') > -1
 };

测试通过了

 PASS  test/isShellFile.test.js
  ✓ isShellFile (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.568s
Ran all test suites.

把上述修改提交到版本控制系统

git commit -am 'fix: 函数永远返回真的bug'

第三个用例

我们再添加一个用例,这次考虑特殊情况: .sh 这种文件,不算是shell文件。

修改 test/isShellFile.test.js

   expect(isShellFile("bash.sh")).toBeTruthy();
   expect(isShellFile("bash.txt")).toBeFalsy();
+  expect(isShellFile('.sh')).toBeFalsy()

测试不通过

 FAIL  test/isShellFile.test.js
  ✕ isShellFile (8ms)

  ● isShellFile

    expect(received).toBeFalsy()
    ^^^
    Received: true

      5 |   expect(isShellFile("bash.sh")).toBeTruthy();
      6 |   expect(isShellFile("bash.txt")).toBeFalsy();
    > 7 |   expect(isShellFile('.sh')).toBeFalsy()
        |                              ^
      8 | });

说明逻辑待完善,修改 src/isShellFile.js

 module.exports = function(filename) {
-  return filename.indexOf(".sh") > -1;
+  let index = filename.indexOf(".sh");
+  return index > -1 && index != 0;
 };

测试通过(为精简文章内容,后面不再展示测试通过的输出),提交代码。

git commit -am 'fix: .sh应该返回false'

第四个用例

按照第三个用例的逻辑, .bash.sh 也不应该是shell文件,那么函数是否能正确判断呢,测试便知。

修改 test/isShellFile.test.js

   expect(isShellFile('.sh')).toBeFalsy()
+  expect(isShellFile('.bash.sh')).toBeFalsy()

测试不通过

 FAIL  test/isShellFile.test.js
  ✕ isShellFile (3ms)

  ● isShellFile

    expect(received).toBeFalsy()
    ^^^
    Received: true

       6 |   expect(isShellFile("bash.txt")).toBeFalsy();
       7 |   expect(isShellFile('.sh')).toBeFalsy()
    >  8 |   expect(isShellFile('.bash.sh')).toBeFalsy()
         |                                   ^
       9 | });

说明逻辑待完善,修改 src/isShellFile.js

 module.exports = function(filename) {
   let index = filename.indexOf(".sh");
-  return index > -1 && index != 0;
+  return !filename.startsWith('.')  && index > -1;
 };

测试通过,提交代码。

git commit -am 'fix: .开头的文件不算sh文件'

第五个用例

再考虑一种情况,如果 .sh 出现在中间呢?如 bash.sh.txt , 它不应该是shell文件,来看看函数是否能通过测试。

修改 test/isShellFile.test.js

   expect(isShellFile('.bash.sh')).toBeFalsy()
+  expect(isShellFile('bash.sh.txt')).toBeFalsy()

测试不通过

 FAIL  test/isShellFile.test.js
  ✕ isShellFile (5ms)

  ● isShellFile

    expect(received).toBeFalsy()
    ^^^
    Received: true

       7 |   expect(isShellFile('.sh')).toBeFalsy()
       8 |   expect(isShellFile('.bash.sh')).toBeFalsy()
    >  9 |   expect(isShellFile('bash.sh.txt')).toBeFalsy()
         |                                      ^
      10 | });

说明逻辑待完善,修改 src/isShellFile.js

 module.exports = function(filename) {
-  let index = filename.indexOf(".sh");
-  return !filename.startsWith('.')  && index > -1;
+  let index = filename.lastIndexOf(".");
+  return !filename.startsWith('.')  && filename.substr(index) == '.sh';
 };

测试通过,提交代码。

git commit -am 'fix: .sh必须在结尾'

重构

我们来观察目前 src/isShellFile.js 的函数逻辑

module.exports = function(filename) {
  let index = filename.lastIndexOf(".");
  return !filename.startsWith('.')  && filename.substr(index) == '.sh';
};

对于 .bashrc 这样的文件,并不是shell文件,因为它是以 . 开头的。

则通过 filename.startsWith('.') 判断即可,前面的函数调用 filename.lastIndexOf(".") 是多余的。也即,目前的函数判断逻辑不够简明。

下面是一种优化思路:

module.exports = function(filename) {
  return !filename.startsWith('.')  && filename.substr(filename.lastIndexOf(".")) == '.sh';
};

测试通过,提交代码

git commit -am 'refactor: 优化逻辑'

注意,这个重构示例的重点是:

  1. 先完成功能,再重构
  2. 重构必须要有测试用例,且确保重构后全部测试用例通过

至于其他方面,见仁见智,并不是重点。

结论

本文通过代码实例,践行了测试先行的理念。

文中的代码实现不是重点,而是开发过程。

文中 文件初始化 及 第一个用例 的内容,尤其值得回味,它体现了两个思路:

  • 总是在有一个失败的单元测试后才开始编码
  • 用必要的最小代码让测试通过

总的来看,TDD总是处于一个循环中:

  1. 编写用例
  2. 测试失败
  3. 编写代码
  4. 测试成功
  5. 提交代码
  6. 重复以上

通过这样,功能的实现每次都是最小成本的,功能也是有步骤地、通过迭代完成的,而不是一步登天。

更关键的是,完善的测试用例,是开发者的“守护天使”,有了它们,以后在添加新功能时,修改/重构代码都有了可靠的保障,让开发者可以充满信心,code with confidence😎!

扩展

使用babel

要想使用import/export语法,需要安装babel相关依赖

  • 安装依赖
yarn add --dev babel-jest @babel/core @babel/preset-env
  • 在项目根路径新增配置文件 babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
};
  • 重新启动测试
yarn test --watch

为什么使用jest

因为这是vue官方工具链的一部分, 同时也可以为后续的组件测试作准备。

更多请点击查看2019-06-04尤雨溪的vue技术分享

🚀Github集成TravisCI:自动发布

前言

已经有阮一峰老师的持续集成服务 Travis CI 教程,为什么还要写这篇文章?

原因有二:

  1. 文章内容有些过时
  2. 文章覆盖度不够,有些实践细节没写出来

由于以上原因,纵然可以笔者很快在Github集成Travis CI并成功构建,但在发布时却踩了一些坑,折腾一波才终于发布成功。故写下此文,旨在补充更多的细节,帮助他人少走弯路。

正文

免费购买Travis CI应用

点击 https://github.com/marketplace/travis-ci,登录后免费购买(开源项目集成Travis CI不收费)。

选择关联仓库

选择个人或组织名下需要关联Travis CI的Github仓库。

已经设置过的,想进行修改,可以在Github的 Personal settings-> Applications 中进入。

image.png

编写CI文件

在项目根目录下新建 .travis.yml 文件

touch .travis.yml

发布到github pages

下面展示一个可以发布到gh-pages的例子,可以稍做修改,复制粘贴使用。

该示例包含了:

  • 指定node.js版本
  • 使用yarn进行安装依赖及构建
  • 对安装需要的依赖进行了缓存
  • 设置了两个不含敏感信息的环境变量
  • 设置了一个含有敏感信息的环境变量
  • 把构建生成的文件部署至github pages
language: node_js
node_js:
- lts/*
env:
- API_SERVER=https://easy-mock.com/mock/5c1b3895fe5907404e654045/femessage-mock PUBLIC_PATH=http://levy.work/nuxt-element-dashboard/
# 默认是yarn, 如果有yarn.lock的话
install:
- yarn
# 默认是 yarn test
script:
- yarn build
cache: yarn
deploy:
  provider: pages
  skip-cleanup: true
  keep-history: true
  local-dir: dist
  on:
    branch: master
  github-token: $GITHUB_TOKEN

下面对文件进行说明。

language: node_js
node_js:
- lts/*
  • 第1行指定了构建环境为node.js
  • 第2、3行指定使用node.js最新的LTS版本
env:
- API_SERVER=xxx PUBLIC_PATH=xxx

上面是设置两个环境变量。

注意,一次构建中传多个环境变量,必须写在同一行,使用空格分开。

env:
- API_SERVER=xxx
- PUBLIC_PATH=xxx

如果写成上面的形式,则会变成两个构建,每一个构建中只有一个环境变量。

install:
- yarn
script:
- yarn build
cache: yarn

上面指定使用yarn进行安装依赖,安装好后执行 yarn build 命令; 为yarn的依赖加速安装,开启了缓存。

下面是最关键的部署配置。

deploy:
  provider: pages
  github-token: $GITHUB_TOKEN
  skip-cleanup: true
  keep-history: true
  local-dir: dist
  on:
    branch: master
  • 第2行指定部署到Github Pages,即仓库的 gh-pages 分支,请确保仓库的pages分支是 gh-pages , 相关操作可以看这里
  • 第3行指定保留构建后的文件
  • 第4行指定每次部署会新增一个提交记录再推送,而不是使用 git push --force
  • 第5行指定构建后要部署的目录
  • 第6、7行指定 master 分支有提交行为时,将触发构建后部署
  • 第8行是部署需要用到的github-token,其中$GITHUB_TOKEN是变量,它可以在Travis CI个人仓库的setting页里设置,相关操作可以看这里

发布到npm

再给出把node.js模块发布到npm的例子

主要是 deploy 这里有所不同

deploy:
  provider: npm
  email: <your_email>
  # api_key: travis encrypt NPM_TOKEN --add deploy.api_key --com
  on:
    branch: master
  skip-cleanup: true

api_key指的的npm的token,可以登录npm后,在个人中心生成

因为不能泄露,所以要通过travis ci的命令行工具进行加密,执行以下命令

travis encrypt NPM_TOKEN --add deploy.api_key --com

复杂例子

下面是一个复杂的例子,也是实际用到的配置,主要是

  • master分支才会触发构建
  • 执行script命令前先读取shell中的环境变量,并生成.env文件
  • 构建成功后
    • 把模块发布到npm
    • 把文档发布到gh-pages
branches:
  only:
    - master
language: node_js
node_js:
- lts/*
git:
  depth: 3
install:
- yarn --frozen-lockfile
before_script: echo OSS_KEY=$OSS_KEY\\nOSS_SECRET=$OSS_SECRET\\nOSS_BUCKET=$=OSS_BUCKET\\nOSS_REGION=$OSS_REGION > .env
script:
- yarn build
cache: yarn
deploy:
- provider: pages
  local-dir: docs
  github-token: $GITHUB_TOKEN
  skip-cleanup: true
  keep-history: true
- provider: npm
  email: [email protected]
  api_key: $NPM_TOKEN
  skip-cleanup: true

FAQ

通过环境变量设置GITHUB_TOKEN

首先为Travis CI新建一个token


点击生成新token

设置权限

image.png
复制生成的token。(记得先不要刷新或离开当前页面,否则token就看不见了,只能重新生成)

登录Travis CI, 进入要集成的项目设置页。

image.png

添加环境变量GITHUB_TOKEN

注意,这里的环境变量是通过bash设置、并在.yml里读取的,所以变量名是大写加下划线形式,这是bash的最佳实践,千万别写成github-token

image.png

GitHub Pages

查看gh-pages分支的部署情况

进入仓库 Settings -> Options

image.png

往下翻看,可以看到效果
image.png
因为笔者自定义了域名,所以地址不是默认的 https://xxx.github.io/xxx

发布 Github Pages 失败

小机率下,你可能会遇到下列错误

/home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-pages-1.10.12/lib/dpl/provider/pages.rb:141:in `mkdir': File exists @ dir_s_mkdir - /tmp/d20190920-7113-ieifmd/work (Errno::EEXIST)

发布到 Github Pages 时如果没有 gh-pages 分支,有可能会失败

解决方案是:手动创建 gh-pages 分支。

使用travis命令行工具加密

加密要用到travis命令行工具,如果是在travis ci web界面设置环境变量,则可直接跳过。

下面给出mac环境下操作需要注意的点

1.安装命令:

brew install travis

否则很可能会出现问题

2.确保在 https://travis-ci.org/ sign in with github

3.然后在项目根目录里,执行命令

travis login —auto

4.修改git设置

vi .git/config

确保

[travis]
  slug = 是你在travis关联的仓库

5.添加加密环境变量

travis encrypt github-token=xxx --add deploy.github-token --com

因为笔者登录的travis ci域名是 https://travis-ci.com,所以要带参数 --com , 默认是 https://travis-ci.org

🌪自动化的GithubWorkflow

前言

本文说明了我们的开源项目的Github协作流程,并解释了如何做到规范化及自动化。

内容涉及:

  1. 内容模板
  2. 分支模型
  3. CI集成
  4. 自动生成Release Notes

最终的效果是:

  1. 不需要提交构建产物到仓库
  2. 合并/发布操作完全在线化,维护者不需要拉取最新代码到本地。

正文

内容模板

内容模板包括:

  • ISSUE_TEMPLATE
  • PULL_REQUEST_TEMPLATE

可以在仓库里新建一个隐藏文件夹 .github , 里面放两个文件:

  • ISSUE_TEMPLATE.md
  • PULL_REQUEST_TEMPLATE.md

也可以通过界面设置。在仓库设置里,点击“Set up templates”

image.png

如果是组织,想为所有项目设置模板,则可以在组织下建立一个名为 .github 的仓库, 再重复上面的过程即可

image.png

具体内容参考我们的仓库设置

分支模型

仓库有两个基础分支:

  1. dev(默认分支)
  2. master(用于发布)

通过pull request来合并新的代码:

  • 协作者的代码通过pr合并到dev
  • dev通过pr合并到master

注意点:

  • merge 到 dev,使用squash merge

squash merge

  • merge 到 master,使用普通的merge
  • 永远不向master直接commit代码

CI集成

推荐两个工具:

  1. netlify
  2. travis ci

netlify的作用是,当有新的pr发生时,可以提前预览修改后构建的产物。可以查看netlify使用教程

travis ci的作用是,master有代码更新时,自动构建发布。可以查看travis ci使用教程

自动生成Release Notes

让我们渐进式地实现这个功能。

首先要规范化commit message,具体可以查看Commit Message Guidelines,这个叫做Conventional Commits

摘取重点如下,格式为:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

其中<type>: <subject> 是必须的。

type的类型有:

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation

规范了commit信息后,就可以使用工具standard-version自动化以下操作:

  • 更新package.json里的version字段
  • 生成新的git tag
  • 生成CHANGELOG.md
  • 把package.json, CHANGELOG.md的改动提交至git仓库,生成相关的commit

如果觉得commit规范不好记,可以使用工具:https://github.com/commitizen/cz-cli

也可以使用vscode的插件:vscode-commititzen

如果想阻止不规范的提交,可以使用工具:https://github.com/conventional-changelog/commitlint

以上操作对非开源项目也适用,是可以在公司产品、项目中推广的方式。

如果要针对 Github 生成 Release Notes,可以借助工具:https://github.com/github-tools/github-release-notes

误区

dev分支不是最新的

结论:master的commit次数会比dev多,但dev的功能代码是比master新的。

解释:因为master合并dev的代码会产生一个commit,同时又因为自动生成Release Notes时,CI会修改package.json,并新增一个commit,所以master永远有比dev多出的commit

下图是dev合并了pr,还master还未合并dev的情况:master既有领先,又有落后。
image.png

以下是master合并了dev,并且通过CI成功发布后的情况
image.png

在dev分支查看package.json

结论:以npm上的版本为准,可以查看README.md的图标
image.png

解释:接入CI后,package.json的修改不再由人工操作,所以dev的package.json不会得到更新。
image.png

🔨揭秘vue-sfc-cli:组件研发利器

前言

本文将揭示vue单文件组件的工具 vue-sfc-cli 的内涵,说明它是如何在整个组件研发流程中提升效率的。

本文可以看成是 📦vue组件发布npm最佳实践 的成长篇,是 🤖打造自动化的Github Workflow 的姐妹篇,是团队最佳实践的落地产物,涉及的背景知识有点多,需要花点时间消化🤔

使用教程

快速开始

npx vue-sfc-cli

# 接下来会有一串的提示,请务必填写
# 推荐kebab-case风格,小写字母,多个单词用-(dash)分隔,如my-component

# 填充完提示后
cd my-component

# 使用git初始化,这样可以使用commit hook
git init

# 安装依赖
yarn

# 开始开发
yarn dev

# 打包
yarn build

# 可以发布了!
yarn publish

参数选项

-u, --upgrade
根据 template目录下模板,生成新的文件,更新到当前组件中。使用的是覆盖策略,默认覆盖的文件定义在 update-files.js。常用于使用最新版本vue-sfc-cli对旧组件的配置进行升级

# cd my-component
npx vue-sfc-cli -u

--files
如果想更新额外的文件,可以传此选项,后接文件名,多个文件使用 , 分隔

npx vue-sfc-cli -u --files package.json,.babelrc.js

--test
生成一个测试的组件模板,常用于ci环境测试。

npx vue-sfc-cli --test

示例文档

在docs目录下,新建 md 文件,建议命名同样是kebab-case

以上传组件upload-to-ali的 docs/draggable.md 文档为例 
image.png

yarn dev 时会转这个markdown文件就会换成demo,可以看到实际代码,还可以实时修改代码,让demo刷新

image.png

API文档

在vue文件里,编写注释,即可生成API文档。

props

在props里使用多行注释
image.png

slot

在slot上一行,使用  @slot 开头的注释
image.png

event

在emit事件上方,使用多行注释
image.png

methods

在要公开显示的方法上方,使用多行注释,并添加 @public
image.png

效果预览
image.png
image.png

引入第三方库

Element-UI为例

yarn add element-ui

新增一个文件:styleguide/element.js

import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(Element)

修改配置文件:styleguide.config.js

image.png

环境变量

如果需要使用环境变量,推荐使用 dotenv 

yarn add dotenv --dev

image.png

prettier and husky

组件模板内置prettier, 可以在提交代码时格式化。

注意的是需要先执行 git init 命令,之后再执行 yarn 安装依赖,否则提交钩子不生效。

注意

不建议在Windows下生成组件,因为.sh可能没有执行权限。 

技术详解

技术概览

  • vue-styleguidist 本地开发demo与生成文档
  • jest 单元测试
  • prettier + husky 代码格式化
  • rollup 打包
  • standard-version 自动生成changelog
  • github-release-notes 生成github release
  • auto-badge 为pr自动添加label
  • netlify 预览pr
  • travis ci 发布npm/github pages
  • shell + dingtalk api 发布成功后发送钉钉消息
  • all-contributors 显示贡献者
  • 内置README.md模板,包括目录结构、返回顶部以及一些常见的badge

模板目录

以下是生成的组件默认模板配置

├── .all-contributorsrc  # all-contributors配置
├── .babelrc 
├── .editorconfig
├── .github
│   └── badge.yml        # github app auto-badge配置,用于给pr自动打标签
├── .gitignore
├── .grenrc.js           # github-release-notes配置
├── .prettierignore      # prettier配置
├── .prettierrc          # prettier配置
├── .travis.yml          # travis ci配置
├── LICENSE              # MIT
├── README.md            # 自述文件
├── build                # rollup配置
│   └── rollup.config.js
├── build.sh             # ci相关文件
├── dist                 # 打包输出目录
├── docs                 # 使用文档,这些md会转换成demo
│   ├── basic.md 
├── notify.sh            # ci相关文件,用于钉钉通知
├── package.json
├── src                   # 源文件目录
│   ├── index.js
│   └── upload-to-ali.vue # 单文件组件
├── styleguide.config.js  # vue-styleguidist配置文件
└── yarn.lock 

开发

选用vue-styleguidist的原因是,好处是:书写md,既可以充当文档,又可以转换成可运行的demo。

这样的好处是,文档与demo一体化,不用同时维护两份代码。

修改md、修改源文件,demo是会hot reload的,非常方便。

测试

对于组件的测试,大家首先想到的是相关的工具集vue-test-utils,然后觉得,组件测试有点难写,或者说,不知道怎么写。

其实可以换个思路,先从简单的做起。做单元测试,更重要的是培养写测试的习惯,所以一开始建议只用jest对纯函数进行测试。

也即,把组件里的方法抽取出来,单独放到一个文件里,然后专门对这些函数进行测试。

这样的好处是,为了方便测试:

  • 开发者在写组件时,需要尽可能地写短小精悍的函数
  • 函数要写成纯函数,也即不依赖或影响到全局变量

这样的方法,也很适合对一个完全没有测试的组件,逐步补充测试用例。

下面是el-data-table对纯函数测试的代码示例

image.png

image.png

附上相关文章:✅使用jest进行测试驱动开发

构建

yarn build 即可构建生成三个文件:

  • upload-to-ali.esm.js
  • upload-to-ali.min.js
  • upload-to-ali.umd.js

使用者import组件时,默认import进来的是 upload-to-ali.umd.js。 关于三个文件的相关描述,📦vue组件发布npm最佳实践已阐述过,就不重复了。

rollup的一个特点是,默认不会把组件的依赖一起打包进去,这个特性有利于减少组件的分发体积。

下面是个示例:

// src/index.js
const crypto = require('crypto')
const axios = require('axios')

执行 yarn build , 会得到以下信息
image.png
请不用担心这个警告,这是有意而为之的,因为不想把依赖把打包进dist进去了。

commit规范

在继续下面的内容之前,再复习一下Conventional Commits

摘取重点如下,格式为:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

其中<type><subject> 是必须的。

type的类型有:

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation

另外约定,更新依赖使用 chore(deps),这也是github官方的做法

PR自动打标签

由于github-release-notes生成的release notes只对打上了label的Pull Request才有效,因此给github仓库添加一个自动添加label的机器人,避免重复劳动。

流程详解

.grenrc.js

.grenrc.js是github release notes 的配置, 这是参考了nuxt、github以及其他流行仓库后得到的配置,可以拿来即用
image.png

badge.yml

.github/badge.yml是 auto-badge 的配置文件,需要放到隐藏文件夹 .github 下。以下配置与上面的 .grenrc.js 相对应,同样可以拿来即用

image.png

自动发布

Travis CI

主要利用Travis CI做到自动化,先看下面的 .travis.yml 配置:

image.png

上面参数的具体说明,可以参考教程:🚀Github集成TravisCI:自动发布

流程详解

其主要流程如下图所示:

build.sh

build.sh内容如下:

#!/bin/sh
yarn stdver

yarn build

git remote add github https://$GITHUB_TOKEN@github.com/FEMessage/upload-to-ali.git > /dev/null 2>&1
git push github HEAD:master --follow-tags

与之相对应的,package.json里scipts需要有以下字段:

"stdver": "standard-version -m '[skip ci] chore(release): v%s'",
"release": "gren release --override"

notify.sh内容如下:

#!/bin/sh
if [ "$TRAVIS_TEST_RESULT" != "0" ]
then
echo "build not success, bye"
exit 1
fi

url=https://api.github.com/repos/FEMessage/upload-to-ali/releases/latest
resp_tmp_file=resp.tmp

curl -H "Authorization: token $GITHUB_TOKEN" $url > $resp_tmp_file

html_url=`cat $resp_tmp_file | sed -n 5p | sed 's/\"html_url\"://g' | awk -F '"' '{print $2}'`
body=`cat $resp_tmp_file | grep body | sed 's/\"body\"://g;s/\"//g'`

msg='{"msgtype": "markdown", "markdown": {"title": "upload-to-ali更新", "text": "@所有人\n# [upload-to-ali]('$html_url')\n'$body'"}}'

curl -X POST https://oapi.dingtalk.com/robot/send\?access_token\=$DINGTALK_ROBOT_TOKEN -H 'Content-Type: application/json' -d "$msg"

rm $resp_tmp_file

这里有两个关键点:

  • 构建失败,不发送消息
  • 调用github api获取最新release时,带上github token

README.md

参考了优秀开源项目后,我们搜索出了一套README.md模板,内置在初始化工程里了
image.png

还有常见的badge
image.png

以及contributors
image.png
相关emoji代表的意思,可以看官方文档

结语

最后,欢迎大家使用https://github.com/FEMessage/vue-sfc-cli开发vue组件~

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.