前言: 一般来说,我们前端是不需要关心部署的事情的,只需要把打包后的文件直接丢给后台去部署就可以了。但是呢,如果频繁修改一点东西就要叫后台进行部署,这样后台会很烦(毕竟人家还有其他工作嘛),我们也会很不好意思。 或许有些公司会给前端配置可视化操作服务器文件的软件(FTP客户端),这时我们就可以打包后自己到服务器上部署了,如果不同环境需要部署到不同服务器,此时我们又需要区分打包再手动上传到服务器上。 这时我们就会想,有没有直接一句命令就能自动化部署到不同服务器上,根本不需要打开软件来手动上传的??? 答案:必须有啊,接下来看看如何进行操作,一劳永逸~~
一、webpack + Nodejs实现前端自动部署 ——完整版(法一)
这种方式就是完全由我们前端工程师来实现的啦,通过写nodejs实现服务器操作,结合webpack打包完成自动部署。
1、首先我们用nodejs来封装一个能操作远程服务器的工具库 文件命名为:serverLib.js
const util = require('util');
const events = require('events');
const { Client } = require('ssh2');
const fs = require('fs');
const path = require('path');
function Connect(server, then) {
const conn = new Client();
conn.on('ready', () => {
then(conn);
}).on('error', (err) => {
}).on('end', () => {
}).on('close', (had_error) => {
})
.connect(server);
}
function Shell(server, cmd, then) {
Connect(server, (conn) => {
conn.shell((err, stream) => {
if (err) {
then(err);
} else {
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);
}
});
});
}
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);
});
}
});
});
}
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);
}
});
}
});
});
}
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);
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);
}
});
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}`);
});
});
control.emit('donext', todoFiles, then);
}
});
}
});
}
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);
}
}
}
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);
});
});
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
const { spawn } = require('child_process');
const uploadDir = require('./serverLib').UploadDir;
class UploadFileWebPackPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.done.tap('upload-file-plugin', async (status) => {
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脚手架来举例,其他项目的引入方式雷同。 这里需要根据我们设定的运行命令参数,和远程服务器的信息进行对应修改即可。 上面的截图进行代码更正,可根据实际测试结果进行修改。
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',
port: 55314,
username: 'xxxxx',
password: 'xxxxxxx',
};
} else if (deployArgv === '-114') {
isNeedUpload = true;
uploadServerConfig = {
host: 'xxx.xxx.xxx.114',
port: 55314,
username: 'xxxxx',
password: 'xxxxxxxxx',
};
}
const webpackConfig = {
configureWebpack: {
plugins: [
],
},
devServer: {
overlay: {
warining: true,
errors: true,
},
},
lintOnSave: false,
publicPath: process.env.NODE_ENV === 'production'
? '/winne-test/'
: '/',
};
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下载,就可以使用啦。
二、jenkins实现前端自动部署(法二)
这个方法一般来说都是后端来配置的,此处不展开,感兴趣的伙伴们,自行百度解决,下面推荐几篇文章。
使用jenkins进行前端项目自动部署 一套基础自动化部署搭建过程(vue实战防坑版) 学会使用 Jenkins 自动部署你的项目(实战)
|