作者:冷卉


github可能是如今世界上托管开源项目最多的、代码最全的、涉及技术面最广、聚集大牛们最多、程序员最爱交流学习协作的平台,所有很多人会调侃说github是世界上最大的交友平台(似乎一点也没错哈)。所以在开源项目领域,github的影响力很大,是开源项目的首选托管平台。

对于程序员来说,没有哪个平台能让你如此自由、便捷地免费享受世界上最优秀的开源代码,与世界上顶级的人交流、分享和学,那些fork、star、follow、issue、pr等等,比起在社交平台上发表一个段子被评论、转发、点赞、关注更有意义。github非常鼓励开放、共享和自由的精神,在遇到困难时我们也会主动上github去寻求好的解决方式、已实现的工具库,对一些流行库非常感兴趣可以去扒它的源码,学习学习其中的精髓。我们可能有很多次打开github,去查阅或者下载一些项目的源码,但是这些在github上能看到的代码仓库都是开源项目吗?开源项目到底是什么?

imove与开源

开源项目包含什么

一个代码仓库不代表是一个开源项目,它包含了源码、文档、官网、社区等等一系列的组成要素,以 imove 为例具体描述一下:

1. 源码仓库
imove的源码仓库包括了多个npm包(packages文件夹)、1个demo示例(example文件夹) ,具体的设计会在下文中详细说明。总之,我们需要建立一个仓库存储项目代码。

2. 官网 & 使用文档
建立使用文档的方式有很多,可以是语雀vuepressdumidocisfygitbook……在这里,我们使用语雀完成使用文档

3. README
在项目中需要加上README,告诉用户开源项目的背景和用途、有哪些使用场景,基本的使用方法是什么,以及如何快速上手、如何搭建、如何运行。允许他人贡献代码,而不是仅仅给别人阅读源码的权限。

4. 问答社区
这里,imove的问答社区直接采用了github issue进行交流。

(1)提交issue的方式:

image.png

  • 点击issue
  • 查看历史上的issue能否解决你的问题,进行issue搜索
  • 确认已有issue无法解决你的问题,点击New issue,创建新issue

(2)回答issue的方式:

image.png

  • 打开某一条issue
  • 可以用quote reply的方式(内容太长就不建议)
  • 在输入框中填写回答
  • 点击Comment提交

5. 在线地址

我们可以把项目地址部署到一个网站供大家使用,这样可以免去本地安装启动项目的时间成本。在这里我们直接使用codesandbox提供的能力,部署了一个静态网站:imove在线地址

6. 问题列表和升级计划
整理项目的最小发布版本、迭代计划及里程碑。

image.png

7. 交流群
提供及时交流社区,即 QQ 群、微信群、钉钉群等。这里因为工作关系,我们采用了钉钉交流群。

image.png

imove迭代历程

imove是如何一步步从小工具做到开源项目的?最开始,我们的目标是为了让前端通过可视化的方法去编写代码,目标十分简单纯粹。最初完成的版本功能仅仅支持流程图绘制、节点代码编写、支持构建打包和源码调用API。

渐渐地,我们发现还有以下种种痛点:

  1. 项目/节点数据没有服务端存储能力
  2. 编写好的节点没有复用的市场
  3. 缺乏插件应用市场
  4. 缺乏web browser 在线调试能力
  5. 提供的代码编辑器能力很弱,类似于文本编辑器
  6. 无法在线运行代码
  7. ……

这些种种痛点,成为了我们迭代项目的需求,于是开始了一步步迭代的旅程:

image.png

于是,就慢慢从内部使用,推广到github进行开源,并坚持迭代和完善。这里推荐大家先设计最小可用MVP版本,在后续持续进行项目迭代。

开源项目流程展示

下面以imove项目为例,演示一下开源项目从0到1的详细过程,希望能给到大家一些参考,帮助大家快速开始自己的开源项目。

项目创建

选择账号

首先,你需要明确即将注册的账号是专门针对一个产品(即项目账号)还是将运维多个产品(即组织账号)。组织账号下可能有很多开源产品,而项目账号下只管理这一个项目。

创建新项目

