说起前端自动化部署,简单来说就是打包完成后自动发布服务器的过程,菜鸟的我简单搜集一下不同的部署方式供大家参考:

# 一、scp2

# 适用情况:webpack、vite项目,本地打包应用

1、本地打包生成dist文件目录

npm run build
1

2、执行上传脚本 参考

// deploy/index.js
const scpClient = require('scp2')
const chalk = require('chalk')
var Client = require('ssh2').Client
const ora = require('ora')
const path = require('path')
const server = require('./deploy.config.js')
const source_path = path.resolve() + '/dist'
const spinner = ora('正在发布到测试服务器...')

spinner.start()

const conn = new Client()

conn.on('ready', function () {
  conn.exec('rm -rf /www/wwwroot/***(项目放置静态地址)', function (err, stream) {
    if (err) throw err
    stream.on('close', function () {
      // 在执行shell命令后,把开始上传部署项目代码放到这里面
      scpClient.scp(source_path, {
        host: server.host,
        port: server.port,
        username: server.username,
        password: server.password,
        path: server.path, // 项目放置静态地址(服务器中地址)
      }, function (err) {
        spinner.stop()

        if (err) {
          console.log(chalk.red('发布失败.\n'))
        } else {
          console.log(chalk.green('Success! 成功发布到服务器! \n'))
        }
      })
      conn.end()
    }).on('data', function (data) {
      console.log('STDOUT: ' + data)
    }).stderr.on('data', function (data) {
      console.log('STDERR: ' + data)
    })
  })
}).connect({
  host: server.host,
  port: server.port,
  username: server.username,
  password: server.password,
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// package.json 
{ 
    "scripts": { 
        "build:develop": "vite build --mode develop && node ./deploy/index.js",
    } 
}
1
2
3
4
5
6

# 二、vite-plugin-lvdeploy

# 适用情况:vite项目, 本地打包应用

1.安装依赖

npm i vite-plugin-lvdeploy
npm i archiver -D
npm i ssh2 -D
1
2
3

2.vite.config.js中引入vitePluginLvdeploy 参考

// vite.config.js
import { defineConfig } from 'vite'
import vitePluginLvdeploy from './vite-plugin-lvdeploy'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
	vue(),
	vitePluginLvdeploy({
		"dev":{
			host:'xxx.xxx.xxx.xxx',//服务器IP
			port:22,//服务器端口
			username:'xxxxxx',//服务器ssh登录用户名
			password:'xxxxxx',//服务器ssh登录密码
			serverpath:'/home/www',//服务器web目录 切记不要加/ 当前目录不存在会创建目录并且当前目录所有文件会被清空重新部署前端项目
		},
		"test":{
		  host:'xxx.xxx.xxx.xxx',//服务器IP
		  port:22,//服务器端口
		  username:'xxxxxx',//服务器ssh登录用户名
		  password:'xxxxxx',//服务器ssh登录密码
		  serverpath:'/home/www',//服务器web目录 切记不要加/ 当前目录不存在会创建目录并且当前目录所有文件会被清空重新部署前端项目
		}
		//...其他自定义环境
	  }),
	]
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

3.执行脚本

let paperFileName = null;// 接收压缩文件名称参数
let DeployConfig = null;//配置项

export default function vitePluginLvdeploy(enforce) {
  return {
    name: 'vite-plugin-lvdeploy',
    apply: 'build',
    config(userConfig, env) {
      // 当前配置 userConfig 当前环境状态 env
      paperFileName = userConfig.build.outDir;
    },
    //  构建结束的最终回调,类型 async, parallel
    closeBundle() {
      DeployConfig = enforce;
      inquiry();
    },
  };
}

import fs from "fs"
import archiver from "archiver";
// 初始化SSH连接
const ssh2 = require('ssh2');
let conn = new ssh2.Client();


function inquiry() {
  console.log(`${__dirname}/${paperFileName}.zip`)
  // 创建文件输出流
  let output = fs.createWriteStream(`${__dirname}/${paperFileName}.zip`);
  let archive = archiver('zip', {
    zlib: { level: 9 } // 设置压缩级别
  })

  // 文件输出流结束
  output.on('close', function () {
    console.log("\x1B[32m%s\x1b[0m", `
   ------------------------------------------------
                  ${archive.pointer()} 字节,已完成压缩
   ------------------------------------------------
  `);
    deleteFolder(__dirname + "/" + paperFileName);// 删除文件夹
    detectionDeploy();// 检测部署
  })

  // 数据源是否耗尽
  output.on('end', function () {
    console.log('数据源已耗尽')
  })

  // 存档警告
  archive.on('warning', function (err) {
    if (err.code === 'ENOENT') {
      console.warn('stat故障和其他非阻塞错误')
    } else {
      throw err
    }
  })

  // 存档出错
  archive.on('error', function (err) {
    throw err
  })

  // 通过管道方法将输出流存档到文件
  archive.pipe(output);

  //打包build里面的所有文件和目录
  archive.directory(paperFileName + '/', false);

  //完成归档
  archive.finalize();
}

function detectionDeploy() {
  if(Object.keys(DeployConfig).length==0){
    console.log("\x1B[33m%s\x1b[0m", '已压缩本地zip包服务器未配置无法部署');
    return;
  }
  const readline = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  readline.question(`?请输入希望的部署环境  ${Object.keys(DeployConfig).join(' 或 ')} (0表示不希望压缩zip和部署) \n`, environmenttxt => {
    let environment = environmenttxt.trim();//处理首位空格
    if (environment in DeployConfig) {
      if (
        DeployConfig[environment].host &&
        DeployConfig[environment].port &&
        DeployConfig[environment].username &&
        DeployConfig[environment].password &&
        DeployConfig[environment].serverpath
      ) {
        // 参数有效
        connect(DeployConfig[environment]);
      } else {
        console.log("\x1B[33m%s\x1b[0m", 'Error:请检查传入参数是否规范')
      }
    } else if (environment == 0) {
      console.log("\x1B[33m%s\x1b[0m", 'Error:部署中断')
    } else {
      console.log("\x1B[33m%s\x1b[0m", 'Error:意料之外的环境')
    }
    readline.close();
  });
}

function connect(serverConfig) {
  console.log("\x1B[36m%s\x1b[0m", "-----------准备连接服务器-----------");
  conn.on('ready', () => {
    console.log("\x1B[36m%s\x1b[0m", "-----------连接服务器成功 准备上传文件-----------");
    // 保证服务器下当前目录存在
    useExec(`mkdir -p ${serverConfig.serverpath}/ && cd ${serverConfig.serverpath}/ && rm -rf *`).then(() => {
      conn.sftp((err, sftp) => {
        sftp.fastPut(`${__dirname}/${paperFileName}.zip`, `${serverConfig.serverpath}/${paperFileName}.zip`, {}, (err, result) => {
          console.log("\x1B[32m%s\x1b[0m", `文件上传完成 : ${__dirname}/${paperFileName}.zip 已部署服务器 ${serverConfig.serverpath}/${paperFileName}.zip`);
          // 解压文件停止连接数据库
          useExec(`
              cd ${serverConfig.serverpath}/ && \
              unzip ${paperFileName}.zip && \
              rm -rf ${paperFileName}.zip && \
              exit
            `).then(() => {
            conn.end();
            console.log("\x1B[35m%s\x1b[0m", `部署完成喽`);
          })

        })
      })
    }).catch(err => { })
  }).on("error", err => {
    console.log("\x1B[31m%s\x1b[0m", `连接服务器失败${err.toString()}`);
  }).connect({
    host: serverConfig.host,
    port: serverConfig.port,
    username: serverConfig.username,
    password: serverConfig.password
  })
}


function useExec(cmd) {
  // 所有错误命令直接关闭服务器连接
  return new Promise((resolve, reject) => {
    conn.exec(cmd, async (err, stream) => {
      if (err) { // 异常抛出
        console.log("\x1B[35m%s\x1b[0m", `异常抛出${err.toString()}`);
        conn.end();
        reject(err);
      }
      stream.on('close', async (code, signal) => { // 结束 code: 代码 signal: 信号
        if (code !== 0) {
          console.log("\x1B[35m%s\x1b[0m", `脚本异常退出code: ${code},异常信号signal:${signal}`)
          conn.end();
          reject("\x1B[35m%s\x1b[0m", `脚本异常退出code: ${code},异常信号signal:${signal}`);
        }
        // 程序执行成功
        // 自己的业务逻辑....记得conn.end();
        resolve();
      }).on('data', async data => { // 数据 程序执行中echo出的数据
        // console.log("\x1B[35m%s\x1b[0m",`echo出的数据${data.toString()}`);
      }).stderr.on('data', async data => { // 标准错误
        console.log("\x1B[35m%s\x1b[0m", `标准错误${data.toString()}`);
        conn.end();
        reject(data);
      });
    });
  })
}

// 删除文件
function deleteFolder(path) {
  let files = [];
  if (fs.existsSync(path)) {
    files = fs.readdirSync(path);
    files.forEach(function (file, index) {
      let dirPath = path + "/" + file;
      if (fs.statSync(dirPath).isDirectory()) {
        deleteFolder(dirPath);
      } else {
        fs.unlinkSync(dirPath);
      }
    });
    fs.rmdirSync(path);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

# 三、GitHub Pages

# 适用情况: 项目在git仓库,上传代码后自动打包部署

1、如果你要部署在 https://<USERNAME>.github.io/ 上; 参考

2、在你的项目中,创建一个 deploy.sh 脚本,包含以下内容(注意高亮的行,按需使用),运行脚本来部署站点:

#!/usr/bin/env sh

# 发生错误时终止
set -e

# 构建
npm run build

# 进入构建文件夹
cd dist

# 放置 .nojekyll 以绕过 Jekyll 的处理。
echo > .nojekyll

# 如果你要部署到自定义域名
# echo 'www.example.com' > CNAME

git init
git checkout -B main
git add -A
git commit -m 'deploy'

# 如果你要部署在 https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git main

# 如果你要部署在 https://<USERNAME>.github.io/<REPO>
# git push -f git@github.com:<USERNAME>/<REPO>.git main:gh-pages

cd -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 四、宝塔

# 适用情况:项目部署在服务器,需要上传整个项目

1、安装宝塔

  • Centos安装脚本 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec

  • Ubuntu/Deepin安装脚本 wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec

  • Debian安装脚本 wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && bash install.sh ed8484bec

2、基础环境

aa9a5f4180e42a06ac4008ac9ba51ed.png

3、Node环境

image.png

4、上传Node项目 文件上传后会根据package.json中安装相关依赖,执行dev、build等命令

image.png