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 [email protected] "./deploy_cms.sh"
ssh [email protected] "./deploy_cms.sh"
因爲線上服務通常是集羣模式,而 webpack在不同服務器執行build,會產生不同的哈希值版本號,會導致遠程資源無法獲取到。所以我們需要在持續集成部署的服務器上做build操作,生成dist路徑,上傳到git和cdn。最後再到集羣的每個服務器上拉取靜態文件即可。
補充:
- 在同一臺服務器上,只要文件完全不變,我們使用vue-cli3構建生成的最終文件的哈希值版本號就不會產生改變。因此,對於用戶來說當我們更新版本時,並不會對用戶造成所有緩存文件失效的性能和體驗影響。
- 在阿里雲的CDN上,是使用協商緩存的ETag來進行文件資源緩存,因此重名新文件覆蓋舊文件時,如文件內容完全一致,Etag也會保持一致,對用戶來講也不必擔心緩存問題;如文件發生變更,用戶協商緩存也將無法命中,就會取新的資源文件。
- 有些方法是把靜態資源的請求發到Nginx,然後再轉發到CDN地址。筆者認爲,這樣會造成所有資源需要重定向、並且在Nginx上無法設置緩存信息,性能上不如本文介紹的直接構建生成CDN地址的HTML文件的方法。
通過這套操作,最終我們實現了在TeamCity上,一鍵執行打包、上傳CDN、部署的整個流程。