这种方式就是完全由我们前端工程师来实现的啦,通过写nodejs实现服务器操作,结合webpack打包完成自动部署
1、首先我们用nodejs来封装一个能操作远程服务器的工具库
文件命名为:serverLib.js
/**
* 该文件封装了对远程服务器的操作
*/
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
/**
* 上传打包后的文件到服务器上的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;
至于webpack插件如何编写,语法是什么?下面推荐几篇文章大家参考下。
怎样编写一个webpack插件
Webpack原理-编写Plugin
webpack官网-自定义插件
3、在webpack配置文件的plugins配置项中引入上面自定义的插件
这里我们以vue-cli脚手架来举例,其他项目的引入方式雷同。
这里需要根据我们设定的运行命令参数,和远程服务器的信息进行对应修改即可。
上面的截图进行代码更正,可根据实际测试结果进行修改。
// 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;
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下载,就可以使用啦。