分享简易前端项目部署node脚本--deploy.js

1,043 阅读2分钟

嘿,前端界的小伙伴们,又到了把代码部署到服务器的那一天了!我知道你们的内心:有些人听到“CICD、Jenkins”就头疼,有些人还得把打包好的文件递给负责人,搞得大家都不太愉快。想要一个既简单又能一键部署的方法吗?来,用我们最熟悉的JavaScript语言,Node来搞定它!

首先,Node就是这么全能,有句话怎么说的来着?如果可以用JS做,最终都会用JS来做!我们用一些Node的神奇依赖库,只需要一个命令,就能自动完成部署,是不是很酷?

  • child_process:这货能让你用JS执行npm命令,打包你的项目。
  • archiver:帮你把文件压成tar包,方便传输。
  • ssh2:连接到远程服务器,执行Linux命令,把你的tar包传过去并解压到Nginx的指定位置。

接下来的部分,我们以Vue为例(React的小伙伴微调一下也能用),详细的部署脚本可以在这个代码仓库找到。

代码仓库在 (前端部署简易脚本deploy.js · Duck/EmpiricalCase - 码云 - 开源中国 (gitee.com)),可以下载下来看完整代码

一、环境变量

先搞定一些变量,方便我们后面的操作。

const cp = require('child_process')
const ssh = require('ssh2')
const archiver = require('archiver')
const fs = require('fs')
const path = require('path')

// 命令执行的模式
const mode = process.argv.slice(2)[0] || 'test'

// 打包后的名称
const distName = 'dist.tar.gz'

// 不同环境打包变量
const unzipDirMode = {
  test: {
    spawn: 'pnpm build:test',
    // 解压文件名
    unzipDir: 'back_end/',
    // 终端定位位置 cd
    servicePath: '/workspace/test',
    // 压缩包存放位置
    serviceFilePath: `/workspace/test/${distName}`
  },
  production: {
    spawn: 'pnpm build:prod',
    unzipDir: 'back_end/',
    servicePath: '/workspace/pro/',
    serviceFilePath: `/workspace/pro/${distName}`
  }
}

// 文件所在地
const distPath = path.resolve(__dirname, '../dist')
// 打包后位置
const zipPath = path.resolve(__dirname, `../${distName}`)
// 远程服务器存放位置
const { spawn, servicePath, serviceFilePath, unzipDir } = unzipDirMode[mode]
// 服务器连接信息
const connectInfo = {
  host: 'ip',
  port: '端口',
  username: '服务器用户名',
  password: '服务器密码'
}

二、打包

运行npm run build,用pnpm的话代码会更短些。

start()

// 入口函数
async function start() {
  try {
    // 1. 打包
    await build()
  } catch (error) {
    console.error('Error:', error.message)
  } finally {

  }
}

/**
 * 1. 本地构建项目
 */
function build() {
  return new Promise((resolve, reject) => {
    //对项目进行打包,然后生成压缩文件
    let pro = cp.spawn(spawn, {
      shell: true,
      stdio: 'inherit'
    })
    pro.on('exit', code => {
      if (code === 0) {
        console.log('---构建成功---')
        resolve()
      } else {
        reject(new Error('构建失败'))
      }
    })
  })
}

三、压缩

我们用tar格式压缩,这样服务器上解压就不需要额外依赖了。

/**
 * 2. 将打包后文件压缩tar
 * @returns
 */
function startZip() {
  return new Promise((resolve, reject) => {
    console.log('---开始打包tar---')
    //定义打包格式和相关配置
    const archive = archiver('tar', {
      gzip: true, // 如果需要压缩,可以使用 gzip
      gzipOptions: { level: 9 } // gzip 压缩级别
    }).on('error', err => reject(err))

    const output = fs.createWriteStream(zipPath)
    //监听流的打包
    output.on('close', () => {
      console.log('---目标打包完成---')
      resolve(true)
    })
    //开始压缩
    archive.pipe(output)
    // 文件夹压缩
    archive.directory(distPath, false)
    archive.finalize()
  })
}

四、文件传输

连接服务器,把tar包传上去。

//链接服务器
let conn = new ssh.Client()
/**
 * 3. 将zip文件传输至远程服务器
 */
function connect() {
  return new Promise((resolve, reject) => {
    conn
      .on('ready', () => {
        conn.sftp((err, sftp) => {
          if (err) {
            return reject(err)
          }
          sftp.fastPut(zipPath, serviceFilePath, {}, (err, result) => {
            if (err) {
              return reject(err)
            }
            //开始上传
            console.log('---压缩包上传成功---')
            resolve()
          })
        })
      })
      .on('error', err => reject(err))
      .connect(connectInfo)
  })
}

五、远程命令

在服务器上解压部署。

/**
 * 4. 解压部署操作
 * @param {*} conn
 */
async function shellCmd(conn) {
  return new Promise((resolve, reject) => {
    conn.shell((err, stream) => {
      if (err) {
        return reject(err)
      }
      //进入服务器暂存地址
      //解压上传的压缩包
      //移动解压后的文件到发布目录
      //删除压缩包
      //退出
      const commands = `
        cd ${servicePath} &&
        mkdir -p ${unzipDir} &&
        tar -xzf ${distName} -C ${unzipDir} &&
	      rm -rf ${distName} &&
        exit
				`
      console.log('--- 终端执行命令:', commands)
      stream
        .on('close', () => resolve())
        .on('data', data => console.log(data.toString()))
        .stderr.on('data', data => console.error('Error:', data.toString()))

      stream.end(commands)
    })
  })
}

六、清理现场

部署完成后,别忘了删除本地的tar包。

/**
 * 5. 删除本地的dist.zip
 */
function delZip() {
  fs.unlink(zipPath, function (err) {
    if (err) {
      console.error('删除文件失败:', err.message)
    }
    console.log('---文件:' + zipPath + '删除成功!---')
  })
}

结束

最后,把所有步骤包在一个start函数里,跑一下,整个部署过程就顺滑完成了。加一条npm命令,下次直接跑这个命令,你的代码就能飞上云端。是不是听起来就很有趣?赶快试试吧!

async function start() {
  try {
    // 1. 打包
    await build()
    // 2. 压缩zip
    await startZip()
    // 3. 将zip文件传输至远程服务器
    await connect()
    // 4. 部署解压
    await shellCmd(conn)

    console.log('---部署完成---')
  } catch (error) {
    console.error('Error:', error.message)
  } finally {
    // 5. 断开ssh,并删除本地压缩包
    conn.end()
    delZip()
  }
}
"scripts": {
    "dev": "vite --mode development",
    "build:test": "vite build --mode test",
    "build:prod": "vite build --mode production",
    
    "deploy:test": "node ./script/deploy test",
    "deploy:prod": "node ./script/deploy production",
  }

pnpm deploy:test pnpm deploy:prod