eJayYoung / blog

逸杰的个人博客
MIT License
11 stars 0 forks source link

如何打造规范的开源项目workflow #1

Open eJayYoung opened 6 years ago

eJayYoung commented 6 years ago

前言

一般开源项目正常的开发流程是:

表面看起来没有什么毛病,但是要做一个规范的多人协作的可维护的开源项目,开发流程上还是有不少细节的点需要我们来打磨。

那在多人协作的开发过程中到底会碰到哪些无法统一的行为?我们该如何解决这些问题?

代码检查

问题

这是一个老生常谈的问题,语句结尾要不要分号?缩进是空格还是tab?是2个还是4个?

解决步骤

目前业界比较统一的做法是使用eslint

作为项目owner,建议全局安装eslint

npm install eslint -g

之后就可以在本地的项目仓库中,执行命令:

eslint --init

可以有三种选择:

常规的前端项目建议选用Standard规范(虽然不是真正的规范,但是属于业内默认的规范吧),初始化如下:

{
   "devDependencies": {
        "eslint": "^4.19.1",
        "eslint-plugin-import": "^2.13.0",
        "eslint-config-standard": "^11.0.0",
        "eslint-plugin-standard": "^3.1.0",
        "eslint-plugin-promise": "^3.8.0",
    }
}

React项目建议选用Airbnb规范,初始化后的package.json如下:

{
    "devDependencies": {
        "eslint": "^4.19.1",
        "eslint-config-airbnb": "^17.0.0",
        "eslint-plugin-import": "^2.13.0",
        "eslint-plugin-react": "^7.10.0",
        "eslint-plugin-jsx-a11y": "^6.1.1"
    }
}

如果需要单独定义一些规则,可以在根目录下新建一个.eslintrc文件,见具体配置

如果需要忽略一些文件,可以在根目录下新建一个.eslintignore文件,见具体配置

最后在package.json中加上命令

{
    "scripts": {
        "lint": "eslint --ext .jsx,.js ."
    }
}

这样项目成员只需要操作以下步骤

衍生问题1

如果是之前的老项目接入了eslint,同时加入了git hook来强制执行,会导致之前的错误没修复完无法提交,那有没有办法只检测当前修改的文件呢?

解决方案

目前社区比较成熟的方案是lint-staged

首先在项目中安装lint-stagedhusky

husky是用来实现绑定git hooks的一个库,后面会提到

npm install -D lint-staged husky

然后在项目中的package.json中添加

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.js": ["eslint --fix", "git add"]
  }
}

以上操作在我们正常的开发流程中做了什么呢?

git add .之后,git commit之前,会触发precommit钩子,执行lint-staged,然后lint-staged会只检查经过暂存区的文件,根据它的配置项执行命令。

例如上面的配置,就是针对所有修改过的以js为后缀的文件,依次执行eslint --fix,git add两个命令,帮你自动修复掉eslint的错误,再将修改添加到暂存区,然后一起commit到工作区。

衍生问题2

eslint只是作为代码检测的工具,并没有规范开发时的行为,那可不可以在编辑器上进行规范呢?

解决方案

主流方案是使用EditorConfig

首先在项目根目录新建一个.editorconfig文件,根据EditorConfig支持的大部分属性自行定义

然后再给编辑器安装EditorConfig的插件,就可以使用了,插件安装可见官方下载

提交信息和修改日志

问题

解决步骤

目前社区使用最广的方案是Commitizenconventional-changelog配合使用。

顾名思义

commitizen

有两种开发方式:

changelog

还是国际惯例,全局安装

npm install -g conventional-changelog-cli

切换到项目目录后,执行

conventional-changelog -p angular -i CHANGELOG.md -s -r 0

就可以根据commit message 自动生成一份CHANGELOG.md文件

再配合上package.json里的scripts

{
    "scripts": {
        "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
    }
}

只需要执行npm run changelog就行了,是不是很简单?

但是该在什么时机执行上面这条命令呢?

根据文档推荐的工作流,在修改package.json中的版本之后执行最合适。

不过在下面的版本控制会将工作流改成用npm version来实现。

{
    "scripts": {
        "version": "npm run changelog && git add CHANGELOG.md"
    }
}

版本迭代

问题

传统版本迭代步骤

流程过于繁冗,很容易遗漏打tag那一步。

解决步骤

使用npm version命令,从文档上我们可以看到其依据semver支持了大部分alias:

npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]

例:初始版本为1.0.0

npm version prepatch //预备补丁版本号 v1.0.1-0

npm version prerelease //预发布版本号 v1.0.1-1

npm version patch //补丁版本号 v1.0.2

npm version preminor //预备次版本号 v1.1.0-0

npm version minor //次版本号 v1.1.0

npm version premajor //预备主版本号 v2.0.0-0

npm version major //主版本号 v2.0.0

当在仓库中执行npm version时,会自动提交git commit并打上git tag

当使用-m参数时,就可以自定义发布版本的信息,其中%s可以用来代替当前版本号

npm version patch -m "upgrade to %s for reasons"

这样以后版本迭代只需要以下步骤

衍生问题

如何发布beta,rc,alpha版本呢?如果发布了,应该如何安装?

解决方案

首先我们要理解这些版本的含义

然后将package.jsonversion改成x.x.x-beta

配合npm publish --tag <tag>,我们可以发布对应的dist-tag

举个例子:

使用npm publish --tag beta发布后,然后就可以使用npm install <pkg>@beta安装对应版本的包。

