基于node.js实现前端项目自动化部署(懒人福音)

2019-10-09 admin

为什么写这个?

真相只有一个=>懒!!!

想直接使用的,github 传送门 后面的可以不用看了- -,记得留个star,笔心

目前主要在做iot项目,由于历史原因,平台还存在许多react/vue的纯H5子项目,这些项目又必须调用APP暴露的某些api,使得本地开发调试时不得不重复构建手动将build包部署到开发服务器上。几个项目,几轮转测试下来,颇有些心累。因此想着能不能写个工具,在执行yarn build构建打包后自动部署到服务器,这样就可以省去:打开ftp工具->寻找构建目录->寻找服务器上部署目录->备份->粘贴复制文件,这套繁琐手动流程,提高工(mo)作(yu)效率。 并且,有了这个后,jenkins自动构建时,项目配置的shell脚本也能少几行代码。

怎么实现?

思路分析

首先,我们先分析下要达到的结果:

  1. 执行yarn build

    个人喜欢用 yarn,用npm的同学可以自己替换。 假设项目都是基于create react app || Vue cli 脚手架搭建的,如果是自己自定义的脚手架的话,请继续往下看。本文以 create react app脚手架为例

  2. 工具自动触发

  3. 登陆远程服务器,建立连接,备份原有项目

  4. 从本地构建目录(本文为项目根目录下的build目录)copy所有文件并上传至远程服务器部署目录

  5. 部署完成,输出提示,关闭远程连接


其次,分析如何达成结果:

  1. 前端项目中写工具,首选node.js
  2. 工具需要在执行yarn build以后自动触发,这里我们需要用到npm scripts中的钩子postbuild ,它会在执行build后做一些收尾工作,正好可以用来触发部署操作。对npm scripts不太了解的同学,可以参阅《阮一峰 npm scripts 使用指南》
  3. 与服务器建立连接,并进行操作,需要建立ssh连接,推荐使用第三方库ssh2
  4. 备份时,文件名最好加上具体时间。例如:my_app.bak2019-10-8-14:36:36。为了方便,直接使用一个轻量级时间库moment

开干

  1. 安装 ssh2 、moment

    yarn add ssh2 moment

  2. 为了方便起见,在项目根目录下新建 deploy.js 。这个js文件就是本次编写的自动化部署工具

    deploy.js不一定要在项目根目录下,如果你对node寻找路径方式比较熟悉,可以放在自己指定的路径下面

  3. 打开 package.json文件,在 scripts中添加:

    "postbuild": "yarn run deploy",  
    "deploy": "node ./deploy.js",
    

    此时,如果往 deploy.js 中添加 console.log('---deploy test--'),控制台执行 yarn build,可以看到,在构建完成后,会继续执行yarn run deploy,最终控制台输出:

    ---deploy test--
    
  4. 根据ssh2文档,编写连接服务器、备份文件、上传文件的代码:

const path = require('path')
const moment = require('moment')
const util = require('util')
const events = require('events')
const Client = require('ssh2').Client
const fs = require('fs')
/*********************************************************************************/
/******************************请手动配置以下内容*********************************/
/*********************************************************************************/
/**
 * 远程服务器配置
 * @type {{password: string, port: number, host: string, username: string}}
 */
const server = {
  host: 'xxx.xxx.xxx.xxx',  //主机ip
  port: 22,                //SSH 连接端口
  username: 'xxxx',        //用户名
  password: 'xxxxxxx',     //用户登录密码
}
const baseDir = 'my_app'//项目目录
const basePath = '/home'//项目部署目录
const bakDirName = baseDir + '.bak' + moment(new Date()).format('YYYY-M-D-HH:mm:ss')//备份文件名
const buildPath = path.resolve('./build')//本地项目编译后的文件目录
/*********************************************************************************/
/**********************************配置结束***************************************/
/*********************************************************************************/

function doConnect(server, then) {
  const conn = new Client()
  conn.on('ready', function () {
    then && then(conn)
  }).on('error', function (err) {
    console.error('connect error!', err)
  }).on('close', function () {
    conn.end()
  }).connect(server)
}

