基于 Webpack 插件体系的 Mock 服务

前端 | 基于Webpack插件体系的Mock服务.png

背景

在软件研发流程中,对于前后端分离的架构体系而言,为了能够更快速、高效的实现功能的开发,研发团队通常来说会在产品原型阶段对前后端联调的数据接口进行结构设计及约定,进而可以分别同步进行对应功能的实现,提升研发速率。除了常见的研发流程提效之外,对于一些特殊的无法满足前后端联调场景下,也可在条件不允许的情况下进行 Mock 处理,等待条件满足后再进行真实的接口联调,如:网络不通、多地协同等。本文从前端研发过程中的 Mock 需求场景出发,结合前端业界通用的 Webpack 工程化的方案来提供 Mock 服务,以期能够给读者提供一些 Mock 工程化的实现方案借鉴。

架构

对于绝大多数业务开发而言,在目前成熟的生产实践中,前端开发团队仍然是以Webpack作为前端工程打包构建的主流工具。因而,对于前端 Mock 服务的工程化方案而言,前端工程架构基建团队提供适配Webpack体系的插件方案是一个不错的工程基建选择。虽然各方前端团队都以各大框架或者框架生态的脚手架方案进行构建,但大部分现有生态工程打包器底层仍然是以Webpack为主,如:@vue/cliumicreate-react-app等。

对于Webpack插件,其本质是一个类(ps:更准确的说是函数,JavaScript 中没有真正意义上的类),需要在类中定义apply方法,用于通过compiler对象挂载Webpack的事件钩子,该回调中可以获取到当前编译的compilation对象以及异步的callbackWebpack提供了丰富的插件入口,并通过tapable钩子事件系统,串联起整个Webpack钩子函数的生命周期流程。对于 Mock 服务而言,其实现的核心思路是在compiler的钩子watchRun进行 Mock 服务器的启动与监听,其 Mock 服务器可以是基于koa或者express的 node 服务器。

注意:在自定义 Webpack 插件时,Webpack4 和 Webpack5 中的守护进程模式、异步加载、定义全局变量、访问实例对象、事件监听器等方面均有所变化,需要开发者进行相应的兼容处理。

目录

├─ lib                                    // Mock服务的核心包
|   ├─ app.js
|   ├─ utils.js
├─ index.js                               // MockServiceWebpackPlugin插件导出

实践

对于项目工期较紧且某一时间段内无法进行前后端联调的场景下,业务开发下的实践可通过引入mock-service-webpack-plugin的插件进行前端 Mock。由于团队是基于 Vue 全家桶进行的业务开发,故而本实践案例以@vue/cli脚手架方案作为工程基建的底座来对业务中的某一个接口联调进行介绍。

在 Vue 脚手架配置中引入mock-service-webpack-plugin插件,对configureWebpack字段进行配置,代码如下:

const path = require("path");
const resolve = (dir) => path.join(__dirname, dir);

const MockServiceWebpackPlugin = require("mock-service-webpack-plugin");

const fs = require("fs");

const mockUrl = "http://localhost:9009"; // 不要与proxy代理服务端口重合

const filterPort = (url) => parseFloat(url.split(":").pop());

const plugins = [],
  proxy = {
    "/api": {
      target: "http://localhost:8198", // 不要与mock服务端口重合
      ws: true,
      pathRewrite: {
        "^/api": "",
      },
    },
  };

if (process.env.VUE_APP_MOCK) {
  plugins.push(
    new MockServiceWebpackPlugin({
      source: path.resolve(process.cwd(), "./src/mock"),
      port: filterPort(mockUrl),
    })
  );
  proxy["/mock"] = {
    target: mockUrl,
    ws: true,
    pathRewrite: {
      "^/mock": "",
    },
  };
}

module.exports = {
  // webpack config
  configureWebpack: {
    plugins,
  },
  devServer: {
    // https: true,
    // 端口配置
    historyApiFallback: true,
    port: 8888,
    // 反向代理配置
    proxy,
  },
};

在项目结构中新建一个目录用于放置相关的 Mock 数据接口,其需要和上述vue.config.js中的 Mock 设置目录相同,结构如下:

├─ src
|   ├─ mock                               // mock目录
|   |    ├─ screenConfig.js
|   ├─ api                                // 真实接口目录
|   |    ├─ BigScreenConfig.js
├─ .env.dev                               // 环境配置
├─ vue.config.js                          // vue cli打包相关配置

注意:通常来说,为了能使用到Webpack的热更新机制,可将 Mock 目录放置到src下的某个目录中

以其中一个大屏自配置的 Mock 接口为例,代码如下:

// src/mock/screenConfig.js

module.exports = {
  path: "/sm/smJsonPnSetting/find",
  methods: "POST",
  data: {
    code: "0",
    success: true,
    msg: "成功",
    data: {
      settingId: "settingId-16943333",
      pnId: "pnId-12345678",
      title: "数字大屏",
      createTime: "2023-09-15 22:27:05",
      updateTime: "2023-09-15 22:27:05",
      isActived: "1",
      content: {
        charts: [
          {
            timeSize: "m15",
            edit: false,
            tabs: [
              {
                lineOptions: {
                  chartType: "area",
                  list: ["上传速率(最小)", "下载速率(最小)"],
                },
                edit: false,
                staticTypes: [
                  {
                    kpiEnAlias: "userUprateAvr",
                    staticMethod: "Min",
                    neType: 5104,
                  },
                  {
                    kpiEnAlias: "userDownrateAvr",
                    staticMethod: "Min",
                    neType: 5104,
                  },
                ],
                title: "图表名称1",
              },
            ],
            id: 1,
            title: "图表01",
          },
        ],
        materials: {
          MaterialResource: {
            top: 900,
            left: 1300,
          },
          MaterialTimeDimension: {
            top: 58,
            left: 1200,
          },
          MaterialChangeView: {
            top: 100,
            left: 1900,
          },
          MaterialTraffic: {
            top: 900,
            left: 1600,
          },
          MaterialAlarm: {
            top: 900,
            left: 700,
          },
          MaterialSelectPn: {
            top: 65,
            left: 1400,
          },
          MaterialCard: {
            top: 900,
            left: 1000,
          },
        },
        logo: "cdn/screen/selfScreen3/default_logo.svg",
        conf: "大屏自配置",
        title: "数字大屏",
        layouts: [
          {
            draggable: false,
            y0: 1,
            x0: 1,
            y1: 2,
            x1: 2,
            id: "1",
            matchId: 1,
            content: "LayoutPerformanceIndex",
          },
          {
            draggable: false,
            y0: 1,
            x0: 2,
            y1: 4,
            x1: 6,
            id: "2",
            matchId: "",
          },
          {
            draggable: false,
            y0: 2,
            x0: 1,
            y1: 3,
            x1: 2,
            id: "4",
            matchId: "",
          },
          {
            draggable: false,
            y0: 3,
            x0: 1,
            y1: 4,
            x1: 2,
            id: "6",
            matchId: "",
          },
        ],
        bottomTabs: [
          {
            itemid: 1,
            src: "img/traffic.png",
            checked: false,
            id: "MaterialTraffic",
            title: "本月流量",
            value: 0,
          },
          {
            itemid: 2,
            src: "img/resource.png",
            checked: false,
            id: "MaterialResource",
            title: "资源概况",
            value: 514,
          },
          {
            itemid: 6,
            src: "img/card.png",
            checked: true,
            id: "MaterialCard",
            title: "号卡详情",
            value: 5,
          },
          {
            itemid: 5,
            src: "img/alarm.png",
            checked: false,
            id: "MaterialAlarm",
            title: "设备告警",
            value: 0,
          },
        ],
      },
      smVersion: "4",
    },
  },
};

对于是否开启 Mock 服务,可借助脚本通过.env变量进行控制,代码如下:

VUE_APP_MODE=dev
VUE_APP_MOCK=false

而在页面中对 Mock 与真实接口基于环境变量来进行切分,代码如下:

// api.js
import axios from "axios";

let BaseAxios = axios.create({
  timeout: 60000,
});

let APIGetFind = async function (params) {
  return await BaseAxios.post("api" + "/sm/smJsonPnSetting/find", params);
};

let MockGetFind = async function (params) {
  return await BaseAxios.post("mock" + "/sm/smJsonPnSetting/find", params);
};