登录 github ,点击右上角的“+”可看到新建项目的链接,或者直接访问 https://github.com/new 。创建完成之后,通过 https://github.com/ykfe/imove 即可访问到项目的主页,这是你就已经有了自己的开源项目了。

image.png

这里需要注意到几种证书的区别:

image.png

从上图中可以看出,协议可以分为两部分:是否允许使用者修改源码后进行闭源商用。

允许使用者修改源码后进行闭源商用:

这里最宽松的就是BSD协议 ,它给了使用者最大的自由。基本上使用者已经和源码脱离关系,不需要额外在任何修改的文件放置版权说明,不能使用原软件打广告(因为已经脱离关系了嘛,但是需要尊重代码作者的著作权),使用者可以对自己的代码做任何操作。BSD鼓励代码共享,允许开发商业软件发布和销售,很多公司在选用开源协议的时候首选BSD,因为可以完全掌控这些第三方代码。

相比之下,MIT协议的作者只想保留版权,而无任何其他了限制。使用者需要在发行版本里包含原版权声明。MIT目前是被广泛使用的,imove就是采用的MIT协议。

Apache协议是要求使用者对于每一个修改的文件放置版权说明的,这点注意就好,不影响商用。

不允许使用者修改源码后进行闭源商用:

GPL协议是需要使用者新增代码也采用同样的许可证,它的出发点也是代码开源、共享,但是要求衍生代码也必须开源。例如linux就是采用的GPL协议,因此使用到linux技术的各个软件也同样开源了。

Mozilla协议不需要使用者新增代码也采用同样的许可证,但是需要对源码的修改之处,提供相应的说明文档。LGPL协议无这些要求,不需要采用相同许可证,也不需要提供说明文档,只需要使用者同样进行开源。

下载

进入 github 项目主页,复制 git 地址,如下图:

6. 开源项目应该怎么做? iMove 带你认识一下 - 图8

推荐选择 use ssh ,不要 use https。使用ssh链接是因为其相比于https链接更加安全:https使用的是标准端口443端口,可以对repo根据权限进行读写,只要有账号密码就可进行操作。而ssh使用的是22端口,也可以对repo根据权限进行读写,但是需要在clone前添加ssh key,这个key是通过ssh key生成器生成的,并将生成的ssh公钥配置到git服务器中作为授权的证据。为了保证仓库安全,最好使用ssh链接。

接下来选择一个合适的文件夹或目录,执行下载命令git clone git@github.com:/imove.git。下载完毕,进入代码目录,运行如下命令修改当前 git 的用户名和邮箱,改成和当前 github 用户名和注册邮箱一致。

  1. cd imove
  2. git config user.name 'xxx'
  3. git config user.email 'xxx@xx.com'

添加ssh key的步骤:

ssh key 就是连接你的电脑和 github 服务器的一把钥匙,只有添加成功了才能把你本地的代码提交到 github 服务器。

如果你是 macOS 系统,运行 ssh-keygen 即可一步一步生成 ssh key ,然后运行 pbcopy < ~/.ssh/id_rsa.pub 即可拷贝下来。在 github 个人中心的设置界面,能找到 SSH and GPS keys 菜单栏,或者直接访问 https://github.com/settings/keys 。页面中点击右侧“new ssh key”按钮即可添加 ssh key ,把刚才的内容粘贴过来添加上就行,这时我们可以向自己的github仓库提交代码了。

项目构建

项目初始化

进入项目目录,然后命令行运行 npm init ,按照提示进行初始化即可。提示中的信息,能写的都写上,尽量保证完备。初始化完成之后,项目根目录下会有 package.json 的文件。在imove项目中,我们是使用lerna进行项目初始化的,它能解决各个库之间修改混乱、难以跟踪的问题,非常方便同时管理多个npm包。具体流程如下:

  1. npm i -g lerna
  2. lerna init

初始化一个lerna命令,生成以下目录结构:

  1. ├── packages
  2. ├── lerna.json
  3. └── package.json

这里,我们预计要发布4个包,包名和作用对应如下:

  • cli:命令行工具,包括imove -iimove -d
  • core:imove核心库,完成逻辑编排
  • compile-code:流程图代码编译工具
  • plugin-store:store数据存储插件

