吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1209|回复: 4
收起左侧

[学习记录] vue+node+webpack前端自动化部署

[复制链接]
EdwardQi 发表于 2022-6-9 09:58
这种方式就是完全由我们前端工程师来实现的啦,通过写nodejs实现服务器操作结合webpack打包完成自动部署
1、首先我们用nodejs来封装一个能操作远程服务器的工具库
文件命名为:serverLib.js
[JavaScript] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/**
* 该文件封装了对远程服务器的操作
*/
const util = require('util');
const events = require('events');
const { Client } = require('ssh2'); // ssh2模块需要使用npm安装
const fs = require('fs');
const path = require('path');
 
/**
* 描述:连接远程电脑
* 参数:server 远程电脑凭证;
then 回调函数
* 回调:then(conn) 连接远程的client对象
*/
function Connect(server, then) {
const conn = new Client();
conn.on('ready', () => {
then(conn);
}).on('error', (err) => {
// console.log("connect error!");
}).on('end', () => {
// console.log("connect end!");
}).on('close', (had_error) => {
// console.log("connect close");
})
.connect(server);
}
 
/**
* 描述:运行shell命令
* 参数:server 远程电脑凭证;
cmd 执行的命令;
then 回调函数
* 回调:then(err, data) : data 运行命令之后的返回数据信息
*/
function Shell(server, cmd, then) {
Connect(server, (conn) => {
conn.shell((err, stream) => {
if (err) {
then(err);
} else { // end of if
let buf = '';
stream.on('close', () => {
conn.end();
then(err, buf);
}).on('data', (data) => {
buf += data;
}).stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
stream.end(cmd);
}
});
});
}
 
/**
* 描述:上传文件
* 参数:server 远程电脑凭证;
localPath 本地路径;
remotePath 远程路径;
then 回调函数
* 回调:then(err, result)
*/
function UploadFile(server, localPath, remotePath, then) {
Connect(server, (conn) => {
conn.sftp((err, sftp) => {
if (err) {
then(err);
} else {
sftp.fastPut(localPath, remotePath, (err, result) => {
conn.end();
then(err, result);
});
}
});
});
}
 
/**
* 描述:下载文件
* 参数:server 远程电脑凭证;
remotePath 远程路径;
localPath 本地路径;
then 回调函数
* 回调:then(err, result)
*/
function DownloadFile(server, remotePath, localPath, then) {
Connect(server, (conn) => {
conn.sftp((err, sftp) => {
if (err) {
then(err);
} else {
sftp.fastGet(remotePath, localPath, (err, result) => {
if (err) {
then(err);
} else {
conn.end();
then(err, result);
}
});
}
});
});
}
 
/**
* 描述:获取远程文件路径下文件列表信息
* 参数:server 远程电脑凭证;
* remotePath 远程路径;
* isFile 是否是获取文件,true获取文件信息,false获取目录信息;
* then 回调函数
* 回调:then(err, dirs) : dir, 获取的列表信息
*/
function GetFileOrDirList(server, remotePath, isFile, then) {
const cmd = `find ${remotePath} -type ${isFile == true ? 'f' : 'd'}\r\nexit\r\n`;
Shell(server, cmd, (err, data) => {
let arr = [];
const remoteFile = [];
arr = data.split('\r\n');
arr.forEach((dir) => {
if (dir.indexOf(remotePath) == 0) {
remoteFile.push(dir);
}
});
then(err, remoteFile);
});
}
 
/**
* 描述:控制上传或者下载一个一个的执行
*/
function Control() {
events.EventEmitter.call(this);
}
util.inherits(Control, events.EventEmitter); // 使这个类继承EventEmitter
 
const control = new Control();
 
control.on('donext', (todos, then) => {
if (todos.length > 0) {
const func = todos.shift();
func((err, result) => {
if (err) {
throw err;
then(err);
} else {
control.emit('donext', todos, then);
}
});
} else {
then(null);
}
});
 