// main.js
Vue.prototype.$mock = process.env.VUE_APP_MOCK;
<script>
import { APIGetFind, MockGetFind } from "@/api/BigScreenConfig";

export default {
  data() {
    return {
      settingId: "",
    };
  },
  methods: {
    async useEffect() {
      console.log("this.settingId", this.settingId);

      const res = this.$mock
        ? await MockGetFind({
            settingId: this.settingId,
          })
        : await APIGetFind({
            settingId: this.settingId,
          });
    
      console.log('res', res);
    },
  },
};
</script>

对于package.json中的脚本设置,代码如下:

{
  "scripts": {
    "serve": "vue-cli-service serve",
    "serve:dev": "cross-env VUE_APP_MODE=dev npm run serve",
    "serve:dev_mock": "cross-env VUE_APP_MODE=dev VUE_APP_MOCK=true npm run serve"
  }
}

源码

index.js

Webpack插件mock-service-webpack-plugin的入口,导出MockServiceWebpackPlugin类,使用进程间通信对 Mock 服务器和本地 Web 开发服务器进行响应,代码如下:

const path = require("path");
const fs = require("fs");
const { fork } = require("child_process");
class MockServiceWebpackPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    const { source, port = "9009" } = this.options;
    if (!source) {
      console.error(
        `Mock Directory did not exist. Please make sure your Mock Source Directory`
      );

      if (!fs.existsSync(source))
        console.error(
          `${source} did not exist. Please make sure your Source is Correct`
        );
    }

    let child;

    child = fork(path.resolve(__dirname, "./lib/app.js"), [], {
      encoding: "utf8",
      execArgv: process.execArgv,
    });

    child.send({ source, port });

    compiler.hooks.watchRun.tapAsync(
      "MockServiceWebpackPlugin",
      (compilation, callback) => {
        console.log("compiler watching...");

        fs.watch(source, { recursive: true }, (eventType, filename) => {
          console.log("eventType:", eventType, "filename:", filename);
          child.kill("SIGKILL");
          child = fork(path.resolve(__dirname, "./lib/app.js"), [], {
            encoding: "utf8",
            execArgv: process.execArgv,
          });
          child.send({ source, port });
        });
        callback();
      }
    );
  }
}

module.exports = MockServiceWebpackPlugin;

lib

app.js

app.js是基于express启动的 Mock 服务器,也是实现 Mock 服务路由的核心,代码如下:

const express = require("express");
const bodyParser = require("body-parser");
const fs = require("fs");
const path = require("path");
const app = express();
const router = express.Router();

const { createRoutes } = require("./utils");

app.use(bodyParser.json());
app.use(
  bodyParser.urlencoded({
    extended: false,
  })
);

process.on("message", ({ source, port }) => {
  console.log(`Options get From Parents`, source, port);

  createRoutes(router, source);

  app.use(router);

  app.listen(port, () => {
    console.log(`Mock Server Listen ${port} is Running`);
  });
});
utils.js

utils.js主要用于 HTTP 请求的相关处理,基于用户的 Mock 服务的 options 进行相应的路由动态生成,代码如下:

const fs = require("fs");
const path = require("path");

const METHODS_MAP = {
  POST: "post",
  GET: "get",
  DELETE: "delete",
  PUT: "put",
};

const createRoutes = (router, p) => {
  const stats = fs.statSync(p);

  if (stats.isDirectory()) {
    fs.readdirSync(p).forEach((item) => {
      createRoutes(router, `${p}/${item}`);
    });
  } else if (stats.isFile()) {
    const { path, methods, data } = require(`${p}`);
    if (!methods)
      console.error(
        `Methods did not exist. Please make sure your method is one of ${Object.keys(
          METHODS_MAP
        ).join(" ")}`
      );

    if (!data)
      console.error(
        `Data did not exists. Please make sure your data is correct`
      );

    router[METHODS_MAP[`${methods}`]](path, (req, res) => {
      res.json(data);
    });
  }
};

module.exports = {
  createRoutes,
};

总结