我们可以通过npm dist-tag ls <pkg>来查看包的dist-tag

{
    latest: 1.0.1, // 这就是npm publish默认发布的tag
    beta: 1.0.1-beta
}

当我们的beta版本稳定后,可以使用npm dist-tag add x.x.x-beta latest设置为稳定版本。

执行落地

问题

正常工作流我们分为两块:

解决方案

根据上述两种流程,我们可以使用git hooknpm hook

开发流程

在上面已经介绍过,我们选用husky作为git hook的实现工具

npm install -D husky

代码lint和单元测试,我们可以放到precommit中执行,修改钩子执行的命令

{
    "scripts": {
        "test": "jest",
        "precommit": "lint-staged && npm run test"
    }
}

commit message lint的解决方案:commitlint

npm install -D @commitlint/config-conventional @commitlint/cli

然后在package.json中配置钩子

{
    "scripts": {
        "commitmsg": "commitlint -E GIT_PARAMS"
    },
    "commitlint": {
        "extends": ["@commitlint/config-conventional"],
        "rules": {
            "subject-case": [0]
        }
    }
}

这样我们在git commit执行之前验证了eslint是否通过,测试用例是否通过,commit message是否符合规范。

当然也可以在commit后面添加参数--no-verify来跳过commit message lint

发布流程

执行npm version命令时会依次执行npm script中的preversionversionpostversion三个钩子,具体详情见文档

prepublishOnly只会在npm publish之前执行,prepareprepublish都会在npm publish和不带参数的npm install时执行,见npm script文档

结尾

至此,我们已经基本打造了一个比较完善的开源项目workflow。

对于项目开发者来说,只需要做以下几点改变:

是不是更简单方便了呢?

欢迎大家拍砖,广纳良言!

参考文献

yukap6 commented 6 years ago

666

CurtisTong commented 6 years ago

666

caiyongmin commented 6 years ago

为大佬打 call

youngBrain1893 commented 6 years ago

感觉 EditorConfig 的约束都能使用 prettier 和 vscode 的配置实现

eJayYoung commented 6 years ago

@youngBrain1893 其实是类似功能的插件,editorConfig也要依赖vscode的插件,prettier是支持一键格式化吧

Adherentman commented 6 years ago

感觉eslint + prettier 在pre-commit 的时候也把格式化做掉。。

eJayYoung commented 6 years ago

感觉eslint + prettier 在pre-commit 的时候也把格式化做掉。。 感谢大佬谏言,之前写这篇文章时,只做了eslint的调研,prettier只是在vscode中本地使用,确实在precommit中加上会更好些

Adherentman commented 6 years ago

有个问题。。在eui里自己去写generate而不用webpack有什么优点吗?

eJayYoung commented 6 years ago

有个问题。。在eui里自己去写generate而不用webpack有什么优点吗?

:sweat_smile:,目前eui不是我在开发。 不过从代码看思路,现在eui也只做了一个copy file的事儿,可能是用于规划整包里子包的文件名吧。本来我之前是计划用webpack打包就行的。 不过子包发布的时候都已经用webpack+babel打过包了,所以整包这块要做的事情最多就是整合入口。

Adherentman commented 6 years ago

😅,目前eui不是我在开发。 不过从代码看思路,现在eui也只做了一个copy file的事儿,可能是用于规划整包里子包的文件名吧。本来我之前是计划用webpack打包就行的。 不过子包发布的时候都已经用webpack+babel打过包了,所以整包这块要做的事情最多就是整合入口。

感谢,最近在研究如何写一个组件库并发布。。发现有太多的结构。

eJayYoung commented 6 years ago

sweat_smile,目前eui不是我在开发。 不过从代码看思路,现在eui也只做了一个copy file的事儿,可能是用于规划整包里子包的文件名吧。本来我之前是计划用webpack打包就行的。 不过子包发布的时候都已经用webpack+babel打过包了,所以整包这块要做的事情最多就是整合入口。

感谢,最近在研究如何写一个组件库并发布。。发现有太多的结构。

是的,我们这边做,主要也是参考antd和uxcore的,可以尝试玩玩lerna。 这是我对eui的架构设计图,可以参考一二 eui 的架构图

Adherentman commented 6 years ago

@eJayYoung 是的。。我基于lerna。。packages里有各种xxx-component-xxx文件夹...感谢!

Adherentman commented 6 years ago

@eJayYoung 有以下疑问。。询问下。 不管是否基于lerna,然后我们把所有组件分成一个包的好处是什么?是按需下载减少项目的大小吗?

eJayYoung commented 6 years ago

@eJayYoung 有以下疑问。。询问下。 不管是否基于lerna,然后我们把所有组件分成一个包的好处是什么?是按需下载减少项目的大小吗?

不是的,放在一个包里的好处是,解决各个组件对于依赖包的版本控制问题,不然每个组件依赖其他组件还得手动维护版本。

Adherentman commented 6 years ago

@eJayYoung 有以下疑问。。询问下。 不管是否基于lerna,然后我们把所有组件分成一个包的好处是什么?是按需下载减少项目的大小吗?

不是的,放在一个包里的好处是,解决各个组件对于依赖包的版本控制问题,不然每个组件依赖其他组件还得手动维护版本。

最近折腾死了。。先js写,发现没法支持ts。然后用ts,发现各种类型定义加继承React.xxxxx要搞半天。然后还是转到了js。现在发现webpack打包出来太多无用的code,又想用rollup但是想了想,还是觉得就该直接babel去转就好了。。坑啊~