乐趣区

Vue项目构建持续集成阿里云CDN

CDN 加速是 Web 应用性能优化和用户体验提升的至关重要的一环,当一个项目构建部署时,就需要考虑到如何高效的去完成相关资源的 CDN 部署。
本文以一个基于 vue-cli3 构建的项目实例,来简单讲解如何配合 Teamcity,自动进行阿里云 CDN 资源部署和持续集成。
项目构建
vue-cli3 默认支持将项目以 test、development、production 三种模式构建,其中 production 模式将在 build 后生成 dist 目录。我们在项目路径下插入 .env.[mode] 格式的文件就可以实现自定义模式。
通常,默认的构建模式无法满足项目研发需求。一个项目至少需要包含

本地调试 – 即开发过程中的 development 模式,不生成 dist 静态目录,使用 vue-dev-server 运行项目;
测试环境 – 即基本的集成测试,需要文件静态化,部署到测试环境;
线上环境 – 即用户环境,也需要文件静态化,并做 CDN 加速等性能优化措施;

按照这个模型,我们需要自定义一个 deploy 模式,来实现和普通 production 打包后,资源引入路径的区别。
首先,环境创建
在项目根目录下创建 .env.deploy 文件,添加内容如下:
NODE_ENV=production
DEPLOY=online
NODE_ENV 的设置代表 webpack 构建时使用 production 模式,即会生成 dist 静态目录。DEPLOY 的设置,是一个我们定义的变量,用于在配置中区分 deploy 和 production 模式。
其次,配置文件
在 vue.config.js 中,配置 BASE_URL
// 根据自定义的变量来进行内容设置
let BASE_URL = ‘/’
switch(process.env.DEPLOY) {
case ‘online’:
BASE_URL = ‘http://web-cdn.xxx.com/’
break
default:
BASE_URL = ‘/’
}
module.exports = {
publicPath: BASE_URL,
….
}
该配置会使得当程序使用 deploy 模式运行时,打包的资源根路径为我们的 CDN 地址。
最后,构建命令
在 package.json 中,配置使用 deploy 模式的打包命令
“scripts”: {
“build”: “vue-cli-service build”,
“deploy”: “vue-cli-service build –mode deploy”,

}
当用户执行 npm run build 时,会生成以 / 为资源路径的文件;当用户执行 npm run deploy 时,生成 index.html 中的资源路径就变成了我们配置的 CDN 路径。
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content=”IE=edge”>
<meta name=viewport content=”width=device-width,initial-scale=1″>
<link rel=icon href=http://web-cdn.xxx.com/favicon.ico>
<title>Demo</title>
<link href=http://web-cdn.xxx.com/css/chunk-0fabbc4c.08fa0fd2.css rel=prefetch>
<link href=http://web-cdn.xxx.com/css/chunk-1025f268.0dc416de.css rel=prefetch>
<link href=http://web-cdn.xxx.com/js/app.84dcc9e6.js rel=preload as=script>
</head>
<body>
<div id=app></div>
<script src=http://web-cdn.xxx.com/js/chunk-vendors.614ecc0c.js></script>
<script src=http://web-cdn.xxx.com/js/app.84dcc9e6.js></script>
</body>
</html>
阿里云 CDN 配置和上传
接下来,我们要做的就是配置一个 CDN,并能够把这些资源传上去。
首先,在阿里云上配置 CDN,做好域名 CNAME 解析,并获取到阿里云的 accessKeyId、accessKeySecret、Region、BucketName 等信息,然后选择一种语言,写好上传脚本。
这里我们以 Node 脚本为例:
// oss-deploy.js

let OSS = require(‘ali-oss’)
let fs = require(‘fs’)

let client = new OSS({
region: ‘oss-cn-hangzhou’,
accessKeyId: ‘xxx’,
accessKeySecret: ‘xxx’,
bucket: ‘xxx’
})

// 使用 async+await 方法,实现同步化,方便在失败后重试处理
async function put(fileName) {
try {
let result = await client.put(fileName, ‘../dist/’ + fileName)
console.log(‘File Upload Success: ‘, fileName)
} catch (e) {
console.log(‘File Upload Failed: ‘, fileName)
// 这里省略异常 / 失败的重试
}
}

// 读取打包后的 dist 路径,按照原文件夹结构,进行上传
let readFileList = (path, filesList) => {
let files = fs.readdirSync(path)
files.forEach(itm => {
if (itm) {
let stat = fs.statSync(path + itm)
if (stat.isDirectory()) {
readFileList(path + itm + ‘/’, filesList)
} else {
filesList.push(path + itm)
}
}
})
return filesList
}
let dist = readFileList(‘../dist/’, [])

// 递归执行文件上传操作
let i = 0, l = dist.length
let uploadAsset = () => {
if (i < l) {
let name = dist[i].split(‘../dist/’)[1]
put(name)
i++
uploadAsset()
}
}
uploadAsset()
执行
npm install –save-dev ali-oss
node oss-deploy.js
即可看到文件已经被上传到了 CDN 路径下。
持续集成
上面的两个模块,已经实现了基本的 CDN 部署。但我们在项目开发的时候,肯定不希望每次 build 完,都去自己执行上传 CDN,再去服务器上部署。
这里我们再把 TeamCity 上实现自动 build、一键上线的流程简单阐述。
TeamCity 上的执行脚本如下:
cd /apps/kaleido-cms/
git pull -f origin master

npm install
npm run deploy

git add dist/*
git commit -m “Deploy”
git push origin master

cd /apps/kaleido-cms/deploy
node oss-deploy.js

ssh root@10.0.0.1 “./deploy_cms.sh”
ssh root@10.0.0.2 “./deploy_cms.sh”
因为线上服务通常是集群模式,而 webpack 在不同服务器执行 build,会产生不同的哈希值版本号,会导致远程资源无法获取到。所以我们需要在持续集成部署的服务器上做 build 操作,生成 dist 路径,上传到 git 和 cdn。最后再到集群的每个服务器上拉取静态文件即可。

补充:

在同一台服务器上,只要文件完全不变,我们使用 vue-cli3 构建生成的最终文件的哈希值版本号就不会产生改变。因此,对于用户来说当我们更新版本时,并不会对用户造成所有缓存文件失效的性能和体验影响。
在阿里云的 CDN 上,是使用协商缓存的 ETag 来进行文件资源缓存,因此重名新文件覆盖旧文件时,如文件内容完全一致,Etag 也会保持一致,对用户来讲也不必担心缓存问题;如文件发生变更,用户协商缓存也将无法命中,就会取新的资源文件。
有些方法是把静态资源的请求发到 Nginx,然后再转发到 CDN 地址。笔者认为,这样会造成所有资源需要重定向、并且在 Nginx 上无法设置缓存信息,性能上不如本文介绍的直接构建生成 CDN 地址的 HTML 文件的方法。

通过这套操作,最终我们实现了在 TeamCity 上,一键执行打包、上传 CDN、部署的整个流程。

退出移动版