除了基于 Webpack 的前端工程化构建,对于RollupVite以及Gulp等其他前端打包构建工具也是现代化前端工程团队需要纳入考虑的工程基建范畴。对于完整的 Mock 服务,也可提供平台服务、IDE 插件等形式来帮助业务团队更好的提升效率及开发体验,工具从来都是服务的承载形式,重要的不是功能本身,而是体验带来的效率优化。

对于前端工程化而言,Mock 服务仅仅是开发流程中的一环,面对日益增加的成本及业务压力,如何有效的提升效率,实现工程效率才是前端工程师应该考虑的重中之重。不仅仅在于企业效益的间接贡献,更重要的是前端工程化实践也是平台工程乃至软件工程方向的重要组成部分,所有的工程能力的提升都是工程师应该一直致力于培养的重要能力,共勉!!!

ps: 最后,对于mock-service-webpack-plugin的实现感到不错的同学,欢迎点个小小的 star,您的 star,是我们最大的动力~~~

参考

  • Webpack 钩子函数
  • webpack 的自定义插件学习
  • 学 webpack 前先看看 tapable 吧
  • Webpack HMR 原理解析
  • Webpack 原理浅析
  • 前端该如何优雅地 Mock 数据
  • 详解如何优雅在 webpack 项目实现 mock 服务器
  • mock 服务搭建
  • Webpack 实战:本地 mock 开发模式实践
  • 基于 webpack-dev-server 搭建 mock 服务
  • 编写 webpack 插件-webpack-mock-service-plugin
  • 【webpack 插件篇】webpack-plugin-mock 一款 mockjs 的 webpack 插件,配置简单、易用
  • 从零开始搭建一个 mock 服务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/267767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LINUX系统安装和管理

目录 一.应用程序 对比应用程序与系统命令的关系 典型应用程序的目录结构 常见的软件包装类型 二.RPM软件包管理 1.RPM是什么&#xff1f; 2.RPM命令的格式 查看已安装的软件包格式 查看未安装的软件包 3.RPM安装包从哪里来&#xff1f; 4.挂载的定义 挂载命令moun…

C语言蛇形矩阵

文章目录 每日一言题目解题思路全部代码结语 每日一言 山有榛&#xff0c;隰有苓。云谁之思&#xff1f;西方美人。 --邶风简兮 题目 解题思路 话不多说&#xff0c;直接看图 通过观察图表&#xff0c;我想到了这种方法&#xff1a; 我将数字放置的位置分为两大类&#xff…

Python深度学习028:神经网络模型太多,傻傻分不清?

文章目录 深度学习网络模型常见CNN网络深度学习网络模型 在深度学习领域,有许多常见的网络模型,每种模型都有其特定的应用和优势。以下是一些广泛使用的深度学习模型: 卷积神经网络(CNN): 应用:主要用于图像处理,如图像分类、物体检测。 特点:利用卷积层来提取图像特…

【UML】第12篇 序列图(1/2)——基本概念和构成

目录 一、什么是序列图&#xff08;Sequence Diagram&#xff09; 1.1 定义 1.2 主要用途 1.3 序列图和BPMN的区别和联系 二、序列图的构成 2.1 对象 2.2 生命线 2.3 消息 2.4 激活 序列图&#xff0c;是我个人认为的用处最多的一种图。产品和研发的同学&#xff0c;都…

WorkPlus一站式协同解决方案,助力企业降本增效

在企业数字化转型的过程中&#xff0c;很多企业都会遇到一个共同问题&#xff1a;重复建设基础功能&#xff0c;耗费大量时间和资源。为解决这一难题&#xff0c;WorkPlus已经将一些通用、基础且有技术门槛的功能进行了集成与开发&#xff0c;如IM&#xff08;即时通讯&#xf…

截断整型提升算数转换

文章目录 &#x1f680;前言&#x1f680;截断&#x1f680;整型提升✈️整型提升是怎样的 &#x1f680;算术转换 &#x1f680;前言 大家好啊&#xff01;这里阿辉补一下前面操作符遗漏的地方——截断、整型提升和算数转换 看这一篇要先会前面阿辉讲的数据的存储否则可能看不…

“C语言“——scanf()、getchar() 、putchar()、之间的关系

