【前端打包部署】談一談我在SPA專案打包=>部署的處理

王玲波_柏林發表於2019-01-23

前言

在上篇《【vue-cli3升級】老專案提速50%(二)》文中,評論區好幾個人對文中task任務以及shell打包推送遠端倉庫表示感興趣,希望我多描述些內容... 基於此原因、在此基礎上,談一談我在SPA專案打包 => 部署的工程化內容

PS:只作為本人工作的分享,不作為任何形式的指南、教程、最佳實踐等,也並非手把手系列

專案環境描述

  • SPA專案(Vue-cli3)
  • Gitlab程式碼倉庫
  • node 10.8.0
  • npm 6.5.0
  • Nginx伺服器
  • Linux系統
  • Jenkins持續整合部署工具

說下上篇文中Task.jsrun.sh

以下內容請結合《【vue-cli3升級】老專案提速50%(一)》《【vue-cli3升級】老專案提速50%(二)》閱讀

為什麼這麼做?

結合實際場景羅列兩個吧...

  • 為了隨時釋出前端專案,不用等到晚上加班釋出...
  • 曾經,在釋出過程中,老闆正在瀏覽…然後白屏了…所以為了釋出時線上使用者無感知

整體流程(畫圖真墨跡啊)

【前端打包部署】談一談我在SPA專案打包=>部署的處理

  1. 執行 run.sh ,以 beta 環境為例,專案根目錄執行 ./run.sh beta

  2. run.sh 中,初始化beta環境的遠端倉庫,拉取程式碼到本地 .deploy/beta 目錄,將 .deploy/beta 目錄中的檔案複製到本地 dist 目錄

  3. run.sh 中,執行構建: npm run build_beta

  4. vue.config.js 中,建立版本號 lastVersion,將版本號 lastVersion 加入到 cssjs 資原始檔名中(我是加在最前面的,位置隨意,和 task.js 中的正則對應)

  5. vue.config.js 中,注入 task.js ,執行任務

    const task = require('./task')
    task.run(lastVersion)
    複製程式碼
  6. task.js 中,將版本號寫入 history.js ,判斷是否超出最大值(最大5個歷史版本),超出則執行 rmFiles() ,刪除本地 dist 目錄中最老的版本資源

  7. 複製本地 dist 目錄檔案到 .deploy/beta 中,提交 .deploy/beta 檔案到遠端倉庫

附圖:瞭解下.deploy/beta目錄檔案,history.js檔案,遠端Gitlab倉庫程式碼

【前端打包部署】談一談我在SPA專案打包=>部署的處理

【前端打包部署】談一談我在SPA專案打包=>部署的處理

【前端打包部署】談一談我在SPA專案打包=>部署的處理

【Task.js】

前提:打包時不清除dist檔案目錄,以增量的形式打包。

vue-cli3下可參考《【vue-cli3升級】老專案提速50%(二)》

vue-cli2下在 /build/build.js 中找到以下程式碼註釋掉

// rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
//   if (err) throw err
// })
複製程式碼

何時執行?build構建時

作用:利用nodeJs的fs模組操作history.js,儲存最多5個版本,超過5個在本地dist目錄中刪除匹配最老版本的相關資源

程式碼(基本上都加上註釋描述了)

const fs = require('fs') // node fs模組,操作檔案
const path = require('path') // node path模組,操作路徑
const endOfLine = require('os').EOL // node os模組,不同作業系統的行尾符

