傳統(tǒng)的手工部署需要經(jīng)歷:
1.打包,本地運(yùn)行npm run build打包生成dist文件夾。
2.ssh連接服務(wù)器,切換路徑到web對(duì)應(yīng)目錄下。
3.上傳代碼到web目錄,一般通過(guò)xshell或者xftp完成。
使用node-ssh實(shí)現(xiàn)
node-ssh是一個(gè)基于ssh2的輕量級(jí)npm包,主要用于ssh連接服務(wù)器、上傳文件、執(zhí)行命令。
const { ssh}= require('node-ssh')
const ssh = new node_ssh()
用到的api:
1.ssh.connect:連接服務(wù)器
ssh.connect({
? host: 'localhost',
? username: 'steel',
? privateKey: '/home/steel/.ssh/id_rsa'
})
2.ssh.putFile:上傳文件
ssh.putFile('/home/steel/Lab/localPath', '/home/steel/Lab/remotePath').then(function() {
? ? console.log("The File thing is done")
? }, function(error) {
? ? console.log("Something's wrong")
? ? console.log(error)
? })
3.ssh.execCommand:執(zhí)行遠(yuǎn)端服務(wù)器命令
ssh.execCommand('hh_client --json', { cwd:'/var/www' }).then(function(result) {
? ? console.log('STDOUT: ' + result.stdout)
? ? console.log('STDERR: ' + result.stderr)
? })
成果

部署過(guò)程
1. 打包代碼
2. 壓縮成zip
3. 連接服務(wù)器
4. 上傳zip
5. 解壓并刪除zip文件
6. 刪除本地zip包
const?path?=?require('path')
const?fs?=?require('fs')
const?shell?=?require('shelljs')?//?執(zhí)行shell命令
const?ora?=?require('ora')?//loading
const?zipFile?=?require('compressing')//?壓縮zip
const?{?NodeSSH?}?=?require('node-ssh')?//?ssh連接服務(wù)器
//?提示方法
const?{?successLog,?errorLog,?infoLog,?underlineLog?}?=?require('../utils/index')
//?變量
const?projectDir?=?process.cwd()
const?SSH?=?new?NodeSSH()
let?distDirPath,?distZipPath
//?第一步?打包代碼?build
const?execBuild?=?async?(script)?=>?{
??return?new?Promise(async?(resolve,?reject)?=>?{
????try?{
??????const?loading?=?ora('(1)?項(xiàng)目開(kāi)始打包')
??????loading.start()
??????underlineLog(script)
??????const?res?=?await?shell.exec(script)?//執(zhí)行打包指令
??????loading.stop()
??????if?(res.code?==?0)?{
????????successLog('打包成功')
????????resolve()
??????}?else?{
????????errorLog('項(xiàng)目打包失敗!')
????????process.exit(1)//退出流程
??????}
????}?catch?(err)?{
??????errorLog(err)
??????process.exit(1)//退出流程
????}
??})
}
//?第二步?開(kāi)始?jí)嚎szip
const?startZip?=?async?(distPath)?=>?{
??try?{
????infoLog('(2)?壓縮成zip')
????distDirPath?=?path.resolve(projectDir,?distPath)
????distZipPath?=?path.resolve(projectDir,?distPath?+?'.zip')
????await?zipFile.zip.compressDir(distDirPath,?distZipPath)
????successLog('壓縮成功')
??}?catch?(err)?{
????errorLog(err)
????process.exit(1)//退出流程
??}
}
//?第三步?連接服務(wù)器?ssh
const?connectSSH?=?async?(config)?=>?{
??infoLog('(3)?連接服務(wù)器')
??try?{
????await?SSH.connect({
??????host:?config.host,
??????port:?config.port,
??????username:?config.username,
??????password:?config.password,
??????privateKey:?config.privateKey,
??????passphrase:?config.passphrase
????})
????successLog('連接成功!')
??}?catch?(error)?{
????successLog('SSH連接失敗!')
????errorLog(err)
????process.exit(1)//退出流程
??}
}
//?第四步?打包代碼
const?uploadFile?=?async?(webDir,?distPath)?=>?{
??try?{
????infoLog(`(4)上傳zip至目錄${underlineLog(webDir)}`)
????await?SSH.putFile(distZipPath,?`${webDir}/${distPath}.zip`)
????successLog('上傳成功')
??}?catch?(err)?{
????errorLog(`zip包上傳失敗?${err}`)
????process.exit(1)
??}
}
//?第五步?開(kāi)始解壓
const?unzipFile?=?async?(webDir,?distPath)?=>?{
??try?{
????infoLog(`(5)?開(kāi)始解壓zip包`)
????await?runCommand(`cd?${webDir}`,?webDir)
????await?runCommand(`unzip?-o?${distPath}.zip?&&?rm?-f?${distPath}.zip`,?webDir)
????successLog('解壓成功')
????SSH.dispose()?//斷開(kāi)連接
??}?catch?(err)?{
????errorLog(`zip包解壓失敗?${err}`)
????process.exit(1)
??}
}
//?第六步?刪除本地dist.zip
const?deleteZip?=?async?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????infoLog(`(6)?開(kāi)始刪除本地zip包`)
????fs.unlink(distZipPath,?err?=>?{
??????if(err)?{
????????console.log(err)
????????errorLog('刪除zip失敗')
????????process.exit(1)
??????}
??????successLog('刪除成功')
??????resolve()
????})
??})
}
//?運(yùn)行命令
async?function?runCommand(command,?webDir)?{
??await?SSH.execCommand(command,?{?cwd:?webDir?})
}
const?deploy?=?async?(config)?=>?{
??let?{?projectName,?name,?script,?distPath,?webDir?}?=?config
??try{
????if?(!script)?script?=?'npm?run?build'
????await?execBuild(script)
????if?(!distPath)?distPath?=?'dist'
????await?startZip(distPath)
????await?connectSSH(config)
????await?uploadFile(webDir,?distPath)
????await?unzipFile(webDir,?distPath)
????await?deleteZip()
????successLog(`\n?恭喜您,${underlineLog(projectName)}項(xiàng)目${underlineLog(name)}部署成功了^_^\n`)
????process.exit(0)
??}?catch?(err)?{
????errorLog(`??部署失敗?${err}`)
????process.exit(1)
??}
}
module.exports?=?deploy
腳手架實(shí)踐
問(wèn)題:
上面的方案已經(jīng)可以完成一個(gè)項(xiàng)目的自動(dòng)化部署,但是再有一個(gè)新的項(xiàng)目要接入自動(dòng)化部署,是不是又得把整個(gè)文件拷貝過(guò)去,是不是非常麻煩?
因此可以將自動(dòng)化部署做成一個(gè)腳手架fe-deploy-cli,支持生成部署配置模板、腳本部署,只需一條命令即可部署到對(duì)應(yīng)環(huán)境中
1) 新建一個(gè)項(xiàng)目deploy-cli,并創(chuàng)建文件夾bin,文件加載有個(gè)deploy-cli.js