于是在packages文件夹下新建了4个文件夹,分别使用npm init进行初始化。生成以下目录结构:

  1. ├── lerna.json
  2. ├── package.json
  3. └── packages
  4. ├── cli
  5. └── package.json
  6. ├── compile-code
  7. └── package.json
  8. ├── core
  9. └── package.json
  10. └── plugin-store
  11. └── package.json

打包工具

前端常见的打包工具有webpack、rollup等。对于应用常用 webpack 打包,对于类库常用 Rollup 打包。如果你需要进行代码拆分、处理很多静态资源等等,那么 webpack 是个很不错的工具。如果你的代码是基于 ES6 模块的,而且希望你写的代码能够被其他人直接使用,你需要的打包工具可能是 Rollup 。在imove项目中,我们选择了rollup作为打包工具,需要分别配置根目录和packages中各文件夹的rollup配置。

根目录下的rollup.config.js配置:

  1. import resolve from '@rollup/plugin-node-resolve';
  2. import commonjs from '@rollup/plugin-commonjs';
  3. import strip from '@rollup/plugin-strip';
  4. import url from '@rollup/plugin-url';
  5. import json from '@rollup/plugin-json';
  6. import { sizeSnapshot } from 'rollup-plugin-size-snapshot';
  7. const input = './src/index.tsx';
  8. const extensions = ['.js', '.jsx', '.ts', '.tsx'];
  9. export default {
  10. input,
  11. plugins: [
  12. resolve({ extensions, preferBuiltins: true }),
  13. commonjs(),
  14. json(),
  15. url({
  16. limit: 10 * 1024,
  17. }),
  18. strip({ debugger: true }),
  19. sizeSnapshot({ printInfo: false }),
  20. ],
  21. };

packages下包各自继承了根目录下的rollup配置(以core包为例):

  1. import pkg from './package.json';
  2. import postcss from 'rollup-plugin-postcss';
  3. import typescript from 'rollup-plugin-typescript';
  4. import rollupBaseConfig from '../../rollup.config';
  5. import commonjs from '@rollup/plugin-commonjs';
  6. export default Object.assign(rollupBaseConfig, {
  7. plugins: [
  8. postcss(),
  9. typescript(),
  10. commonjs()
  11. ],
  12. output: [
  13. {
  14. file: pkg.main,
  15. format: 'cjs',
  16. },
  17. {
  18. file: pkg.module,
  19. format: 'es',
  20. },
  21. ],
  22. external: Object.keys(pkg.peerDependencies),
  23. });

在packages下5个包的package.json中,分别有以下的scripts命令:

  1. {
  2. "scripts": {
  3. "declare-type": "tsc --emitDeclarationOnly",
  4. "build": "rollup -c & npm run declare-type"
  5. }
  6. }

于是我们根目录执行lerna run build --parallel即可同时执行packages下所有包对应的npm run build命令,完成打包。打包是完成了,但是有个问题:本地开发的npm如何调试呢?

这时,我们可以在项目根路径下执行npm link packages/cli,这时,可以把全局安装的imove命令映射到此文件夹中,因此,我们再执行imove -dimove -i命令时,就直接使用的packages/cli中的命令了。相同的方法,可以在项目根路径下执行npm link packages/core,这时对于项目中引入的@imove/core包,就能直接使用packages/core文件夹的打包文件。能方便地进行边修改边调试。

我们可以实时调试本地npm包了,如何实现实时热更新看效果呢?这时,我们在packages下5个包的package.json中加入了watch命令:

  1. {
  2. "scripts": {
  3. "declare-type": "tsc --emitDeclarationOnly",
  4. "build": "rollup -c & npm run declare-type",
  5. "watch": "watch 'npm run build' ./src"
  6. }
  7. }

当src文件夹发生变化时,是会重新执行npm run build命令的。这时,我们只需要根目录执行lerna run watch --parallel即可同时执行packages下所有包对应的npm run watch命令,实现了热更新。

代码规范配置

