使用Plop.js高效生成模板文件

前情

开发是个创造型的职业,也是枯燥的职业,因为开发绝大多数都是每天在业务的代码中无法自拨,说到开发工作,就永远都逃不开新建文件的步骤,特别现在组件化开发胜行,每天都是在新建新建组件的道路上一去不返,我们做的最多就是直接拷贝一个旧代码组件,重命下名再删减删减完成新组件的创建

思考

对于这种组件,整体基础结构是一样的,我们可不可以有更好的方式一键生成了,就避免了反反复的拷贝删减动作,有一天我在逛博客论坛的时候我发现了Plop.js,发现它正是解决这种场景的

Plop.js介绍

官网: Consistency Made Simple : PLOP

**官网的介绍:**Plop is a little tool that saves you time and helps your team build new files with consistency,翻译就是:Plop是一个小工具,可以节省您的时间,并帮助您的团队构建一致的新文件

**工作主流程:**我用过后对它的工作整体流程理解是这样的,通过plopfile.js定义一个一个生成器,每一个生成器根据你传的配置再调用指定目录(一般是plop-template目录)下的hbs模板文件,再通过模板渲染生成最终符合特定结构的文件

需求描述

最近我手上的项目主要是uni-app项目,我们就以uni-app项目做实验,我每天可能都会重复的工作有新建组件、页面、API接口文件,如下图所示

实战

生成组件

定义组件生成器:

javascript 复制代码
module.exports = function (plop) {
  // 导入所需模块
  const { exec } = require('child_process');
  const path = require('path');
  const fs = require('fs');

  // 定义打开文件的函数
  function openFile(filePath) {
    const fullPath = path.resolve(process.cwd(), filePath);
    if (fs.existsSync(fullPath)) {
      console.log(`\n正在打开文件: ${fullPath}`);

      // 根据操作系统选择打开方式
      const isWin = process.platform === 'win32';

      // Windows - 尝试使用cursor打开,如果你是用的vs code,请把cursor换成code即可
      exec(`cursor "${fullPath}"`, (error) => {
        if (error) {
          // 如果VS Code不可用,尝试使用默认程序打开
          console.log(`打开文件失败: ${error},文件路径: ${fullPath}`);
        }
      });
    }
    return '文件已创建';
  }

  // 新建组件 组件生成器
  plop.setGenerator("component", {
    description: "新建组件",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "要新建的组件名:",
      },
    ],
    actions: [
      {
        type: "add",
        path: "components/{{pascalCase name}}/{{pascalCase name}}.vue",
        templateFile: "plop-templates/Component.vue.hbs",
      },
      // 实现生成文件后主动打开的功能
      function(answers) {
        const filePath = `components/${plop.getHelper('pascalCase')(answers.name)}/${plop.getHelper('pascalCase')(answers.name)}.vue`;
        return openFile(filePath);
      }
    ],
  });
};

这里多做了一个功能,当生成完页面后,使用cursor编辑器主动打开当前生成的文件,其中openFile就是打开当前生成的文件,这个可有可无,加了体验感觉会好一些

模板文件plop-templates/Component.vue.hbs:

xml 复制代码
<template>
    <div @click="handleClick">{{ msg }}-{{ msgIn }}</div>
</template>

<script setup>
    import { ref, onMounted } from 'vue';

    defineOptions({
        name: "{{camelCase name}}"
    })

    const props = defineProps({
        msg: {
            type: String,
            default: 'Hello uniapp!'
        }
    })

    const emit = defineEmits(['update:msg']);

    const msgIn = ref('Hello world!');

    const handleClick = () => {
        emit('update:msg', props.msg + ' ' + msgIn.value);
    }

    onMounted(() => {
        console.log('---- onMounted ----');
    })
</script>

<style lang="scss">

</style>

演示动图:

生成页面

定义生成页生成器:

