本文讲解如何构建一个工程化的前端库,并结合 Github Actions,自动发布到 Github 和 NPM 的整个详细流程。
我们经常看到像 Vue、React 这些流行的开源项目有很多配置文件,他们是干什么用的?他们的 Commit、Release 记录都那么规范,是否基于某种约定?
废话少说,先上图!
上图标红就是相关的工程化配置,有 Linter、Tests,Github Actions 等,覆盖开发、测试、发布的整个流程。
相关配置清单
下面我们从创建一个 TypeScript 项目开始,一步一步完成所有的工程化配置,并说明每个配置含义以及容易踩的坑。
为了避免兼容性问题,建议先将 node 升级到最新的长期支持版本。
首先在 Github 上创建一个 repo,拉下来之后通过npm init -y
初始化。然后创建src
文件夹,写入index.ts
。
package.json
生成之后,我需要添加如下配置项:
"main": "index.js",
+ "type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
+ "publishConfig": {
+ "access": "public"
+ }
我们将项目定义为ESM规范,前端社区正逐渐向ESM标准迁移,从Node v12.0.0开始,只要设置了 "type": "module"
, Node 会将整个项目视为ESM规范,我们就可以直接写裸写import/export
。
publishConfig.access
表示当前项目发布到NPM的访问级别,它有 restricted
和public
两个选项,restricted
表示我们发布到NPM上的是私有包(收费),访问级别默认为restricted
,因为我们是开源项目所以标记为public
。
创建项目之后,我们开始安装工程化相关的依赖,因为我们是 TypeScript 项目,所以也需要安装 TypeScript 的依赖。
先安装 TypeScript,然后使用 tsc
命名生成 tsconfig.json
。
npm i typescript -D
npx tsc --init
然后我们需要添加修改 tsconfig.json
的配置项,如下:
{
"compilerOptions": {
/* Basic Options */
"baseUrl": ".", // 模块解析根路径,默认为 tsconfig.json 位于的目录
"rootDir": "src", // 编译解析根路径,默认为 tsconfig.json 位于的目录
"target": "ESNEXT", // 指定输出 ECMAScript 版本,默认为 es5
"module": "ESNext", // 指定输出模块规范,默认为 Commonjs
"lib": ["ESNext", "DOM"], // 编译需要包含的 API,默认为 target 的默认值
"outDir": "dist", // 编译输出文件夹路径,默认为源文件同级目录
"sourceMap": true, // 启用 sourceMap,默认为 false
"declaration": true, // 生成 .d.ts 类型文件,默认为 false
"declarationDir": "dist/types", // .d.ts 类型文件的输出目录,默认为 outDir 目录
/* Strict Type-Checking Options */
"strict": true, // 启用所有严格的类型检查选项,默认为 true
"esModuleInterop": true, // 通过为导入内容创建命名空间,实现 CommonJS 和 ES 模块之间的互操作性,默认为 true
"skipLibCheck": true, // 跳过导入第三方 lib 声明文件的类型检查,默认为 true
"forceConsistentCasingInFileNames": true, // 强制在文件名中使用一致的大小写,默认为 true
"moduleResolution": "Node", // 指定使用哪种模块解析策略,默认为 Classic
},
"include": ["src"] // 指定需要编译文件,默认当前目录下除了 exclude 之外的所有.ts, .d.ts,.tsx 文件
}
更多详细配置参考:www.typescriptlang.org/tsconfig
注意的点,如果你的项目涉及到WebWorker API
,需要添加到 lib
字段中
"lib": ["ESNext", "DOM", "WebWorker"],
然后我们将编译后的文件路径添加到 package.json
,并在 scripts
中添加编译命令。
- "main": "index.js",
+ "main": "dist/index.js",
+ "types": "dist/types/index.d.ts"
"type": "module",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
+ "scripts": {
+ "dev": "tsc --watch",
+ "build": "npm run clean && tsc",
+ "clean": "rm -rf dist"
+ },
"publishConfig": {
"access": "public"
}
types
配置项是指定编译生成的类型文件,如果 compilerOptions.declarationDir
指定的是dist
,也就是源码和 .d.ts
同级,那么types
可以省略。
验证配置是否生效,在 index.ts
写入
const calc = (a: number, b: number) => {
return a - b
}
console.log(calc(1024, 28))
在控制台中执行
npm run build && node dist/index.js
会在 dist
目录中生成 types/index.d.ts
、index.js
、index.js.map
,并打印 996
。
代码规范离不开各种 Linter
, 之所以把这两个放在一起讲,借用 Prettier
官网的一句话:“使用 Prettier 解决代码格式问题,使用 linters 解决代码质量问题”。虽然eslint
也有格式化功能,但是prettier
的格式化功能更强大。
大部分同学编辑器都装了prettier-vscode和eslint-vscode这两个插件,如果你项目只有其中一个的配置,因为这两者部分格式化的功能有差异,那么就会造成一个的问题,代码分别被两个插件分别格式化一次,网上解决prettier
+eslint
冲突的方案五花八门,甚至还有把整个rules
列表贴出来的。
那这里我们按照官方推荐,用最少的配置去解决prettier
和eslint
的集成问题。
首先安装 eslint
,然后利用 eslint
的命令行工具生成基本配置。
npm i eslint -D
npx eslint --init
执行上面命令后会提示一些选项,我们依次选择符合我们项目的配置。
注意,这里 eslint
推荐了三种社区主流的规范,Airbnb、Standard、Google,因个人爱好我选择了不写分号的 Standard规范。
生成的.eslintrc.cjs
文件应该长这样
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'standard'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module'
},
plugins: [
'@typescript-eslint'
],
rules: {
}
}
有些同学可能就要问了,这里为什么生成的配置文件名称是.eslintrc.cjs
而不是.eslintrc.js
?
因为我们将项目定义为ESM
,eslit --init
会自动识别type
,并生成兼容的配置文件名称,如果我们改回.js
结尾,再运行eslint
将会报错。出现这个问题是eslint
内部使用了require()
语法读取配置。
同样,这个问题也适用于其他功能的配置,比如后面会讲到的Prettier
、Commitlint
等,配置文件都不能以xx.js
结尾,而要改为当前库支持的其他配置文件格式,如:.xxrc
、.xxrc.json
、.xxrc.yml
。
验证配置是否生效,修改index.ts
const calc = (a: number, b: number) => {
return a - b
}
- console.log(calc(1024, 28))
+ // console.log(calc(1024, 28))
在package.json
中添加lint
命令
"scripts": {
"dev": "tsc --watch",
"build": "npm run clean && tsc",
+ "lint": "eslint src --ext .js,.ts --cache --fix",
"clean": "rm -rf dist"
},
然后在控制台执行 lint
,eslint
将会提示 1 条错误信息,说明校验生效。
npm run lint
# 1:7 error 'calc' is assigned a value but never used no-unused-vars
因为是 Typescript 项目所以我们还要添加Standard规范提供的 TypeScrip 扩展配置(其他规范同理)
安装eslint-config-standard-with-typescript
npm i eslint-config-standard-with-typescript -D
添加修改 .eslintrc.cjs
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
- extends: ['standard']
+ extends: ['standard', 'eslint-config-standard-with-typescript'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
+ project: './tsconfig.json'
},
plugins: ['@typescript-eslint'],
rules: {}
}
验证配置是否生效
在控制台执行lint
,eslint
将会提示 2 条错误信息,说明校验生效。
npm run lint
# 1:7 error 'calc' is assigned a value but never used no-unused-vars
# 1:14 error Missing return type on function
现在我们按照官网的推荐方式,把 prettier
集成到 eslint
的校验中。
安装 prettier
并初始化配置文件
npm i prettier -D
echo {}> .prettierrc.json
然后在.prettierrc.json
添加配置,这里只需要添加和你所选规范冲突的部分。
{
"semi": false, // 是否使用分号
"singleQuote": true, // 使用单引号代替双引号
"trailingComma": "none" // 多行时尽可能使用逗号结尾
}
更多配置详见:prettier.io/docs/en/opt…
安装解决冲突需要用到的两个依赖
prettier
冲突的规则prettier
代替 eslint
格式化npm i eslint-config-prettier eslint-plugin-prettier -D
再添加修改 .eslintrc.cjs
,如下:
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
- extends: ['standard', 'eslint-config-standard-with-typescript'],
+ extends: ['standard', 'eslint-config-standard-with-typescript', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
project: './tsconfig.json',
},
- plugins: ['@typescript-eslint'],
+ plugins: ['@typescript-eslint', 'prettier'],
- rules: {},
+ rules: {
+ 'prettier/prettier': 'error'
+ },
}
然后验证配置是否生效,修改index.ts
- const calc = (a: number, b: number) => {
+ const calc = (a: number, b: number): number => {
return a - b
}
- // console.log(calc(1024, 28))
+ console.log(calc(1024, 28))
然后在控制台执行lint
,这里prettier
和eslint
的行为已保持一致,如果没有报错,那就成功了。
npm run lint
我们现在已经完成了eslint
和prettier
的集成配置。和编辑器无关,也就是说无论你使用什么编辑器,有没有安装相关插件,都不会影响代码校验的效果。
因为一个项目通常是团队合作,我们不能保证每个人在提交代码之前执行一遍lint
校验,所以需要git hooks
来自动化校验的过程,否则禁止提交。
安装Husky
并生成.husky
文件夹
npm i husky -D
npx husky install
然后我们需要在每次执行npm install
时自动启用husky
如果你的npm
版本大于等于7.1.0
npm set-script prepare "husky install"
否则手动在package.json
中添加
"scripts": {
"dev": "tsc --watch",
"build": "npm run clean && tsc",
"lint": "eslint src --ext .js,.ts --cache --fix",
"clean": "rm -rf dist",
+ "prepare": "husky install"
},
然后添加一个lint
钩子
npx husky add .husky/pre-commit "npm run lint"
相当于手动在.husky/pre-commit
文件写入以下内容:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint
测试钩子是否生效,修改index.ts
const calc = (a: number, b: number): number => {
return a - b
}
- console.log(calc(1024, 28))
+ // console.log(calc(1024, 28))
然后提交一条commit
,如果配置正确将会自动执行lint
并提示 1 条错误信息,commit
提交将会失败。
git add .
git commit -m 'test husky'
# 1:7 error 'calc' is assigned a value but never used
为什么需要 Commitlint
,除了在后续的生成changelog
文件和语义发版中需要提取commit
中的信息,也利于其他同学分析你提交的代码,所以我们要约定commit
的规范。
安装 Commitlint
npm i @commitlint/config-conventional @commitlint/cli -D
最后将Commitlint
添加到钩子
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
创建.commitlintrc
,并写入配置
{
"extends": [
"@commitlint/config-conventional"
]
}
注意,这里配置文件名使用的是.commitlintrc
而不是默认的.commitlintrc.js
,详见 Eslint 章节
测试钩子是否生效,修改index.ts
,让代码正确
const calc = (a: number, b: number): void => {
console.log(a - b)
}
- // calc(1024, 28)
+ calc(1024, 28)
提交一条不符合规范的commit
,提交将会失败
git add .
git commit -m 'add eslint and commitlint'
修改为正确的commit
,提交成功!
git commit -m 'ci: add eslint and commitlint'
Angular 规范说明:
美好生活从测试覆盖率 100% 开始。
安装jest
,和类型声明@types/jest
,它执行需要ts-node
和ts-jest
这里暂时固定了ts-node
的版本为 v9.1.1
,新版的ts-node@v10.0.0
会导致jest
报错,等待官方修复,详见:issues
npm i jest @types/jest ts-node@9.1.1 ts-jest -D
初始化配置文件
npx jest --init
然后修改jest.config.ts
文件
// A preset that is used as a base for Jest's configuration
- // preset: undefined,
+ preset: 'ts-jest'
将测试命令添加到package.json
中。
"scripts": {
"dev": "tsc --watch",
"build": "npm run clean && tsc",
"lint": "eslint src --ext .js,.ts --cache --fix",
"clean": "rm -rf dist",
"prepare": "husky install",
+ "test": "jest"
},
创建测试文件夹__tests__
和测试文件__tests__/calc.spec.ts
修改index.ts
const calc = (a: number, b: number): number => {
return a - b
}
- // console.log(calc(1024, 28))
+ export default calc
然后在calc.spec.ts
中写入测试代码
import calc from '../src'
test('The calculation result should be 996.', () => {
expect(calc(1024, 28)).toBe(996)
})
验证配置是否生效
在控制台执行test
,将会看到测试覆盖率 100% 的结果。
npm run test
最后我们给__tests__
目录也加上lint
校验
修改package.json
"scripts": {
"dev": "tsc --watch",
"build": "npm run clean && tsc",
- "lint": "eslint src --ext .js,.ts --cache --fix",
+ "lint": "eslint src __tests__ --ext .js,.ts --cache --fix",
"clean": "rm -rf dist",
"prepare": "husky install",
"test": "jest"
},
这里如果我们直接执行npm run lint
将会报错,提示__tests__
文件夹没有包含在tsconfig.json
的include
中,当我们添加到include
之后,输出的dist
中就会包含测试相关的文件,这并不是我们想要的效果。
我们使用typescript-eslint
官方给出的解决方案,如下操作:
新建一个tsconfig.eslint.json
文件,写入以下内容:
{
"extends": "./tsconfig.json",
"include": ["**/*.ts", "**/*.js"]
}
在.eslintrc.cjs
中修改
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
- project: './tsconfig.json'
+ project: './tsconfig.eslint.json'
},
然后验证配置是否生效,直接提交我们添加的测试文件,能正确提交说明配置成功。
git add .
git commit -m 'test: add unit test'
我们通过Github Actions
实现代码合并或推送到主分支,dependabot
机器人升级依赖等动作,会自动触发测试和发布版本等一系列流程。
在项目根目录创建.github/workflows
文件夹,然后在里面新建ci.yml
文件和cd.yml
文件
在ci.yml
文件中写入:
name: CI
on:
push:
branches:
- '**'
pull_request:
branches:
- '**'
jobs:
linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16
- run: npm ci
- run: npm run lint
tests:
needs: linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16
- run: npm ci
- run: npm run test
上面配置大概意思就是,监听所有分支的push
和pull_request
动作,自动执行linter
和tests
任务。
GithubActions 更多用法参考:github.com/features/ac…
然后推送代码,验证配置是否生效
git add .
git commit -m 'ci: use github actions'
git push
此时打开当前项目的 Github 页面,然后点击顶部 Actions 菜单就会看到正在进行的两个任务,一个将会成功(测试),一个将会失败(发布)。
上面只是实现了代码自动测试流程,下面实现自动发布的流程。
在此之前需要到NPM网站上注册一个账号(已有可忽略),并创建一个package
。
然后创建GH_TOKEN
和NPM_TOKEN
(注意,不要在代码中包含任何的 TOKEN 信息):
将创建好的两个TOKEN
添加到项目的 Actions secrets 中:
Github 项目首页 -> 顶部 Settings 菜单 -> 侧边栏 Secrets
然后修改package.json
中的“name”
,“name”
就是你在NPM上创建的package
的名称。
在cd.yml
文件中写入:
name: CD
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16
# https://github.com/semantic-release/git/issues/209
- run: npm ci --ignore-scripts
- run: npm run build
- run: npx semantic-release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
由于“黑命贵”,Github 已将新项目的默认分支名称更改为 “main”
,详见:issues, 为了方便,后面统一称为 主分支
所以如果你的主分支名称是“main”
,上面的branches
需要修改为:
on:
push:
branches:
- main
pull_request:
branches:
- main
然后安装语义发版依赖,需要用到semantic-release
和它的插件:
npm i semantic-release @semantic-release/changelog @semantic-release/git -D
在项目根目录新建配置文件.releaserc
并写入:
{
"branches": ["master"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/github",
"@semantic-release/npm",
"@semantic-release/git"
]
}
这里同样,如果你的主分支名称是“main”
,上面的branches
需要修改为:
"branches": ["+([0-9])?(.{+([0-9]),x}).x", "main"],
最后新建分支 develop 分支并提交工作内容。
git checkout -b develop
git add .
git commit -m 'feat: complete the CI/CD workflow'
git push --set-upstream origin develop
git push
然后将 develop 分支合并到 主分支,并提交,注意:这个提交会触发测试并 发布版本 (自动创建tag
和changelog
)
git checkout master
git merge develop
git push
完成上面操作之后,打开 Github 项目主页 和 NPM 项目主页 可以看到一个 Release 的更新记录。
最后切回到 develop 分支,创建一个自动更新依赖的workflow
。
在.github
文件夹中创建dependabot.yml
文件,并写入内容:
version: 2
updates:
# Enable version updates for npm
- package-ecosystem: 'npm'
# Look for `package.json` and `lock` files in the `root` directory
directory: '/'
# Check the npm registry for updates every day (weekdays)
schedule:
interval: 'weekly'
提交并查看 workflows 是否全部通过,再合并到 主分支 并提交,这个提交不会触发版本发布。
git pull origin master
git add .
git commit -m 'ci: add dependabot'
git push
git checkout master
git merge develop
git push
触发版本发布需要两个条件:
push
和pull_request
到 主分支 上才会触发版本发布commit
前缀为feat
、fix
、perf
才会发布,否则跳过。更多发布规则,详见:github.com/semantic-re…
SemanticRelease 使用方式,详见:semantic-release.gitbook.io
如果你能正确配置上面所有步骤,并成功发布,那么恭喜你!你拥有了一个完全自动化的项目,它拥有:自动依赖更新、测试、发布,和自动生成版本信息等功能。
完整的项目示例:@resreq/event-hub
本文未涉及到:组件库、Monorepo、Jenkins CI 等配置,但能覆盖绝大部前端项目 CI/CD 流程。
有些地方讲得比较细,甚至有些啰嗦,但还是希望能帮助到大家!撒花!
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/bFQxpDU6u4GnURADgnxU7Q
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。