实现前端项目自动化部署(webpack+nodejs)

2023-11-03

前言:
一般来说,我们前端是不需要关心部署的事情的,只需要把打包后的文件直接丢给后台去部署就可以了。但是呢,如果频繁修改一点东西就要叫后台进行部署,这样后台会很烦(毕竟人家还有其他工作嘛),我们也会很不好意思。
或许有些公司会给前端配置可视化操作服务器文件的软件(FTP客户端),这时我们就可以打包后自己到服务器上部署了,如果不同环境需要部署到不同服务器,此时我们又需要区分打包再手动上传到服务器上。
这时我们就会想,有没有直接一句命令就能自动化部署到不同服务器上,根本不需要打开软件来手动上传的???
答案:必须有啊,接下来看看如何进行操作,一劳永逸~~

一、webpack + Nodejs实现前端自动部署 ——完整版(法一)

这种方式就是完全由我们前端工程师来实现的啦,通过写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下载,就可以使用啦。

二、jenkins实现前端自动部署(法二)

这个方法一般来说都是后端来配置的,此处不展开,感兴趣的伙伴们,自行百度解决,下面推荐几篇文章。

使用jenkins进行前端项目自动部署
一套基础自动化部署搭建过程(vue实战防坑版)
学会使用 Jenkins 自动部署你的项目(实战)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实现前端项目自动化部署(webpack+nodejs) 的相关文章

随机推荐

  • codeforces Epic Game 题解

    Simon and Antisimon play a game Initially each player receives one fixed positive integer that doesn t change throughout
  • MOT学习 - 卡尔曼滤波

    参考资料 https www bzarg com p how a kalman filter works in pictures 协方差矩阵 https youzipi blog csdn net article details 48788
  • 关于Qt和C++中的反射机制实现与运用(2)

    看到一个很好的实例 这里就转载过来了 原文地址 C 反射机制 一 前言 Java有着一个非常突出的动态相关机制 Reflection 用在Java身上指的是我们可以于运行时加载 探知 使用编译期间完全未知的classes 换句话说 Java
  • Springboot+Vue前后端分离实现token登录验证和状态保存

    token可用于登录验证和权限管理 大致步骤分为 前端登录 post用户名和密码到后端 后端验证用户名和密码 若通过 生成一个token返回给前端 前端拿到token用vuex和localStorage管理 登录成功进入首页 之后前端每一次
  • 从放弃到再入门之拉格朗日对偶问题推导

    https blog csdn net qq 34564612 article details 79974635
  • 【第19篇】SSD论文翻译和代码汇总

    文章目录 摘要 1 简介 2 单次检测器 SSD 2 1 模型 2 2 训练 3 实验结果 3 1 PASCAL VOC2007 3 2 模型分析 3 3 PASCAL VOC2012 3 4 COCO 3 5 初步的ILSVRC结果 3
  • 2021-07-08

    阿里DevOps发布策略简介 前言 DevOps追求更短的迭代周期 更高频的发布 但发布的次数越多 引入故障的可能性就越大 更多的故障将会降低服务的可用性 进而影响到客户体验 所以 为了保证服务质量 守好发布这个最后一道关 阿里逐步发展出了
  • Vue3+Element-Plus中的Tree组件,多选时的赋值与取值

    el tree 是 element ui 提供的一个树形组件 可以在 Vue js 应用中使用 el tree 组件提供了 getCheckedNodes 方法 可以用来获取树形结构中被选中的节点
  • C/C++混合编程编译问题

    以下为本实验使用的编译器版本 系统环境为 目的 搞清以下几个问题 1 g 能否编译c文件 2 g 编出的s文件和gcc编出来的有何异同 3 cplusplus宏在何时被定义 4 c调用c 的注意事项 5 c 调用c的注意事项 6 针对上述问
  • js检测是否为数组的方法-JavaScript学习笔记

    1 方法一 instanceof 运算符 它可以用来检测是否为数组 代码 var arr var obj console log arr instanceof Array true console log obj instanceof Ar
  • Spring Boot 学习研究笔记(九) - Spring Data JPA常用注解

    Spring Data JPA 常用注解详细说明 1 Entity Entity 说明这个class 是实体类 并且使用默认的orm 规则 即 class 名对应数据库表中的表明 class 字段名即表中的字段名 如果想要改变这种默认的or
  • shell之多分支if语句

    1 语法格式 if 条件测试1 then 命令序列1 elif 条件测试2 then 命令序列2 elif 条件测试3 then 命令序列3 else 命令序列n fi 2 吃鸡段位排行 bin bash Author hejing Dat
  • 蓝牙硬件设备没有链接到计算机,电脑未发现蓝牙硬件设备怎么办

    有时我们想连接蓝牙硬件设备 却发现找不到了 没发现 怎么办 下面是学习啦小编为大家介绍电脑未发现蓝牙硬件设备的解决方法 欢迎大家阅读 电脑未发现蓝牙硬件设备的解决方法 首先我们在桌面找到 我的电脑 当然如果是WIN7 WIN8等系统以上的电
  • Postman 中文汉化界面

    参考 大佬的Gitee地址 gitee地址
  • GateWay 中StripPrefix的作用

    StripPrefix网关过滤器工厂采用一个参数StripPrefix StripPrefix参数表示在将请求发送到下游之前从请求中剥离的路径个数 spring cloud gateway routes id crm uri http cr
  • [1068]启动hdfs时,报错 Canary 测试无法在目录 /tmp/.cloudera_health_monitoring_canary_files 中创建文件

    启动hdfs时 报错 Canary 测试无法在目录 tmp cloudera health monitoring canary files 中创建文件 经过查看日志 发现 Name node is in safe mode 解决方法 sud
  • python练习10

    活动 10 while 循环的应用实例 一 程序示例 问 题 编程实现 猜数游戏 在给定某数后 让用户进行猜测并输入数 计算机给出相应提示 如偏大 偏小或正 确 若所猜测的数正确 则输出猜测次数 否则继续猜数 设计算法 初始 给定数 num
  • logback最详细配置讲解,及命名规范

    介绍 logback是一款日志框架 这个就不详细赘述了 命名规范 一般来说logback的配置文件可以命名为logback xml 项目启动时就可以读取到里面的配置 但是在spring boot项目中 这样会有一点问题 因为logback
  • 通过Docker方式安装海思Hi3516EV200交叉编译工具arm-himix100-linux

    前言 安装交叉编译环境 繁琐 耗时间 还得解决ubuntu版本不同 导致的各种错误 docker很好的解决了 不同版本 不同系统之间的差异性的问题 DOPI制作了docker方式的交叉编译环境 工作环境 vmare安装的ubuntu18 0
  • 实现前端项目自动化部署(webpack+nodejs)

    前言 一般来说 我们前端是不需要关心部署的事情的 只需要把打包后的文件直接丢给后台去部署就可以了 但是呢 如果频繁修改一点东西就要叫后台进行部署 这样后台会很烦 毕竟人家还有其他工作嘛 我们也会很不好意思 或许有些公司会给前端配置可视化操作