javascript 复制代码
module.exports = function (plop) {
  // 导入所需模块
  const { exec } = require('child_process');
  const path = require('path');
  const fs = require('fs');

  // 定义打开文件的函数
  function openFile(filePath) {
    const fullPath = path.resolve(process.cwd(), filePath);
    if (fs.existsSync(fullPath)) {
      console.log(`\n正在打开文件: ${fullPath}`);

      // 根据操作系统选择打开方式
      const isWin = process.platform === 'win32';

      // Windows - 尝试使用cursor打开
      exec(`cursor "${fullPath}"`, (error) => {
        if (error) {
          // 如果VS Code不可用,尝试使用默认程序打开
          console.log(`打开文件失败: ${error},文件路径: ${fullPath}`);
        }
      });
    }
    return '文件已创建';
  }

  // 添加页面到pages.json
  function addPageToConfig(answers) {
    try {
      const pagesConfigPath = path.resolve(process.cwd(), 'pages.json');

      if (!fs.existsSync(pagesConfigPath)) {
        return '无法找到pages.json';
      }

      // 读取pages.json文件内容
      let fileContent = fs.readFileSync(pagesConfigPath, 'utf8');

      // 查找pages数组的结束括号位置
      const lastBracketIndex = fileContent.lastIndexOf(']');
      if (lastBracketIndex === -1) {
        return '无法在pages.json中找到pages数组';
      }

      // 检查pages数组是否为空
      const pagesArrayContent = fileContent.substring(
        fileContent.indexOf('"pages"'), 
        lastBracketIndex
      );

      // 是否需要添加逗号(如果数组中已有内容)
      const needComma = pagesArrayContent.includes('{');

      // 新页面的配置信息
      const newPageConfig = `${needComma ? ',' : ''}
        {
            "path": "pages/${plop.getHelper('pascalCase')(answers.name)}/${plop.getHelper('pascalCase')(answers.name)}",
            "style": {
                "navigationBarTitleText": "${answers.title}"
            }
        }`;

      // 将新页面添加到数组末尾
      fileContent = fileContent.substring(0, lastBracketIndex) + 
                    newPageConfig + 
                    fileContent.substring(lastBracketIndex);

      // 保存更新后的文件
      fs.writeFileSync(pagesConfigPath, fileContent, 'utf8');

      return 'pages.json已更新';
    } catch (error) {
      console.error('更新pages.json时出错:', error);
      return `更新配置失败: ${error.message}`;
    }
  }

  // 新建页面
  plop.setGenerator("page", {
    description: "新建页面",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "要新建的页面名:",
      },
      {
        type: "input",
        name: "title",
        message: "要新建的页面标题:",
      },
    ],
    actions: [
      {
        type: "add",
        path: "pages/{{pascalCase name}}/{{pascalCase name}}.vue",
        templateFile: "plop-templates/page.vue.hbs",
      },
      addPageToConfig,
      function(answers) {
        const filePath = `pages/${plop.getHelper('pascalCase')(answers.name)}/${plop.getHelper('pascalCase')(answers.name)}.vue`;
        return openFile(filePath);
      }
    ],
  });
};

生成页面比生成组件需要多做一个功能,就生成页面后,需要向pages.json中添加路由申明,其中addPageToConfig就实现路由申明的

模板文件plop-templates/page.vue.hbs:

xml 复制代码
<template>
    <div>{{ msg }}</div>
</template>

<script setup>
    import { ref, onMounted } from 'vue';

    defineOptions({
        name: "{{camelCase name}}"
    })

    const msg = ref('Hello uniapp!');

    onMounted(() => {
        console.log('---- onMounted ----');
    })

</script>

<style lang="scss">

</style>

演示动图:

生成接口文件

定义接口文件生成器:

javascript 复制代码
module.exports = function (plop) {
  // 导入所需模块
  const { exec } = require('child_process');
  const path = require('path');
  const fs = require('fs');

  // 定义打开文件的函数
  function openFile(filePath) {
    const fullPath = path.resolve(process.cwd(), filePath);
    if (fs.existsSync(fullPath)) {
      console.log(`\n正在打开文件: ${fullPath}`);

      // 根据操作系统选择打开方式
      const isWin = process.platform === 'win32';

      // Windows - 尝试使用cursor打开
      exec(`cursor "${fullPath}"`, (error) => {
        if (error) {
          // 如果VS Code不可用,尝试使用默认程序打开
          console.log(`打开文件失败: ${error},文件路径: ${fullPath}`);
        }
      });
    }
    return '文件已创建';
  }

  // 新建接口文件
  plop.setGenerator("api", {
    description: "新建接口文件",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "要新建的接口文件名:",
      },
    ],
    actions: [
      {
        type: "add",
        path: "api/{{pascalCase name}}.js",
        templateFile: "plop-templates/api.js.hbs",
      },
      function(answers) {
        const filePath = `api/${plop.getHelper('pascalCase')(answers.name)}.js`;
        return openFile(filePath);
      }
    ],
  });
};

模板文件plop-templates/api.js.hbs:

javascript 复制代码
import request from "../utils/request.js";

/**
 * 获取数据
 */
export const queryList = async (id) => {
  return request.request(`/api/test/${id}`, {}, {
    method: "GET"
  })
};

至此,生成模板文件的需求也就基本完成了,生成api接口文件就不录屏了,跟生成组件基本是一样的流程

代码我上传到gitee仓库,可以clone下来跑跑试试,仓库地址:xiewu/plopjsTest

小结

通过上面几个示例我相信你已经基本了解了Plop.js的大致使用方式,上面示例工程还是可以优化,对于uni-app项目,很大可能需要代码分包的,而上面代码只是实现了在主包中增加组件,这我也尝试做了,你可以把上面代码clone下来后切换到feat-complete分支即可。

这里只是抛砖引玉,对于聪明的你,也一定知道怎么把Plop.js应用到自己的项目中了,也期待你的留言和分享👀

Plop.js入门好文推荐:

Plop.js:一键生成代码模板,提升开发效率的利器-CSDN博客

前端工程化-使用 plop 生成项目模板文件

相关推荐
站在风口的猪110834 分钟前
《前端面试题:CSS预处理器(Sass、Less等)》
前端·css·html·less·css3·sass·html5
程序员的世界你不懂1 小时前
(9)-Fiddler抓包-Fiddler如何设置捕获Https会话
前端·https·fiddler
MoFe11 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
去旅行、在路上2 小时前
chrome使用手机调试触屏web
前端·chrome
Aphasia3112 小时前
模式验证库——zod
前端·react.js
lexiangqicheng3 小时前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪3 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
烛阴4 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
孟孟~4 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~4 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js