19.开源输出一份优秀的自述文档,需加点什么料
技术要点:Package、License、Readme
前言
通过前四章的学习,你已熟悉如何搭建类库模块
与多包仓库
的前端基建。通过前端工程化
的手段打造了属于自己的轮子,在编码方面已完成很多需求,然而一个良好实用的前端基建系统还需完善一些看似不是很重要的细节。
这些细节有可能因为入口文件的优先级别导致构建流程出错,有可能因为软件的版权问题导致使用源码时受到影响,有可能因为文档的排版布局不够出色不能引人注目等问题。
前端基建系统最终还是会开源到公司内网或外网,所以在发布前必须将这些细节完善,确保开源项目不受这些小问题的影响。本章将带领你完善开源输出的发布准备,从项目配置文件
、软件许可证书
和项目自述文档
三方面入手,确保源码在上线前拥有基础保障,使其他开发者看得省心用得放心
。
方案:优化项目配置文件
Package指项目配置文件,用于定义整个项目所需的配置信息,包括但不限于描述配置、文件配置、脚本配置、依赖信息、发布配置、第三方配置等字段。Package
对应实体文件是package.json
,没错,就是经常npm i
时引用到的文件。
很多同学对package.json
配置字段的印象是又多又乱。为了方便对比,通过下图展示它们各自的组成与作用,相信你有更好的理解。
其中有几个配置字段尤为重要,以第15章封装的工具库为例,以下将一一展开讲述。
版本编号
version
表示版本编号,用于确定项目在每个阶段的唯一性。在项目每次定版发布前,需更新一次版本。执行npm view react versions
,输出以下信息,可能都会经常见到这些版本,但很少同学会主动了解这些版本为何这样设计。
"16.0.0-alpha",
"16.0.0-alpha.1",
"16.0.0-alpha.2",
"16.0.0-alpha.3",
...
"16.0.0-beta",
"16.0.0-beta.1",
"16.0.0-beta.2",
"16.0.0-beta.3",
...
"16.0.0-rc",
"16.0.0-rc.1",
"16.0.0-rc.2",
"16.0.0-rc.3",
...
"16.0.0",
"16.1.0",
"16.1.1",
"16.2.0",
...
从react
的版本列表不难得出以下结论。
- 版本的形式严格遵循
a.b.c
- 版本的迭代严格遵循
右位递增
- 发布重要版本前,可发布
alpha
、beta
、rc
等先行版本 - 先行版本的编号后方可带上
次数
或元信息
在早期的软件发展道路中,安装某个软件包时会发现该软件包中又依赖不同指定版本的其它软件包。随着系统功能越来越复杂,依赖软件包越来越多,其依赖关系也越来越深,可能面临版本控制被锁死的风险,这就是依赖地狱。
基于该原因,Github
起草了一份具备指导意义且统一版本编号的表示规则,称为语义化版本
(Semantic Versioning),简称Semver
。目前它由Npm团队
维护。
Semver
的出现规定了版本如何表示如何迭代如何比较。遵从Semver规范
的Npm模块
,其依赖会很清晰,不会出现循环依赖、依赖冲突等常见问题。关于Semver
的完整规范可查看Semver官网。
版本的命名完全遵循Semver规范
,格式为主版本.次版本.修订版本
。
版号 | 简称 | 又称 | 功能 | 描述 |
---|---|---|---|---|
主版本 | major |
Breaking Change |
在原来功能框架中新增功能 | 通常是做了不兼容API 的改动 |
次版本 | minor |
New Feature |
在原来功能框架中新增功能 | 通常是做了向下兼容的功能改动 |
修订版本 | patch |
Bug Fix |
在原来功能框架中修复缺陷 | 通常是做了向下兼容的缺陷改动 |
先行版本的次数
与元信息
可加到主版本.次版本.修订版本
的后方作为延伸。
先行版本
为何要使用先行版本?若某个版本改动较大且不稳定,可能无法满足预期的兼容性需求,就需发布先行版本作为铺垫或过渡,所以很多明星项目都会这样处理。先行版本通过-
连接编号与次数
或元信息
。
标记 | 简称 | 功能 | 描述 |
---|---|---|---|
内测版本 | alpha |
主要以实现软件功能为主 | 只在开发者内部交流,问题较多需继续优化 |
公测版本 | beta |
主要以修复问题缺陷为主 还会加入新功能 |
可在社区外部交流,问题不多但需继续优化 |
候选版本 | gama 或rc |
主要以消除最终错误为主 不会加入新功能 |
与正式版本无异 |
当经过先行版本一系列测试后,终归会有一个正式版本,该版本是最终交付到用户使用的一个版本,也就是release
。例如react
的releases。
版本准则
标准的版本必须严格遵循a.b.c
且三者都为非负整数,禁止在数字前方补零,版本发布需严格递增。例如1.1.1 -> 1.2.0 -> 1.2.1
。版本发行后,任何改动都必须以新版本发行。
1.0.0
的版本用于界定公共API
。当发布软件到正式环境或存在稳定API
时就可发布1.0.0
的版本了,因此1.0.0
不是随便定义的。
版本的优先层级指不同版本在排序时如何比较。判断优先层级时,必须把版本依序拆分为主版本
、次版本
、修订版本
和先行版本
后比较。
版本规则
在执行npm i react
时会在package.json
中生成以下描述。当执行安装命令安装Npm模块
时,Npm
会首先安装依赖的最新版本,然后将包名及版本编号写入到package.json
中。被安装依赖的版本前方会默认加上^
。
{
"dependencies": {
"react": "^18.0.0"
}
}
除了^
,还有~
、<
、>
、=
、<=
、>=
、-
、||
、x
、*
。
^
表示同一主版本
中不小于指定版本的版本。^2.2.1
对应主版本为2
,不小于2.2.1
的版本,例如2.2.1
、2.2.2
、2.3.0
等,主版本
固定。
~
表示同一主版本
与次版本
中不小于指定版本的版本。~2.2.1
对应主版本为2
,次版本为2
,不小于2.2.1
的版本,例如2.2.1
、2.2.2
等,主版本
与次版本
固定。
>、<、=、>=、<=、-
表示一个版本范围。-
必须使用前后空格间隔。
{
"engines": {
"node": "16.0.0 - 16.14.0",
"npm": ">=7.10.0"
}
}
||
表示满足多个条件的版本。||
必须使用前后空格间隔。
{
"engines": {
"node": ">=16.0.0 || <=16.14.0",
"npm": ">=7.10.0"
}
}
x
与*
表示通配版本。
{
"engines": {
"node": "16.x",
"npm": "*"
}
}
发行版本
通常发布一个Npm模块
到Npm公有仓库
,正确做法是先修改package.json
的version
,然后执行npm publish
发布模块。手动修改版本的做法建立在你对Semver规范
特别熟悉的基础上,否则可能会造成版本混乱。Npm
考虑到这点就提供相关命令让开发者更好地遵循Semver规范
。
- 升级主版本:
npm version <major>
- 升级次版本:
npm version <minor>
- 升级修订版本:
npm version <patch>
入口文件
main/module/browser
同时表示入口文件,到底它们存在何种关系,又存在优先级别吗?在此引申出一个新问题,require
与import
引用的Npm模块
,到底是使用哪个字段加载入口文件?
第15章开发工具库时就埋下该问题。在开发前就明确清楚Npm模块
的应用场景。
- 只能在
Web
中使用 - 只能在
Node
中使用 - 可在
Web
与Node
中使用
这貌似对应了三种主流的模块规范
,分别是ESM
、CJS
和UMD
。ESM
容易静态分析,可启用摇树优化删除所有未实际使用的代码,推荐首选;CJS
使用同步加载适用于Node
环境,无摇树优化;UMD
是CJS
与ESM
的结合体,可自动挂在全局导出global
,适用于Web
但也兼容Node
。
第15章开发的工具库可在Web
与Node
中使用,那仅仅一个main
字段是无法保障在Web
中加载哪个文件,在Node
中又加载哪个文件,因此在不同环境中加载Npm模块
不同入口文件,显然一个main
字段已不能够满足需求,这就衍生出module
与browser
两个字段。
根据CJS/ESM/UMD
的特性与Npm官网
对main/module/browser
字段的描述,可得到以下对应关系。
main
main
字段在Web
与Node
环境中都可用。若将项目发布为Npm模块
,使用require
或import
导入它时,默认返回的入口文件就是main
字段指向入口文件的module.exports
或export/export default
。若不指定该字段,默认返回是根目录中的index.js
,若入口文件未找到则直接报错。
module
main
字段在Web
与Node
环境中都可用,前提是入口文件必须使用ESM
。更多时候是main
与module
混用,使用CJS
书写是为了用户在配置babel
时可放心屏蔽node_modules
文件夹,使用ESM
书写是为了用户在使用Npm模块
时可享受摇树优化带来的好处。
{
"main": "dist/web.js",
"module": "dist/web.esm.js"
}
这样相当一个Npm模块
内同时发布两种模块规范
的版本。当打包工具遇到这种Npm模块
时,若能识别module
字段则会优先使用ESM
版本的入口文件,这样可启用摇树优化机制,若不能识别module
字段则会使用CJS
版本的入口文件,这样也不会阻碍打包流程。
在module
字段未作为package.json
标准字段前,社区默契地形成使用jsnext:main
字段代表module
字段,因此引用一些老旧但下载量很高的Npm模块
,还是能看到该字段的存在,而webpack
也一直能识别该字段。
{
"main": "dist/web.js",
"jsnext:main": "dist/web.esm.js"
}
browser
browser
字段只能在Web
环境中可用。若Npm模块
只在Web
中使用且严禁在Node
中使用,那使用browser
指定入口文件再也适合不过了。
综上所述,为了能让打包工具更快更准地寻找入口文件,Npm模块
就必须根据以下情况配置相关字段。
- 若
Npm模块
只导出CJS
的入口文件,使用main
字段 - 若
Npm模块
只导出ESM
的入口文件,使用module
字段 - 若
Npm模块
导出CJS
与ESM
的入口文件,使用使用main/module
字段 - 若
Npm模块
只在Web
中使用且严禁在Node
中使用,使用browser
字段 - 若
Npm模块
只在Node
中使用,使用main
字段 - 若
Npm模块
能在Web
与Node
中使用,使用main/browser
字段 - 若
Npm模块
导出CJS
与ESM
的入口文件且能在Web
与Node
中使用,使用main/module/browser
字段
针对第15章开发的工具库,很明显符合最后一种情况,在package.json
中指定以下字段。
{
"main": "dist/web.js",
"jsnext:main": "dist/web.esm.js",
"module": "dist/web.esm.js",
"browser": "dist/web.umd.js"
}
为了让各个版本webpack
都能正确识别上述字段,根据字段权重配置resolve-mainFields
。webpack
在执行时,遇到Npm模块
会优先解析jsnext:main
,然后解析module
,然后解析browser
,最后解析mian
。
export default {
resolve: {
mainFields: [
"jsnext:main", // ESM
"module", // ESM
"browser", // UMD
"main" // CJS
] // 导入模块入口描述
}
};
方案:优化软件许可证书
License指软件许可证书,又称开源协议
,用于授权或约定使用者可拥有的权利与需遵从的义务。License
对应实体文件是license
或LICENSE
。
现在很多优秀的开源项目都有配置开源协议,不同开源协议的约束条件也不同,因此开源不等于免费,开源也不等于无约束。乱用开源协议但不遵守法律,那就真的可能会收到律师函了。
对于大型项目,可能有专业的律师团队撰写开源协议。可是作为一名开发者,有时想开源自己的项目但又不想自己的源码被随意借鉴、分享和抄袭,就必须撰写开源协议约束一些被行业反感的行为。
开源协议往往需具备专业知识,况且它涉及法律规则,普通人不可能在短时间内就掌握这些知识。Github
就提供了一系列流行的开源协议满足项目需求,打开创建仓库,点击License:None
,任君选择。
协议 | 简述 |
---|---|
AGPL | 拓展GPL3 ,使用在线网络服务也需开源 |
Apache | 允许他人修改源码后闭源,需对每个被改动的文件做版权说明 |
BSD2/BSD3 | 与MIT 相似,未经事先书面许可不得使用版权所有者信息做推广 |
BSL | 与GPL3 相似,无需复制版权说明 |
CCZ | 放弃作品版权并将其奉献给大众,不对源码做任何担保 |
EPL | 与GPL3 相似,有权使用、修改、复制和发布软件原始版本与迭代版本,但在某些情况下需将改动内容释出 |
GPL2 | 与GPL3 相比,若使用源码作为服务提供而不分发软件则无需开源 |
GPL3 | 无论以何种方式修改或使用源码,需开源 |
LGPL | 与GPL3 相比,允许商业软件通过类库引用使用类库而无需开源 |
MIT | 允许他人修改源码后闭源,无需对每个被改动的文件做版权说明,二次开发可用原作者信息做推广 |
Mozilla | 与LGPL 相似,需对每个被改动的文件做版权说明 |
Unlicense | 与CCZ 相似,允许开放商标与专利授权 |
若还有不懂可查看阮一峰老师对开源协议的图例。
当然不选择开源协议也行,若将源码托管到Github公有仓库
中,会默认公开源码且所有开发者都能自由View
或Fork
。当然还是在项目中撰写一份开源协议约束一些被行业反感的行为会更好。
MIT
是一份很宽松的开源协议,它允许对源码做任何形式的改动与推广,若不知如何选择就使用该协议GPL
是一份很自由的开源协议,它鼓励免费,若不介意的话可用该协议BSD
是一份很严格的开源协议,它未经事先书面许可不得使用,若介意的话可用该协议
在此贴下MIT的协议模板,毕竟它是最多项目使用的协议无之一,复制粘贴修改主体信息即可。
方案:优化项目自述文件
Readme指项目自述文件,用于为使用者提供项目详细信息。与产品说明书很像,包括但不限于背景、安装、使用、徽章、示例、维护、贡献、证书等信息。Readme
对应实体文件是readme.md
或README.md
。
很多项目越来越重视写好一个Readme
,因为Readme
最能直接反映项目的质量与好坏。优秀项目不一定有一个好的Readme
,但不好的Readme
一定不是一个优秀项目。
标准规范
Github
中有一个堪称教科书式的Readme
项目standard-readme,它为开发者提供一份标准化的编写规范。为了方便编写Readme
,我将其翻译下,得出一篇完善的Readme
需由以下部分组成,包括必选与可选。
基本上根据上述组成部分就能编写一份标准规范的Readme
了,一份最基本的Readme
必须由以下部分组成。
- Title:标题
- Short Description:简短描述
- Table of Contents:内容列表
- Install:安装
- Usage:用法
- License:许可证书
徽章图标
平时逛Npm
与Github
会发现很多仓库的Readme
都会贴上五颜六色的徽章图标,这些图标美观好看又方便,为清一色的Readme
加上了点睛之笔。我的开源项目bruce的文档站点也使用了很多美观好看的徽章图标点缀文档内容。
其实这些徽章图标都是由Shields动态生成。Shields
是一个以SVG
或光栅格式提供简洁清晰的徽章服务,可轻松地应用在Readme
或任何网页中。
该服务支持数十种持续集成服务、软件包注册表、发行版、应用商店、社交网络、代码覆盖率服务和代码分析服务,每个月提供超过7.7亿
张图像。基本上每个受欢迎的开源项目都会使用Shields
提供的徽章服务。
手动生成
打开Shields,填写label标签
与message消息
,选择 color颜色
,点击Make Badge
就会生成一个URL
,该URL
其实就是一个动态生成的图像。
img.shields.io/badge/BMW-330Li-blue
仔细观察URL
与生成图像,图标由左右两部分组成,左边是label标签
,右边是message消息
与color颜色
,由此可推算出徽章图标的生成公式是https://img.shields.io/badge/[label]-[message]-[color]
。
自动生成
有了上述公式,可根据自己想法随意拼接出不同徽章图标了。颜色除了可用颜色关键字
,还可用HEX
、RGB
、RGBA
、HSL
和HSLA
表示,若使用HEX
表示,无需加上#
。
img.shields.io/badge/Name-JowayYoung-f66
img.shields.io/badge/Sex-Male-66f
img.shields.io/badge/Age-29-f90
img.shields.io/badge/Area-Guangzhou-09f
除了这种常见URL
生成方式,还可在URL
后方加上参数实现定制。
logo与logoWidth
通过logo
设置左边内容的图标,这些图标可通过SimpleIcons获取。
img.shields.io/badge/@yangzw/bruce--app-1.0.0-f66?logo=npm
通过logoWidth
设置左边内容的图标宽度。该宽度并不是图标的宽度,而是图标的外层宽度。
img.shields.io/badge/@yangzw/bruce--app-1.0.0-f66?logo=npm&logoWidth=50
labelColor
通过labelColor
设置左边内容的背景色。背景色默认是#5d5d5d
,可用颜色关键字
、HEX
、RGB
、RGBA
、HSL
和HSLA
表示,若使用HEX
表示,无需加上#
。
img.shields.io/badge/@yangzw/bruce--app-1.0.0-f66?labelColor=66f
link
通过link
设置左右两边内容的链接。第一个link
表示左边内容的链接,第二个link
表示右边内容的链接。
img.shields.io/badge/@yangzw/bruce--app-1.0.0-f66?link=https://yangzw.vip&link=https://juejin.cn
另外Github
也提供类似的徽章服务,若要实时展示自己项目的Star
、Fork
和Watch
,可在Readme
中加入以下内容。
<iframe src="https://ghbtns.com/github-btn.html?user=JowayYoung&repo=bruce&type=star&size=large&count=true" frameborder="0" scrolling="0" width="130" height="30" title="GitHub"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=JowayYoung&repo=bruce&type=fork&size=large&count=true" frameborder="0" scrolling="0" width="130" height="30" title="GitHub"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=JowayYoung&repo=bruce&type=watch&size=large&count=true" frameborder="0" scrolling="0" width="140" height="30" title="GitHub"></iframe>
从三条URL
可知,整体链接为https://ghbtns.com/github-btn.html
,通过加上以下参数动态生成对应统计数据,然后以<iframe>
加入到Readme
。因为md文件
本身就是一个特殊的html文件
,所以加入<iframe>
也是能正常渲染的,除非MD引擎
自动把<iframe>
过滤掉。
- user:用户名称
- repo:仓库名称
- type:统计类型,可选
star/fork/watch
- size:图标尺寸,可选
small/larget
- count:是否显示计数,可选
true/false
因为是实时渲染,相信不少开发者关注bruce后,每次看都会看到不一样的统计数据。
总结
本章更多是理论与实践相结合的内容,所以必须自己动手操作一次。通过从项目配置文件
、软件许可证书
和项目自述文档
三方面入手处理好部署项目前的细节问题,也是一件让项目变得更好的事情,毕竟细节决定成败,我也相信任何细节都能导向好的结果。趁热打铁,赶紧把自己项目的细节问题处理好吧!
本章内容到此为止,希望能对你有所启发,欢迎你把自己的学习心得打到评论区!
- 示例项目:fe-engineering
- 正式项目:bruce