module.exports = {
  maxHistoryNum: 5, // 控制儲存版本最大值
  historyFile: path.resolve(__dirname, './dist/history.js'), // 本地dist目錄中的history.js
  staticDir: path.resolve(__dirname, './dist/'), // 本地dist目錄
	// history.js不存在,建立
  creataHistoryIfNotExist () {
    if (!fs.existsSync(this.historyFile)) {
      this.storeHistory([], 'a+')
    }
  },

  // @done 將資料寫到 history.js
  storeHistory (list, mode) {
    let historyFile = this.historyFile
    let outJson = 'module.exports = [' + endOfLine
    let listLen = list.length
    if (list && listLen > 0) {
      list.forEach((item, index) => {
        if (index === listLen - 1) {
          outJson += `  ${item}${endOfLine}`
        } else {
          outJson += `  ${item},${endOfLine}`
        }
      })
    }
    outJson += ']' + endOfLine
		// 以 a+ 的falg標誌在history.js寫入和追加內容
    fs.writeFileSync(historyFile, outJson, {
      flag: mode
    })
  },

  // 遞迴刪除目錄中的檔案 (構建完成時版本>最大值時執行)
  rmFiles (dirPath, regexp) {
    let files
    try {
      files = fs.readdirSync(dirPath)
    } catch (e) {
      return
    }
    if (regexp && files && files.length > 0) {
      for (let i = 0; i < files.length; i++) {
        let filename = files[i]
        let filePath = dirPath + '/' + files[i]
        if (fs.statSync(filePath).isFile() && regexp.test(filename)) {
          console.log('刪除過期的歷史版本->(' + regexp + '):' + filename)
          fs.unlinkSync(filePath)
        } else {
          this.rmFiles(filePath, regexp)
        }
      }
    }
  },

  // @done 更新history.js內容
  cleanOldVersionFilesIfNeed (version) {
    let staticDir = this.staticDir
    let maxHistoryNum = this.maxHistoryNum

    let history = []

    try {
      history = require(this.historyFile)
    } catch (e) {
      console.log(e)
    }

    // 加入最新的版本,老的的版本刪除
    history.push(version)

    // 如果歷史版本數超過限制,則刪除老的歷史版本
    let len = history.length
    if (len > maxHistoryNum) {
      let oldVersions = history.slice(0, len - maxHistoryNum)

      for (let i = 0; i < oldVersions.length; i++) {
        let ver = oldVersions[i]
        let reg = new RegExp(ver)
        this.rmFiles(staticDir, reg)
      }

      // 更新history檔案
      let newVersions = history.slice(len - maxHistoryNum)
      this.storeHistory(newVersions)
    } else {
      // 寫入history檔案
      this.storeHistory(history)
    }
  },

  // 入口
  run (version) {
    this.creataHistoryIfNotExist()
    this.cleanOldVersionFilesIfNeed(version)
  }
}

複製程式碼

run.sh

我們簡單的分為beta(測試環境)和pro(線上環境)

作用:一鍵打包原生程式碼,提交上傳至遠端程式碼倉庫

先看程式碼:

#!/bin/bash