scanf函数说明 scanf函数是对来自于标准输入流的输入数据作格式转换&#xff0c;并将转换结果保存至format后面的实参所指向的对象。 而const char*format 指向的字符串为格式控制字符串&#xff0c;它指定了可输入的字符串以及赋值时转换方法。 简单来说给一个打印格式(输入…

css radial-gradient 径向渐变基本语法与使用

在之前的文章《深入理解Css linear-gradient线性渐变》我们了解了CSS中的线性渐变&#xff0c;本文将介绍CSS中的另一种渐变———径向渐变&#xff08;Radial Gradient&#xff09;&#xff1a; CSS中的径向渐变&#xff08;Radial Gradient&#xff09;允许你创建从一个颜色…

基于Java SSM框架实现交通事故档案管理系统项目【项目源码+论文说明】

基于java的SSM框架实现交通事故档案管理系统演示 摘要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于交通事故档案管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0…

企业知识库:从信息管理到知识创新的转变

在当今这个信息爆炸的时代&#xff0c;企业知识库的建设已经成为了企业持续发展的重要基石。从传统的信息管理到现代的知识创新&#xff0c;企业知识库的角色和功能也在不断地演变和升级。本文将探讨企业知识库的发展历程&#xff0c;以及如何实现从信息管理到知识创新的转变。…

谷歌Gemini演示视频解析

在刚刚过去的前两天 谷歌发布了号称最强的多模态大模型Gemini 不仅提供了Ultra、Pro 和 Nano版本 而且在32项学术基准中 Gemini Ultra都达到了SOTA水平 甚至在MMLU测试中 Gemini Ultra 的得分率高达 90.0%&#xff0c; 是第一个超过人类专家的模型 应该说&#xff0c;G…

【vtkWidgetRepresentation】第十八期 vtkHoverWidget

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkHoverWidget,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. vtkHoverWidget vtkHoverWidget用于在呈现窗口中…

如何在飞书自建项目中接入ChatGPT打造智能问答助手并远程访问

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

10.3 uinput

uinput 简介 uinput 是一个内核驱动&#xff0c;应用程序通过它可以在内核中模拟一个输入设备&#xff0c;其设备文件名是 /dev/uinput 或 /dev/input/uinput。 uinput 使用 使用 uinput 时遵循以下步骤&#xff1a; 通过 open 打开 uinput 设备通过 ioctl 设置属性位图通过…

Windows基础知识:一站式整理指南

目录 学习目标&#xff1a; 学习内容&#xff1a; 学习产出&#xff1a; Windows操作系统的发展历史和版本特点 Windows界面和桌面元素的基本介绍 文件和文件夹管理&#xff1a;创建、复制、移动、删除等操作 系统设置和个性化&#xff1a;调整屏幕分辨率、更改桌面背景、设置…

Python之classmethod和staticmethod的区别

python中3种方式定义类方法&#xff0c;常规方式、classmethod修饰方式、staticmethod修饰方式。 class A(object):def foo(self, x):print(调用foo函数 (%s, %s)%(self, x))print(self:, self)classmethoddef class_foo(cls, x):print(调用class_foo函数 (%s, %s) % (cls, x))…

Azure Machine Learning - 如何使用 GPT-4 Turbo with Vision

介绍如何在Azure中使用GPT-4 Turbo with Vision 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c;项目管理…

Plantuml之对象图语法介绍(十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

MySQL undo日志精讲3-从回滚段中申请 Undo 页面链表

回滚段-Rollback Segment Header 页面 回滚段的概念 我们现在知道一个事务在执行过程中最多可以分配4个 Undo 页面链表&#xff0c;在同一时刻不同事务拥有的 Undo 页面链表是不一样的&#xff0c;所以在同一时刻系统里其实可以有许许多多个 Undo 页面链表存在。为了更好的管…

qt项目-《图像标注软件》源码阅读笔记-Shape类绘图及其子类

目录 1. Shape 概览 2. Shape 基类 2.1 字段 2.2 方法 2.3 嵌套类型 3. Shape2D 2d形状纯虚基类 3.1 字段 3.2 方法 4. Shape3D 3d形状纯虚基类 5. Shape2D子类 5.1 Rectangle 矩形类 1. Shape 概览 功能&#xff1a;Shape类及其子类负责形状的绘制及形状的存储。…