去年5月CUPOJ前端使用Vue进行了重构,因此使用Vue-CLI打包变成了每次发布新的变更必不可少的一个任务。
由于前端项目本身代码量较大,逻辑也比较复杂,打包起来对性能要求相对较高。平时都是在学校服务器直接打包后复制dist
目录下的文件到指定文件夹来部署。
每次都执行同样的代码比较痛苦,便写了一个简单的脚本,把整个过程变成了一键部署。若是能够把整个过程CI化,会大大提升平时发布的效率。
于是学习了Lucien的Github Actions以及部署CDN的方式,进行了一下改造。
目标
- 使用CI自动编译并发布Release版本
- 自动部署代码到生产环境
- CDN加速静态资源
构建Github Actions工作流
根据给出的文档,Github Actions会自动扫描repo下.github/workflows
目录下的*.yml
文件,载入Actions workflow中。
由于许多的流程是共通的,因此官方把这些共通的流程发布成为一个模块,称作action
。每个用户都可以使用别人发布的action
,也可以自己发布action
。这一点我不在这里展开,这里重点将如何利用它们进行发布。
参考官方文档给出的语法,我们总结出一个大致的yml格式。
对于一个workflow
,我们要先对它命名:
name: Publish releases to CDN repository
给出workflow
的触发条件:
on:
push:
branches:
- typescript
paths:
- 'package.json'
- 'src/**'
- 'public/**'
需要完成的工作:
其中publish
是jobs
的名称,你可以随意命名。一个jobs
可以有多个执行的job
。
若job
存在if
语句,则条件成立时执行job
。
if
语句的逻辑请参考官方文档。
step
是workflow
执行的步骤。 按编写顺序执行。
这里简单介绍一下step
包含的内容:
一个step
可以是引用别人的action
,如下面的Checkout code
。
根据action
要求填入的with
属性,输入需要填写的内容。
对于每个step
,给出对应的name
为该操作命名。
可以用if
条件语句决定step
是否执行。
该语句相关语法请参考官方文档。
run
语法可以执行一至多行命令。
run: # 单行命令
run: |
# 多
# 行
# 命
# 令
env
: 为每个step
加入环境变量。
由于我们需要自动编译并发布,因此我需要建立一个提供Node.js的发布环境,并从repo拉取代码,安装依赖并编译后,发布到目标的仓库。
为了能够发布到目标的仓库,我们需要生成token,用于push操作验证身份。
前往https://github.com/settings/tokens可生成token。
由于token需要被保密,我们在Github中你需要执行Action的仓库点击Settings
,在左边的菜单找到Secrets
填入你刚才生成的token, 命名为GITHUB_ACTIONS_TOKEN
。你也可以命名为其他的变量名。后面会在文件中使用。
准备工作就绪以后,在.github/workflows
目录创建一个yml文件,编写action
name: Publish releases to CDN repository
on:
push:
branches:
- typescript
paths:
- 'package.json'
- 'src/**'
- 'public/**'
jobs:
publish:
if: github.repository == 'ryanlee2014/CUP-Online-Judge-NG-FrontEnd'
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v2
name: Checkout code
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
- name: Build env
run: |
echo "::set-env name=NEED_BUILD::$(cat NEED_BUILD)"
- name: Use Node.js environment
if: env.NEED_BUILD == 'true'
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Build distribution if needed
if: env.NEED_BUILD == 'true'
run: |
npm i
npm run modern
- name: Update dist folder to current repository
if: env.NEED_BUILD == 'true'
run: |
git config --local user.email "gxlhybh@gmail.com"
git config --local user.name "Ryan Lee"
git add dist
git commit -m "Auto commit version $(cat public/VERSION)"
git push -f https://ryanlee2014:$GITHUB_TOKEN@github.com/ryanlee2014/CUP-Online-Judge-NG-FrontEnd.git typescript
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_ACTIONS_TOKEN }}
- uses: actions/checkout@v2
name: Checkout CDN Repository
with:
repository: ryanlee2014/CUP-Online-Judge-CDN
path: cdn
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
- name: Move file to another repository
run: cp -r dist/* cdn/
- name: Commit files and push
run: |
cd cdn
git config --local user.email "gxlhybh@gmail.com"
git config --local user.name "Ryan Lee"
git add --all
git commit -m "deploy `TZ=UTC-8 date +'%Y-%m-%d %H:%M:%S'`"
git tag "v`cat VERSION`"
git push -f https://ryanlee2014:$GITHUB_TOKEN@github.com/ryanlee2014/CUP-Online-Judge-CDN.git master
git push --tags https://ryanlee2014:$GITHUB_TOKEN@github.com/ryanlee2014/CUP-Online-Judge-CDN.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_ACTIONS_TOKEN }}
这个过程的关键在于最后的Commit files and push
。刚才我们生成了token, 并命名为GITHUB_ACTIONS_TOKEN
。这里可以通过${{}}语法引入yml文件中,通过secret.GITHUB_ACTIONS_TOKEN访问到。
如上所示,通过将GITHUB_TOKEN作为环境变量引入,便可push到目标的仓库中。
发布Release
由于Github可以通过tag发布Release, 因此只需要在执行完打包后添加一个tag并push到目标repo即可。核心代码如下:
git config --local user.email "gxlhybh@gmail.com"
git config --local user.name "Ryan Lee"
git add --all
git commit -m "deploy `TZ=UTC-8 date +'%Y-%m-%d %H:%M:%S'`"
git tag "v`cat VERSION`"
git push -f https://ryanlee2014:$GITHUB_TOKEN@github.com/ryanlee2014/CUP-Online-Judge-CDN.git master
git push --tags https://ryanlee2014:$GITHUB_TOKEN@github.com/ryanlee2014/CUP-Online-Judge-CDN.git
这部分代码便是上面yml文件的最后一个step
。
自动部署到目标环境
Github提供了webhook功能,可以在Settings
-> Webhook
中设置。
点击Add Webhook
, 便可进入添加webhook界面。
通过设置Webhook触发的URL和path和触发Secret(随便设置一个并记录下来), 设置Content-type为application/json,便完成初期工作。
URL格式:${href}${pathname}, 如http://oj.cupacm.com/webhook 中 http://oj.cupacm.com是href, /webhook 是pathname
接着我们可以通过一个webhook daemon服务来响应Github的POST操作。这里参考CUP-Online-Judge-Webhook-Service
在src/config.json
中有四个参数项,分别是
{
"secret": "#刚才设置的secret",
"path": "#触发webhook的pathname",
"port": #监听的端口,
"shell": "#执行shell文件的命令"
}
使用nginx或其他Web Server将webhook的pathname反代到服务端口,即可触发Webhook, 程序会自动执行shell文件。
只需要在shell文件中编写好自动部署的脚本即可完成全自动部署工作。
使用JSDelivr作为CDN加速
前面我们使用CI发布了Release, 因此我们可以通过JSDelivr给出的Github Release包访问的文档,从JSDelivr CDN上拉取静态资源。
根据官网给出的访问方式如下:
// load any GitHub release, commit, or branch
// note: we recommend using npm for projects that support it
https://cdn.jsdelivr.net/gh/user/repo@version/file
// load jQuery v3.2.1
https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/dist/jquery.min.js
// use a version range instead of a specific version
https://cdn.jsdelivr.net/gh/jquery/jquery@3.2/dist/jquery.min.js
https://cdn.jsdelivr.net/gh/jquery/jquery@3/dist/jquery.min.js
// omit the version completely to get the latest one
// you should NOT use this in production
https://cdn.jsdelivr.net/gh/jquery/jquery/dist/jquery.min.js
// add ".min" to any JS/CSS file to get a minified version
// if one doesn't exist, we'll generate it for you
https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/src/core.min.js
// add / at the end to get a directory listing
https://cdn.jsdelivr.net/gh/jquery/jquery/
然而这并不能完全把请求转向CDN。
参考CUP-Online-Judge-NG-FrontEnd
前面发布的指令中,对于每个Release包,均从VERSION
文件中读取Release版本,而根据repo中给出的打包过程,我们发现VERSION
文件版本是从package.json
中读取version
字段生成的。因此我们可以根据此修改vue.config.js
文件,使Vue-Router使用CDN资源
给出如下例子
const version = require("./package.json").version;
const webPath = `https://cdn.jsdelivr.net/gh/ryanlee2014/CUP-Online-Judge-CDN@v${version}/`;
module.exports = {
publicPath: process.env.NODE_ENV === "production" && !process.env.DISABLE_CDN ? webPath : "/"
};
通过设置publicPath, 我们可以把请求转到CDN文件上,实现静态资源CDN化的目标。
我估计上面的内容没几个人能看得懂,不过我还是要先写下来备忘