项目的基本骨架搭建好了,我们需要完成每个包代码的编写,这时建议使用到eslint规范,能够使项目代码更加规范。eslint的配置如下:

  1. module.exports = {
  2. extends: [
  3. "prettier/@typescript-eslint",
  4. "plugin:react/recommended",
  5. "plugin:@typescript-eslint/recommended",
  6. "plugin:prettier/recommended"
  7. ],
  8. env: {
  9. node: true,
  10. browser: true,
  11. },
  12. settings: {
  13. react: {
  14. version: "detect"
  15. }
  16. },
  17. // ...
  18. };

在package.json的script中可以加入lint命令,并安装pre-commit库,能在每次git commit前执行npm run lint命令,完成代码规范检查。这样可以做到代码的风格统一。如果我们需要在每次commit前进行自动规范化代码,则可以进行以下的配置:

  1. "scripts": {
  2. "lint": "eslint --cache --fix --ext .ts,tsx,js,jsx ./packages/**/src > ./.eslint-error.log; exit 0"
  3. },
  4. "pre-commit": [
  5. "lint"
  6. ]

这里的pre-commit钩子是在提交commit信息前运行,最先触发运行的脚本。例如,检查是否有东西被遗漏、运行一些自动化测试、以及检查代码规范。

代码编写

接下来编写好每个包的代码,通过热更新的方式边写代码边完成功能的自测。对源码感兴趣的朋友,可以看看:

语义化版本

语义化版本是为了在项目的版本号中,直接反映代码所做出的的修改,因此使用者是可以基于版本号推测出此版本的改动范围是什么。在npm包中使用语义化版本,可以方便开发者进行版本管理,同时也方便使用者使用cnom选择指定的版本相匹配的最新版本进行安装。版本号分为以下三级,分别为:

  • 主版本号(major):此时做了不兼容的大版本修改
  • 次版本号(minor):此时做了向下兼容的功能新增
  • 修订号(patch):此时做了向下兼容的问题修正

有时候为了表达更加确切的版本信息,可能会在版本号后方添加标签,常见的标签有dev(开发阶段)、alpha(内部人员测试)、beta(比alpha有大的改进,仍存在bug)、gamma(与稳定版接近)、stable(稳定版本)、latest(最新版本)等,如1.2.1-beta-1表示预发布版本号。可以使用以下命令查看npm包的标签和版本号:

  1. npm dist-tags ls <pkg>
  2. 例如:
  3. npm dist-tags ls @imove/core

可以看到:

  1. latest: 0.3.8

如何更新版本号呢?这时不用手动地修改 package.json。而是使用npm-version命令可以完成:

  1. npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]

newversion表示直接添加一个新的版本号,major表示主版本增加1,premajor表示预备主版本,主版本增加1,增加先行版本号,release表示预先发布版本,先行版本号增加1。如我们需要给主版本号加1,则执行npm version major,package.json 中的版本号会自动进行更新。

测试

单元测试

单元测试是针对一个模块进行测试,能够保障项目各个模块的稳定性。在imove中,我们采用了jest框架进行单元测试,引入代码如下:

package.json

  1. {
  2. "scripts": {
  3. "test": "jest --coverage",
  4. }
  5. }

执行jest --init生成jest.config.js配置文件:

  1. module.exports = {
  2. clearMocks: true,
  3. coverageDirectory: "coverage",
  4. coverageProvider: "v8",
  5. testEnvironment: "node",
  6. rootDir: '../',
  7. moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
  8. testMatch: [
  9. "__tests__/core/src/**/*.(spec|test).[jt]s?(x)"
  10. ]
  11. }

接下来运行npm run test,即可运行__tests__文件夹下所有的测试用例。

端对端测试

端对端测试也是集成测试,测的是一个用户功能模块,比如用户注册功能,集成测试完全是用测试脚本去模拟用户操作,比如打开浏览器、点击注册链接、输入用户名密码、点击注册。以上的单元测试可以保证各模块的稳定性,但是不能保证完整流程的稳定性。因此引入端对端测试是非常有必要的。首先需要根据项目整理功能列表,逐个完成测试用例。

  1. i. 整理功能列表,逐个完成测试用例
  2. ⅰ. 工具栏
  3. 1. 撤销
  4. 2. 重做
  5. 3. 放大
  6. 4. 缩小
  7. 5. 修改字号
  8. 6. 修改字重
  9. 7. 修改斜体
  10. 8. 修改下划线
  11. 9. 修改文字颜色
  12. 10. 修改背景颜色
  13. 11. 修改边框颜色
  14. 12. 修改水平方向对齐
  15. 13. 修改垂直方向对齐
  16. 14. 修改层级置底
  17. 15. 修改层级置顶
  18. ⅱ. 设置
  19. 1. 修改节点名称
  20. 2. 修改节点代码
  21. 3. 修改投放配置schema
  22. 4. 修改npm依赖
  23. 5. 修改投放配置
  24. ⅲ. 节点/边 操作
  25. 1. 添加 开始/分支/处理 节点
  26. 2. 边连线
  27. 3. 删除节点/边