function doShell(server, cmd, then) {
  doConnect(server, function (conn) {
    conn.shell(function (err, stream) {
      if (err) throw err
      else {
        let buf = ''
        stream.on('close', function () {
          conn.end()
          then && then(err, buf)
        }).on('data', function (data) {
          buf = buf + data
        }).stderr.on('data', function (data) {
          console.log('stderr: ' + data)
        })
        stream.end(cmd)
      }
    })
  })
}

function doGetFileAndDirList(localDir, dirs, files) {
  const dir = fs.readdirSync(localDir)
  for (let i = 0; i < dir.length; i++) {
    const p = path.join(localDir, dir[i])
    const stat = fs.statSync(p)
    if (stat.isDirectory()) {
      dirs.push(p)
      doGetFileAndDirList(p, dirs, files)
    }
    else {
      files.push(p)
    }
  }
}

function Control() {
  events.EventEmitter.call(this)
}

util.inherits(Control, events.EventEmitter)

const control = new Control()

control.on('doNext', function (todos, then) {
  if (todos.length > 0) {
    const func = todos.shift()
    func(function (err, result) {
      if (err) {
        then(err)
        throw err
      }
      else {
        control.emit('doNext', todos, then)
      }
    })
  }
  else {
    then(null)
  }
})

function doUploadFile(server, localPath, remotePath, then) {
  doConnect(server, function (conn) {
    conn.sftp(function (err, sftp) {
      if (err) {
        then(err)
      }
      else {
        sftp.fastPut(localPath, remotePath, function (err, result) {
          conn.end()
          then(err, result)
        })
      }
    })
  })
}

function doUploadDir(server, localDir, remoteDir, then) {
  let dirs = []
  let files = []
  doGetFileAndDirList(localDir, dirs, files)

  // 创建远程目录
  let todoDir = []
  dirs.forEach(function (dir) {
    todoDir.push(function (done) {
      const to = path.join(remoteDir, dir.slice(localDir.length + 1)).replace(/[\\]/g, '/')
      const cmd = 'mkdir -p ' + to + '\r\nexit\r\n'
      console.log(`cmd::${cmd}`)
      doShell(server, cmd, done)
    })// end of push
  })

  // 上传文件
  let todoFile = []
  files.forEach(function (file) {
    todoFile.push(function (done) {
      const to = path.join(remoteDir, file.slice(localDir.length + 1)).replace(/[\\]/g, '/')
      console.log('upload ' + to)
      doUploadFile(server, file, to, done)
    })
  })
  control.emit('doNext', todoDir, function (err) {
    if (err) {
      throw err
    }
    else {
      control.emit('doNext', todoFile, then)
    }
  })
}

console.log('--------deploy config--------------')
console.log(`服务器host:            ${server.host}`)
console.log(`项目文件夹:            ${baseDir}`)
console.log(`项目部署以及备份目录:  ${basePath}`)
console.log(`备份后的文件夹名:      ${bakDirName}`)
console.log('--------deploy start--------------')

doShell(server, `mv ${basePath}/${baseDir} ${basePath}/${bakDirName}\nexit\n`)

doUploadDir(server, buildPath, `${basePath}/${baseDir}`, () => console.log('--------deploy end--------------'))

运行结果示例

使用scripts触发时,运行yarn build以后,会自动触发生命周期钩子 postbuild,进行部署。此过程会先在本地构建打包项目至配置的buildPath目录,然后在远程服务器xxx.xxx.xxx.xxx/home中将my_app备份为my_app.bak2019-10-8-23:06:27,最后将本地buildPath目录文件全部上传到/home/my_app,完成部署。

$ yarn run deploy
$ node ./deploy.js
--------deploy config--------------
服务器host:           xxx.xxx.xxx.xxx
项目文件夹:            my_app
项目部署以及备份目录:    /home
备份后的文件夹名:       my_app.bak2019-10-8-23:06:27
--------deploy start--------------
cmd::mkdir -p /home/my_app/static
exit

cmd::mkdir -p /home/my_app/static/css
exit

cmd::mkdir -p /home/my_app/static/js
exit

cmd::mkdir -p /home/my_app/static/media
exit

