hello,大家好,我是叶小秋,今天给大家分享一个前端部署工具。
不知道现在你是否还在使用手工部署前端项目,或者是你的团队中并没有一个简单、方便、快速的前端部署工具。
如果是这样的话,你可以尝试一下 ye-deploy
npm:www.npmjs.com/package/ye-…
从上面可以看到,仅输入一个 ye-deploy 命令,就完成了一个前端项目的部署,那他是如何实现的呢?
话不多说,我们来动手实践一下
首先的话,我们来搭建一个项目的基础配置
这里我使用 father-build 作为项目构建工具,配置也很简单,npm i father-build 下载,在项目根目录创建 .fatherrc 配置文件
在 package.json 中配置打包命令
当然你这里你也可以使用其他构建工具(gulp、rollup、webpack)
然后我们创建一下项目的基础目录
到这里项目的基础配置已经完成了,接下来就是代码的实现了
我们先来想一下,如果要实现一个前端部署工具,我们要分几步走?
- 首先我们需要读取用户配置
- 校验用户配置是否正确
- 进行项目打包
- 连接服务器,将打包产物上传到服务器
- 针对动态部署的,需要执行一些服务器的命令
- 部署完成
我们先来看,读取用户配置。在src/utils/get-user-config.ts 下创建一个获取用户配置的方法
import {isAbsolute,resolve} from 'path'
import {existsSync} from 'fs'
import getExistFile from './get-exist-file'
const CONFIG_FILES = [
'.ye-deploy.js'
]
interface YOpts {
cwd:string,
customPath?:string
}
export default function getUserConfig(opts:YOpts){
const {cwd,customPath} = opts
let finalPath = ''
if(customPath){
finalPath = isAbsolute(customPath)?customPath:resolve(cwd,customPath)
if(!existsSync(finalPath)){
throw new Error(`找不到配置文件:${customPath}`)
}
}
const configFile = finalPath || getExistFile({cwd,files:CONFIG_FILES})
if(!configFile){
throw new Error('找不到配置文件')
}
const userConfig = require(configFile)
return userConfig
}
读取用户配置后,我们需要校验一下用户的参数是否正确
在这之前我们先来看一下我们的一个配置参数
export interface YOpts {
host:string, // 服务器地址
port:string | number, // 端口
username:string, // 服务器用户名
password:string, // 服务器密码
privateKey:string, // 服务器密钥
passphrase:string, // 密钥密码
distPath:string, // 本地打包文件目录
webDir:string, // 服务器上部署的地址
script:string, // 项目打包命令
delDistFile:boolean, // 部署完成后是否删除打包文件
config?:string, // 配置文件地址
cwd:string,
useUploadValidate?:(itemPath:string)=>boolean, // 上传过程中 过滤某些文件
useUploadDone?:(command)=> Promise<void> | void, // 上传完成后允许用户自定义一些操作
}
如何去校验参数配置呢?
这里我使用schema-utils来进来校验,在src/utils下创建schema.ts 文件,用于描述校验参数
// https://www.npmjs.com/package/schema-utils
export default {
type:"object",
properties:{
host:{
type:"string"
},
port:{
anyOf:[
{
type:"string",
},
{
type:"number",
}
]
},
username:{
type:"string"
},
password:{
type:"string"
},
privateKey:{
type:"string"
},
passphrase:{
type:"string"
},
distPath:{
type:"string"
},
webDir:{
type:"string"
},
script:{
type:"string"
},
delDistFile:{
type:"boolean"
},
config:{
type:"string"
},
},
additionalProperties:false
}
具体使用看 schema-utils 的官方文档
校验完参数之后,我们需要去进行项目打包,可以看到我们的配置参数里面有一个script,这个是项目的打包命令,我们可以借助 child_process exec 来开启一个子线程,进行项目打包
// 项目打包
async runBuild() {
const spinner = ora('项目打包中...').start()
try {
await new Promise((resolve,reject)=>{
exec(this.options.script,(err)=>{
if(err){
reject(err)
}else {
resolve(true)
}
})
})
spinner.succeed('项目打包成功')
}catch(err){
spinner.fail('项目打包失败,请检查项目配置,重新部署!')
console.log(err);
process.exit()
}
}
项目打包完成之后,我们需要把打包后的产物上传到服务器,这个如何去做呢?
这里我们可以借助 node-ssh模块,通过他去把打包的产物上传到服务器
// 连接服务器
async connectSSH() {
const spinner = ora('正在连接服务器...').start()
try {
await this.ssh.connect(this.options)
spinner.succeed('服务器连接成功')
}catch(err){
spinner.fail('服务器连接失败!')
console.log(err);
process.exit()
}
}
// 上传到服务器
async uploadServer() {
await this.clearOldFile()
try {
await this.ssh.putDirectory(this.options.distPath,this.options.webDir,{
recursive:true,
concurrency:10,
validate:(itemPath)=>{
// 强制禁止 node_modules这些文件上传
const baseName = basename(itemPath)
const prohibitFiles = ['node_modules']
if(prohibitFiles.includes(baseName)){
return false
}
if(this.options.useUploadValidate){
return this.options.useUploadValidate(itemPath)
}
return true
},
tick:(localPath,remotePath,error)=>{
if(error){
console.log('上传失败',localPath)
this.failed.push({localPath,remotePath})
}else {
console.log('上传成功',localPath);
}
}
})
this.uploadServerAgain()
}catch(err){
ora().fail('部署失败!')
throw new Error(err)
}
}
上传完成后,对于我们静态的部署其实已经是完成了,但如果要进行动态的部署该如何处理呢?比方说去部署一个 docker 应用
这个时候我们可以借助 node-ssh 的execCommand去执行一些服务器的命令,定义一个 useUploadDone 方法,在上传完成之后触发,useUploadDone 传入 封装后的execCommand,这样子用户就可以在上传完成之后,去执行一些服务器上的命令,比方说,打包 docker镜像。
// command 命令操作
async runCommand({command,cwd,log=true}:{ command:string,cwd?:string,log?:boolean}){
return new Promise(async (resolve,reject)=>{
await this.ssh.execCommand(command, { cwd: cwd || this.options.webDir,
onStdout:(chunk)=>{
if(log){
console.log(chunk.toString('utf8'))
}
},
onStderr:(chunk)=>{
const errText = chunk.toString('utf8')
if(log){
console.log(errText)
}
reject(errText)
}
})
resolve(true)
})
}
// 上传完成
async uploadDone(){
// 上传完成后允许用户自定义一些操作
if(this.options.useUploadDone){
await this.options.useUploadDone(this.runCommand.bind(this))
}
// 断开连接
this.ssh.dispose()
if(this.options.delDistFile){
rimraf.sync(this.options.distPath)
}
}
之后就是输出部署完成信息
async doneMessage(startTime:Date,endTime:Date){
ora().succeed(`开始时间:${startTime.toLocaleString()}`)
ora().succeed(`结束时间:${endTime.toLocaleString()}`)
const time = Math.round((endTime.getTime() - startTime.getTime()) / 1e3)
ora().succeed(`总耗时:${time}s`)
ora().succeed('部署成功!')
console.log(`项目部署地址: ${this.options.webDir}`)
}
至此,我们的前端部署工具就完成了
接下来就可以发布npm仓库了
完成代码可以看:github.com/MrYeZiqing/…
你也可以直接npm上 下载 ye-deploy 使用
ye-deploy 定位是一个轻量级的前端部署工具,如果你或者你的团队需要一个规范标准的前端部署工具,我个人还是推荐使用像 Jenkins 这些。