我们采用的是cypress框架,使用以下结构编写测试用例。直接运行cypress open,即可跑端对端测试。

  1. /// <reference types="cypress" />
  2. context('Actions', () => {
  3. beforeEach(() => {
  4. cy.visit('http://localhost:8000/')
  5. })
  6. it('修改节点名称', () => {
  7. })
  8. it('修改节点代码', () => {
  9. })
  10. it('修改投放配置schema', () => {
  11. })
  12. it('修改npm依赖', () => {
  13. })
  14. })

项目发布

提交代码

开发阶段提交代码的步骤如下:

  1. 在项目的0.3.x分支(目前的开发分支,类似于dev分支)上进行开发,只有发布版本时合并到master
  2. 基于0.3.x分支切一个自己的开发分支,如 mydev
  3. 编写代码
  4. 完成自测
  5. 将 mydev 分支合并到 0.3.x 分支

发布版本阶段提交代码的步骤如下:

  1. 修改 package.json 版本号,按照之前既定的版本规则进行修改
  2. 再次确认版本号,因为版本号非常重要
  3. 合并0.3.x分支到master,并提交 master 到远程(不建议把lock文件推到仓库)
  4. 创建 tag 并提交到远程
  5. 提交到npm

发布release版本

分两种情况:1)你是开源项目的创建者;2)你是后期才加入到开源项目中,成为贡献者

(1)你是开源项目的创建者

直接选择github中的Release,选择“Draft a new release”,填写完信息后点击“Publish release”,即可完成发布。

image.png

image.png

image.png

(2)你是后期才加入到开源项目中,成为贡献者

成为项目的Contributor,接收邀请,在邮件中同意即可。结下的操作同(1),选择github中的Release完成发布。这时,在release界面可以看到已经发版的打包源码。

image.png

发布npm包

  1. 在npm官网 https://www.npmjs.com/ 完成注册,牢记username、password、mail
  2. 如果你是直接使用你创建的账号发布npm包,就是npm包的owner,则可以直接进行下一步的操作。如果不是npm包的owner,则需要获取npm包发布权限,让npm包发布者添加你为贡献者,如下所示:
  1. npm owner add <user> [<@scope>/]<pkg>
  2. npm owner rm <user> [<@scope>/]<pkg>
  3. npm owner ls [<@scope>/]<pkg>
  1. 打开终端,输入 npm login ,会提示你输入用户名密码邮箱
  2. 打开项目,在 packages 下,我们发现有4个文件夹,需要对应发布4个npm包
  3. 验证每个包功能是否正常,如果正常,提升一个版本号
  4. 到 packages 的各个文件夹下,终端执行 npm publish —access public 即可完成发布

npm包发布过程中可能遇到如下的报错信息:

  1. ERR! code E404
  2. npm ERR! 404 Not Found - PUT https://npm.pkg.github.com/zswui
  3. npm ERR! 404
  4. npm ERR! 404 'zswui@0.0.41' is not in the npm registry.
  5. npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
  6. npm ERR! 404
  7. npm ERR! 404 Note that you can also install from a
  8. npm ERR! 404 tarball, folder, http url, or git url

可能情况是:

  • npm login没成功,采用“npm who am i”检测是否登录
  • npm包没有发布权限,应联系开发者添加权限

项目部署

把项目部署在线上,可以方便使用者直接体验产品效果,而不需要手动下载安装运行源码。常见的项目部署方案有:自己购买服务器和域名、使用免费的github.io官网……因为在imove中,我们想直接部署一个online demo,供大家在不需要手动启动项目的基础上直接体验使用效果,因此使用codesandbox工具即可满足要求,也不需要额外的购买服务器和维护成本。