upload /home/my_app/asset-manifest.json
upload /home/my_app/favicon.ico
upload /home/my_app/index.html
upload /home/my_app/logo192.png
upload /home/my_app/logo512.png
upload /home/my_app/manifest.json
upload /home/my_app/precache-manifest.20dc8cb74286fd491ca0a9fc9b07234a.js
upload /home/my_app/robots.txt
upload /home/my_app/service-worker.js
upload /home/my_app/static/css/main.2cce8147.chunk.css
upload /home/my_app/static/css/main.2cce8147.chunk.css.map
upload /home/my_app/static/js/2.222d1515.chunk.js
upload /home/my_app/static/js/2.222d1515.chunk.js.map
upload /home/my_app/static/js/main.0782b2ff.chunk.js
upload /home/my_app/static/js/main.0782b2ff.chunk.js.map
upload /home/my_app/static/js/runtime~main.077bb605.js
upload /home/my_app/static/js/runtime~main.077bb605.js.map
upload /home/my_app/static/media/logo.5d5d9eef.svg
--------deploy end--------------

Done in 16.58s.


结语

项目GitHub地址: https://github.com/hello-jun/deploy 此工具也可以单独使用,稍加改造后,也可以用来自动部署react native项目,有兴趣的可以自己尝试~ 欢迎star、留言、issue。 希望本文对各位有所帮助,祝工作生活愉快!

[转载]原文链接:https://segmentfault.com/a/1190000020619327

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-76928.html

文章标题:基于node.js实现前端项目自动化部署(懒人福音)

相关文章
three.js实现围绕某物体旋转
话不多说,请看代码: 可以拖动右上角观察变化 &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot; style=&quot;width: 100%; height:100%;&quot;&gt...
2017-02-17
前端交流QQ群
我们建立了一个前端交流QQ群供大家交流,有什么问题都可以在群里提问,欢迎你的加入,也希望我们大家能够在群里互帮互助,同时也能学到东西。 我们相信,前端有你更精彩! 为了让更多的小伙伴加入我们,欢迎大家转发扩散! 长按以上二维码加入我们 ...
2016-04-01
Node.js学习(1)----HTTP服务器与客户端
Node.js 标准库提供了 http 模块,其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端。http.Server 是一个基于事件的 HTTP 服务器,它的核心由 Node.js 下层 C++部分实现,而接口由 Jav...
2015-11-12
2014年最流行前端开发框架对比评测
如今,各种开发框架层出不穷,各有千秋。哪些是去年较受开发者关注的呢?前不久,云适配根据Github上的流行程度整理了2014年最受欢迎的6个前端开发框架,并进行对比说明,希望帮助有需要的朋友选择合适自己的前端框架。 1. Bootstrap...
2015-11-12
javaScript+turn.js实现图书翻页效果实例代码
为了实现图书翻页的效果我们在网上可以看到很多教程 在这里推荐turn.js 网上的turn.js 有api 不过是英文的  很多人看起来不方便 .关于代码也是奇形怪状在这里我将详细讲解如何使用turn.js实现翻页效果 ,本篇文章只是讲解 ...
2017-03-16
梳理前端开发使用eslint-prettier检查和格式化代码
问题痛点 在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。 对于代码版本管理系统(svn 和 git或者其他)...
2018-05-07
js实现手机拍照上传功能
在前段时间的项目开发中,用到了拍照上传的地方,后来发现了最为简单的一种方法,现总结如下: &lt;form id=&quot;form&quot; method=&quot;post&quot; action=&quot;http:&#x2...
2017-03-06
如何为高负载网络优化Nginx 和 Node.js?
译者:AlfredCheung 在搭建高吞吐量web应用这个议题上,NginX和Node.js可谓是天生一对。他们都是基于事件驱动模型而设计,可以轻易突破Apache等传统web服务器的C10K瓶颈。预设的配置已经可以获得很高的并发,不过,...
2015-11-12
Web前端开发与iOS终端开发的异同
毕业之前一直在做前端开发,毕业后就转成做iOS开发,这两者有很多挺有意思的对比,尝试写下我能想到的它们的一些相同点和不同点。 语言 前端和终端作为面向用户端的程序,有个共同特点:需要依赖用户机器的运行环境,所以开发语言基本上是没有选择的,...
2016-01-13
纯JS实现旋转图片3D展示效果
CSS: &lt;style type=&quot;text&#x2F;css&quot;&gt; #show{position:relative;margin:20px auto;width:800px;} .item{position:...
2017-03-22
回到顶部