2) deploy-cli.js內(nèi)容
#!/usr/bin/env?node
//?件頭部必須有?#!/usr/bin/env?node?這么一行,意思是使用?node?進(jìn)行腳本的解釋程序,那下面的就可以使用?node?的語(yǔ)法了;
const?packageJson?=?require('../package.json');
const?fs?=?require('fs')
//?const?path?=?require('path')
const?{?checkDeployConfig,?underlineLog,?infoLog?}?=?require('../utils/index');
const?deploy?=?require('../lib/deploy');
const?deployInit?=?require('../lib/init');
const?deployPath?=?process.cwd()
const?deployConfigPath?=?`${deployPath}/deploy.config.js`;
const?version?=?packageJson.version
//?run
function??run(argv)?{
??if?(argv[0]?===?'-v'?||?argv[0]?===?'--version')?{
????console.log(`??version?is?${version}`);
??}?else?if?(argv[0]?===?'-h'?||?argv[0]?===?'--help')?{
????console.log('??usage:\n');
????console.log('??-v?--version?[show?version]\n');
????console.log('??init??生成配置文件deploy.config.js');
??}?else?if?(argv[0]?===?'init')?{
????deployInit(deployConfigPath)
??}?else?{
????//?判斷配置文件是否存在
????if?(fs.existsSync(deployConfigPath))?{
??????deployMian(argv);
????}?else?{
??????infoLog(`缺少部署文件${underlineLog(deploy.confog.js)},請(qǐng)運(yùn)行${underlineLog('deploy?init')}下載部署配置`);
????}
??}
}
async?function?deployMian(argv)?{
??const?deployConfigs?=?checkDeployConfig(deployConfigPath)
??deployConfigs.forEach(config?=>?{
????const?{?projectName,?name,?command?}?=?config
????if(command?===?argv[0])?{
??????console.log(`${underlineLog(projectName)}項(xiàng)目${underlineLog(name)}部署`)
??????deploy(config)
????}
??})
}
run(process.argv.slice(2))
3)package.json包中添加配置
"bin":?{
????"deploy":?"./bin/deploy-cli.js"
??},
4)將 deploy-cli 目錄打成一個(gè)全局包
cmd中執(zhí)行?
npm install . -g
這里可能會(huì)存在權(quán)限問(wèn)題,需要用管理員權(quán)限運(yùn)行cmd
5) 之后就可以可以執(zhí)行的指令了

最后
參考資料:
前端自動(dòng)化部署項(xiàng)目到服務(wù)器 -- 一行命令搞定,摒棄傳統(tǒng)的手工部署 npm run build