# 初始化上下文。有引數的
initContext(){
	# dist目錄
	source_dir=dist

	# 獲取引數,beta環境和pro環境
	if [ $# -gt 0 ] && [ $1 = 'beta' ];then
		# beta環境遠端倉庫地址,
		git_url=git@beta/example.git

		# 本地根目錄存放打包程式碼
		dest=".deploy/beta"

		# npm 的指令碼名,對應package.json定義的script
		node_script=build_beta
	else
		# pro環境遠端倉庫地址
		git_url=git@pro/example.git

		# 本地根目錄
		dest=".deploy/pro"

		# npm 的指令碼名,對應package.json定義的script
		node_script=build_pro
	fi
}

# 初始化git目錄,pull最新程式碼
init(){
	echo +++init start;

	if [ ! -d $dest ]; then
	  	git clone $git_url $dest
	fi

	# 記錄現在的目錄位置,pwd獲取本地路徑
	cur=`pwd`

  	# 進入git目錄
  	cd $dest

  	# git checkout .
  	git add .
  	git stash

  	# reset為線上最新版本,要先pull一下再reset。
  	git pull origin master
  	git reset --hard origin/master

	# 然後再pull一下
	git pull origin master

	# 回到原來的目錄
	cd $cur
	echo ---init end;
}

# 重置dist目錄
resetDist(){
	echo +++resetDist start

	rsync -a --delete --exclude='.git' $dest/. ./dist

	echo ---resetDist end
}

# 構建
build(){
	echo +++build start
	npm run $node_script
	echo ---build end
}

# 檢查是否成功
checkBuild(){
	if [[ ! -f $source_dir/index.html || ! -d $source_dir/static ]]; then
		echo error
	else
		echo ok
	fi
}

# 複製程式碼到$dest目錄
cpCode(){
	echo +++cpCode start
	# 複製程式碼,所有檔案包含隱藏檔案
	rsync -r --delete --exclude='.git'  $source_dir/. $dest

	echo ---cpCode end
}

# 提交到遠端git倉庫
commit(){
	echo +++commit start
	# 記錄現在的目錄位置,最後要回來的
	cur=`pwd`

	# 進入git目錄
	cd $dest
	# 提交的字串
	commit_str="commited in `date '+%Y-%m-%d_%H:%M:%S'`"

	git add .
	git commit -am "${commit_str}"
	git push origin master

	# 回到原來的目錄
	cd $cur
	echo ---commit end
}

# 顯示幫助資訊
help(){
	# 微信h5版本
	echo ----微信h5版本--------
	echo ./run.sh build			"#"構建程式碼
	echo ./run.sh init			"#"初始化git倉庫
	echo ./run.sh commit		"#"提交到git
	echo ./run.sh	 			"#"執行全部任務
	echo ./run.sh hello			"#"hello
	echo ./run.sh test			"#"test

	echo ./run.sh beta			"#"一鍵構建和提交beta版本
	# app內嵌版本
	echo ----app內嵌版本--------
	echo ./run.sh app			"#"一鍵構建和提交app版本

	echo ----幫助資訊--------
	echo ./run.sh help			"#"幫助
}

# 測試用的
test(){
	echo "a test empty task"
}

# 入口
if [[ $# -lt 1  ||  $1 = 'app'  ||  $1 = 'beta' ]]; then
	# 無引數則打pro包,否則打相應型別的包
	if [ $# -lt 1 ];then
		type=pro
	else
		type=$1
	fi

	echo ===\>準備構建${type}版
	initContext $type && init && resetDist

	# 構建程式碼
	buildRes=$(build)

	# 檢查構建結果
	echo -e "$buildRes"

	if [[ $buildRes =~ "ERROR" ]]; then
		echo "$(tput setaf 1)xxx\>build error,task abort$(tput sgr0)"
	else
		# 程式碼構建成功才繼續。
		checkRes=$(checkBuild)

		if [ $checkRes == "ok" ];then
		 	cpCode && commit
			echo "$(tput setaf 2)===\>task complete$(tput sgr0)"
		else
			echo "$(tput setaf 1)xxx\>build error,task abort$(tput sgr0)"
		fi
	fi


elif [ $1 ]; then
	# 引數不是包型別的,當中函式處理
	echo ===\>準備執行${1}函式
	initContext beta

	func=$1
	$func
	echo ===\>task complete
fi

複製程式碼

上篇文章評論區有人問到 rsync ,文中用來同步本地硬碟中的不同目錄。可以看下這篇 教程

【前端打包部署】談一談我在SPA專案打包=>部署的處理

Linux 系統已經整合了 rsync 命令,windows 的使用者需要自行安裝(教程),不然會和這位小夥伴一樣遇到 command not found,本人windows已經3年沒碰了...

以上,是對上篇文章中對打包這塊內容的講解,接下來說下部署

部署工具我接觸過walle、Jenkins

walle的話就是提交對應版本進行釋出,持續整合部署方案的話就是Jenkins了。Jenkins文件

為什麼要做自動化部署?結合自身實際場景描述下:

  • 後端有微服務,前端自然不能落下。各個大模組(業務線)都是獨立的工程,比如商城、教育、社群等等。當開發迭代一個版本時,可能就會涉及到A、B、C、D等幾個專案,部署的痛苦就來了…我特麼的一個個打包,然後再一個個的部署...這個過程可能半小時就過去了…效率低下又low,實力不允許這麼做
  • 公司有區域網限制,如果在家手動部署,還需要連線VPN,內心是拒絕的
  • 我只想安心敲程式碼…別特麼的動不動讓我部署下,打斷老子的思路...

當然,使用Jenkins持續整合方案,該裝的環境還是一個都不能少的…這個也結合自身實際情況自行研究吧

本文也是描述下這個概念,各自實際情況各異,扔需自行研究

安裝什麼的自行文件吧(安裝,外掛,配置等),從新建任務開始

  1. 進入Jenkins系統,左側新建任務

    【前端打包部署】談一談我在SPA專案打包=>部署的處理

  2. 輸入名稱,選擇構建一個自由風格的軟體專案,確定

    【前端打包部署】談一談我在SPA專案打包=>部署的處理

  3. 填寫任務描述、原始碼倉庫地址,分支預設master

    【前端打包部署】談一談我在SPA專案打包=>部署的處理

  4. 配置觸發器,Gitlab提交程式碼觸發自動構建任務

    【前端打包部署】談一談我在SPA專案打包=>部署的處理

  5. 如果需要構建時執行指令碼,比如

    npm i
    npm run build
    複製程式碼

    【前端打包部署】談一談我在SPA專案打包=>部署的處理

  6. 儲存

很簡單的幾個配置步驟就能完成自動化部署任務,簡單高效。自行研究嘗試玩玩吧 下篇我再出個Jenkins+Github持續整合的詳細教程好了

  1. 關於Jenkins持續整合

Jenkins持續整合網上已經有了很成熟的教程,感覺我沒有必要再出篇類似的教程了,我fork了一份可做參考借鑑(可能Gitlab/Github版本不是最新的,部分內容略有差異,本人親測問題不大)

Jenkins+Gitlab: 教程

Jenkins+Github:手把手教程

結尾

通過這樣的方式打包部署個人認為是適合絕大多數中小公司的,哈哈。我本來考慮的是將css、js部署在cdn伺服器上,index.html單獨部署的,考慮到暫時還沒到需要這麼幹的程度,就不折騰伺服器了,思路大同小異。

在寫這篇文章的時候,正好看到大公司裡怎樣開發和部署前端程式碼?,大家可以瞭解下他們是怎麼設計的。我這中小公司,感覺文中的思路方案也差不多OK的啦

提前寫了,本來說是週末寫的,正好今天不忙,花2小時簡單寫下

相關文章