首先需要进入codesandbox空间中,创建一个sandboxfork一份仓库代码,运行代码,生成预览地址https://oyg3y.sse.codesandbox.io/,即可直接访问项目。

关注issue和PR

开源项目的作者应该多关注issue讨论区,会有很多非常好的建议,可以多留意。以imove为例,我们在开发的过程中,收到了以下的issue建议:

image.png

以上的issue提出了产品体验问题、交互优化问题、技术实现问题……于是,我们思考这些问题是会给使用者带来痛点?是紧急修复还是放入后期的迭代计划中?提的建议是否技术可行?imove针对这些issue做了以下的改变,这些建议会帮助项目越来越完善。

  1. 代码编辑体验——使用代码编辑器替换纯文本框,增加代码高亮和智能提示
  2. 在线运行节点代码——支持单节点直接运行,随时随地进行代码测试
  3. 选中边线高亮——原有边线选中无感知,改进了交互体验
  4. 双击编辑代码——双击即可打开代码编辑框,而不是需要点击节点后再在右侧选择代码“编辑”按钮打开编辑框
  5. add online example——将项目部署线上,不需要本地打包运行即可体验imove
  6. UI上增加说明文档——直接在界面中加入”帮助指引“,可以不用去翻阅使用文档,可快速上手
  7. ……

另外还需要多关注 pr,其他人贡献的 pr 可以通过 https://github.com/ykfe/imove/pulls 看到。对于每一个 pr ,如果你想合并,直接 merge 就好;如果你不想合并,留言说明然后关闭掉即可。

关于开源的碎碎念

做开源并不能给你带来一些让你看得见摸得着的好处,但是依然有许许多多的程序员还在坚持做开源,一直活跃在开源社区。为什么呢?就像我们愿意每周抽出时间来读书,想提高自己的精神涵养和知识面,随着时间的沉淀,那些书本里的东西会成为我们人生路上的照明灯。做开源其实也是一样的道理,做开源项目对于程序员的成长有以下好处:

  1. 能够提升写代码的质量。如果你知道你写的代码面向所有人,会不会更加注意代码的编写质量?我们需要从零开始,对每一段代码完全负责。而且在开源社区如果有人给你的代码提建议,又多了一些学习的机会。
  2. 全面锻炼自己的产品意识。因为做开源做的是产品,不再是单纯的技术活,UI、运维、推广、反馈、你都得考虑。
  3. 提高自己的社区知名度。 github 上的 star 能间接的反应你的贡献价值,它能让你得到业内认可,把一个优质项目开源是个迅速提升影响力的好方法。
  4. 交流与成长。大家把自己的工作成果和经验免费分享出来,遇到相同问题的人可以直接用你得出的经验和成果解决问题。这可以大大降低重复劳动现象的出现,帮助他人的同时,也可以提升个人影响力。
  5. 展示个人代码。如果你在找工作,做开源项目非常能够帮助你。公司实际上很好奇你写的代码质量如何,仅仅从有限时间的面试看不出来太多。所以很多公司会非常喜欢看你的开源项目,来评估一下你的代码质量如何。

如果我们想做个开源项目,应该如何选择呢?是写一个工具库、一个类似eggjs的完整框架?还是写各种函数库?在这里,有几条选择的原则可以参考一下:

  1. 建议开发小巧的工具,而不是大而全的框架。什么是小巧的工具?这里提供大家参考:https://github.com/parro-it/awesome-micro-npm-packages
  2. 建议有所创新,能够解决大家的痛点,但是目前也没有什么合适的库。
  3. 建议针对工作上的痛点,开发能提效平常工作的工具库,这样有足够的驱动力能让你做得更好。

总结

完成到这里,一个开源项目算是比较完善了,之后可以不断地进行版本迭代。imove最初只是为了应对业务上遇到的痛点问题而设计的一款工具,后面会发现功能是非常不完善的,于是踏上了一步步的迭代旅程。开源项目可能只是开发者履历上小小的一笔,但是可以极大地帮助你成长,还是推荐大家有时间可以体验下开源项目。