/**
* 描述:下载目录到本地
* 参数:server 远程电脑凭证;
* remotePath 远程路径;
* localDir 本地路径,
* then 回调函数
* 回调:then(err)
*/
function DownloadDir(server, remoteDir, localDir, then) {
GetFileOrDirList(server, remoteDir, false, (err, dirs) => {
if (err) {
throw err;
} else {
GetFileOrDirList(server, remoteDir, true, (err, files) => {
if (err) {
throw err;
} else {
dirs.shift();
dirs.forEach((dir) => {
const tmpDir = path.join(localDir, dir.slice(remoteDir.length + 1)).replace(/[//]\g/, '\\');
// 创建目录
fs.mkdirSync(tmpDir);
});
const todoFiles = [];
files.forEach((file) => {
const tmpPath = path.join(localDir, file.slice(remoteDir.length + 1)).replace(/[//]\g/, '\\');
todoFiles.push((done) => {
DownloadFile(server, file, tmpPath, done);
console.log(`downloading the ${file}`);
});// end of todoFiles.push
});
control.emit('donext', todoFiles, then);
}
});
}
});
}
 
/**
* 描述:获取windows上的文件目录以及文件列表信息
* 参数:destDir 本地路径,
* dirs 目录列表
* files 文件列表
*/
function GetFileAndDirList(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);
GetFileAndDirList(p, dirs, files);
} else {
files.push(p);
}
}
}
 
/**
* 描述:上传文件夹到远程目录
* 参数:server 远程电脑凭证;
* localDir 本地路径,
* remoteDir 远程路径;
* then 回调函数
* 回调:then(err)
*/
function UploadDir(server, localDir, remoteDir, then) {
const dirs = [];
const files = [];
GetFileAndDirList(localDir, dirs, files);
 
// 删除远程指定目录下的所有文件
const deleteDir = [(done) => {
const cmd = `rm -rf ${remoteDir}* \r\nexit\r\n`;
console.log(cmd);
Shell(server, cmd, done);
}];
 
// 创建远程目录
const todoDir = [];
dirs.forEach((dir) => {
todoDir.push((done) => {
const to = path.join(remoteDir, dir.slice(localDir.length)).replace(/[\\]/g, '/');
const cmd = `mkdir -p ${to}\r\nexit\r\n`;
console.log(cmd);
Shell(server, cmd, done);
});// end of push
});
 
// 上传文件
const todoFile = [];
files.forEach((file) => {
todoFile.push((done) => {
const to = path.join(remoteDir, file.slice(localDir.length)).replace(/[\\]/g, '/');
console.log(`upload ${to}`);
UploadFile(server, file, to, done);
});
});
 
control.emit('donext', deleteDir, (err) => {
if (err) {
throw err;
} else {
control.emit('donext', todoDir, (err) => {
if (err) {
throw err;
} else {
control.emit('donext', todoFile, then);
}
});
}
});
}
 
exports.Shell = Shell;
exports.UploadFile = UploadFile;
exports.DownloadFile = DownloadFile;
exports.GetFileOrDirList = GetFileOrDirList;
exports.DownloadDir = DownloadDir;
exports.UploadDir = UploadDir;


2、封装一个webpack插件
该插件实现webpack打包后将打包目录文件上传到服务器上。
文件命名为:uploadFileWebPackPlugin.js
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
/**
* 上传打包后的文件到服务器上的webpack插件
*/
const { spawn } = require('child_process');
const uploadDir = require('./serverLib').UploadDir;
 
class UploadFileWebPackPlugin {
constructor(options) {
this.options = options;
}
 
apply(compiler) {
// 定义在打包后执行这个webpack插件
// 需要用到对应的钩子函数
compiler.hooks.done.tap('upload-file-plugin', async (status) => {
// console.log('this.options: ', this.options);
this.deploy();
});
}
 
deploy() {
const chmod = spawn('chmod', ['-R', '777', this.options.buildFolder]);
chmod.on('exit', (code, signal) => {
console.log('\n服务器授权成功,开始自动化部署~~\n');
uploadDir(
this.options.serverConfig,
this.options.buildFolder,
this.options.servePath,
(err) => {
if (err) throw err;
console.log('\n自动化部署成功~\n');
},
);
});
}
}
module.exports = UploadFileWebPackPlugin;

3、在webpack配置文件的plugins配置项中引入上面自定义的插件
这里我们以vue-cli脚手架来举例,其他项目的引入方式雷同。
这里需要根据我们设定的运行命令参数,和远程服务器的信息进行对应修改即可。
[url=]image.png[/url]
[url=]上面的截图进行代码更正,可根据实际测试结果进行修改。[/url]
[url=]
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
// vue.config.js文件
/**
* 自动化部署代码引入 start
*/
// 引入自定义的上传文件webpack插件
const UploadFileWebPackPlugin = require('./webpack-plugin/uploadFileWebPackPlugin');
 
// 获取运行命令的参数
const deployArgv = process.argv.pop();
// 通过参数判断是否要执行上传插件
let isNeedUpload = false;
let uploadServerConfig = {};
// 根据参数设置不同服务器信息
if (deployArgv === '-95') {
isNeedUpload = true;
uploadServerConfig = {
host: 'xxx.xxx.xxx.95', // 服务器ip地址
port: 55314, // 服务器端口号
username: 'xxxxx', // 登录服务器的用户名
password: 'xxxxxxx', // 登录服务器的密码
};
} else if (deployArgv === '-114') {
isNeedUpload = true;
uploadServerConfig = {
host: 'xxx.xxx.xxx.114',
port: 55314,
username: 'xxxxx',
password: 'xxxxxxxxx',
};
}
/**
* 自动化部署代码引入 end
*/
const webpackConfig = {
configureWebpack: {
// plugin配置项
plugins: [
// // 在npm run build的时候才执行这个插件(自动化部署插件)
// ---- 尝试过这个方法使用插件,但是在不加参数的时候就会报错说webpack插件定义不正确的情况
// (process.env.NODE_ENV === 'production' && isNeedUpload)
// && new UploadFileWebPackPlugin({
// // 服务器的配置信息
// serverConfig: uploadServerConfig,
// // 本地打包输出的文件夹路径
// buildFolder: 'dist/',
// // 上传到服务器上的路径
// servePath: '/home/sendi/fe/winne-test/',
// }),
],
},
// 暂时关闭eslint校验, 方便测试
devServer: {
overlay: {
warining: true,
errors: true,
},
},
lintOnSave: false,
// 配置部署应用包时的基本 URL
publicPath: process.env.NODE_ENV === 'production'
? '/winne-test/'
: '/',
};
 
// webpack插件根据环境判断来使用改为这个方式(在加参数或者不加参数的情况都能正确运行)
if ((process.env.NODE_ENV === 'production' && isNeedUpload)) {
webpackConfig.configureWebpack.plugins.push(
new UploadFileWebPackPlugin({
// 服务器的配置信息
serverConfig: uploadServerConfig,
// 本地打包输出的文件夹路径
buildFolder: 'dist/',
// 上传到服务器上的路径
servePath: '/home/sendi/fe/winne-test/',
}),
);
}
 
module.exports = webpackConfig;
[/url]4、运行打包命令,实现前端项目的自动化部署
1)、没用到自动化部署时,我们这样打包项目
使用npm打包:npm run build
使用yarn打包:yarn build
2)、需要自动化部署时,我们这样打包项目(打包命令后面加参数,识别不同参数部署到不同服务器)
使用npm打包:npm run build – -95 或者 npm run build – -114 (注意在参数前有两个中划线)
使用yarn打包:yarn build -95 或者 yarn build -114最后
如果要更方便的使用,可以把自动部署功能代码直接封装成npm包发布到npm上,这样以后用到时可以直接使用npm下载,就可以使用啦。

免费评分

参与人数 2吾爱币 +8 热心值 +2 收起 理由
E式丶男孩 + 1 + 1 用心讨论,共获提升!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

  • · Aarow|主题: 970, 订阅: 305

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| EdwardQi 发表于 2022-6-9 10:10
大家有什么指点的就多多指点指点~
E式丶男孩 发表于 2022-6-9 16:19
在配置自定义插件的时候,plugin数组里面的内容注释了,在用的时候是不是得取消注释,并且配置自己的内容
 楼主| EdwardQi 发表于 2022-6-10 09:29
E式丶男孩 发表于 2022-6-9 16:19
在配置自定义插件的时候,plugin数组里面的内容注释了,在用的时候是不是得取消注释,并且配置自己的内容

不用,我写了两种实现方式。注释起来的如果不带命令进行打包的话,会报错。
首先先把plugin的设置为[] 空数组,后面要如果没有执行指定命令的话会走默认build,指定了的话会push我们写好的webpack插件
E式丶男孩 发表于 2022-6-10 17:24
EdwardQi 发表于 2022-6-10 09:29
不用,我写了两种实现方式。注释起来的如果不带命令进行打包的话,会报错。
首先先把plugin的设置为[]  ...

懂了谢谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-2 01:20

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表