前端开发中如何快速生成代码片段与模块

152 阅读5分钟

1、vsCode代码片段

①vsCode中搜索"代码片段"

image.png

②重新新建一个代码片段文件

image.png

③命名文件名称

image.png

④打开snippet-generator.app/

image.png

⑤放代码片段里边

image.png

⑥输入指令"react-components"生成代码片段

image.png

2、nodeCli脚本快速生成一个列表和表单

在开发后台管理系统时,我们遇到了一个典型的场景:项目包含上百个功能相似的页面,基本都是基于增删改查的基础架构,只有业务逻辑存在差异。目前的解决方案是通过复制现有页面代码再手动修改,但这种方式存在几个明显问题:

  1. 需要频繁复制粘贴相似代码
  2. 必须手动清理无关的业务逻辑代码
  3. 每个模块涉及多个文件(列表、表单、路由配置等)
  4. 修改维护成本高,容易产生冗余代码

更理想的解决方案是开发一个Node.js命令行工具,通过自动化生成来提升效率。具体实现思路如下:

  1. 基于模板引擎动态生成标准化的页面代码
  2. 自动创建关联文件(列表组件、表单组件等)
  3. 智能更新路由配置文件
  4. 支持通过一条命令快速生成完整的功能模块

这个方案的核心在于文件系统的读写操作,通过程序化方式将模板代码写入指定位置。相比手动复制,自动化生成具有以下优势:

  • 确保代码一致性
  • 减少人为错误
  • 提升开发效率
  • 便于统一维护

接下来,我们将具体实现这个Node.js命令行工具,通过模板化和自动化来解决批量页面开发的问题。

①我们先确定一个思路,定义两个方法,一个生成代码文件,一个往路由文件里边加代码

先在项目根目录新增一个scripts/generateModule.js文件

const createModuleFiles = ()=>{
  console.log('新增了模块')
}
const updateRoutes = ()=>{
  console.log("往路由里加路径")
}

// 执行
createModuleFiles();
updateRoutes();

②在package.json下增加一条命令

    "generate": "node scripts/generateModule.js",

③在控制台上执行"pnpm generate ModuleName"

image.png

④现在先判断文件夹中是否已存在我们要创建的文件,没有则创建文件

const fs = require('fs');
const path = require('path');
// 获取命令行参数(模块名称)
const moduleName = process.argv[2];//pnpm generate Test(即拿到Test)
if (!moduleName) {
  console.error('请提供模块名称,例如:pnpm generate Test');
  process.exit(1);
}
// 路径定义
const srcDir = path.join(__dirname, '..', 'src');
const pageDir = path.join(srcDir, 'pages');
const moduleDir = path.join(pageDir, moduleName);
const componentsDir = path.join(moduleDir, 'components');
const createModuleFiles = () => {
  try {
    // 创建page目录
    if (!fs.existsSync(pageDir)) {
      fs.mkdirSync(pageDir, { recursive: true });
    }
    // 创建模块目录
    if (!fs.existsSync(moduleDir)) {
      fs.mkdirSync(moduleDir, { recursive: true });
    }
    // 创建components目录
    if (!fs.existsSync(componentsDir)) {
      fs.mkdirSync(componentsDir, { recursive: true });
    }
  } catch {
    console.error('创建目录失败');
  }
}
const updateRoutes = () => {
    
}
// 执行
createModuleFiles();
updateRoutes();

⑤我们需要生成两个文件,一个是列表文件,一个是表单文件,我们得分别定义这两个文件的代码模板

const fs = require('fs');
const path = require('path');
// 获取命令行参数(模块名称)
const moduleName = process.argv[2];//pnpm generate Test(即拿到Test)
if (!moduleName) {
  console.error('请提供模块名称,例如:pnpm generate Test');
  process.exit(1);
}


const ListTemplate = `   
import React, { memo, ReactNode } from 'react';
interface IProps {
  children?: ReactNode;
}
/**
 * react组件
 * @returns
 */
const name: React.FC<IProps> = () => {
  return <div className="name">name</div>;
};
export default memo(name);
`

const formTemplate = `
import React, { memo, ReactNode } from 'react';
interface IProps {
  children?: ReactNode;
}

/**
 * 表单组件名称
 * @returns
 */
const EditForm: React.FC<IProps> = () => {
  return <div className="editForm">EditForm</div>;
};

export default memo(EditForm);
`

// 路径定义
const srcDir = path.join(__dirname, '..', 'src');
const pageDir = path.join(srcDir, 'pages');
const moduleDir = path.join(pageDir, moduleName);
const componentsDir = path.join(moduleDir, 'components');
const createModuleFiles = () => {
  try {
    // 创建page目录
    if (!fs.existsSync(pageDir)) {
      fs.mkdirSync(pageDir, { recursive: true });
    }
    // 创建模块目录
    if (!fs.existsSync(moduleDir)) {
      fs.mkdirSync(moduleDir, { recursive: true });
    }
    // 创建components目录
    if (!fs.existsSync(componentsDir)) {
      fs.mkdirSync(componentsDir, { recursive: true });
    }

        // 写入文件
    fs.writeFileSync(path.join(moduleDir, 'index.tsx'), ListTemplate);
    fs.writeFileSync(
      path.join(componentsDir, 'EditForm.tsx'),
      formTemplate,
    );
  } catch {
    console.error('创建目录失败');
  }
}
const updateRoutes = () => {
    
}
// 执行
createModuleFiles();
updateRoutes();

到这一步,我们可以执行命令生成文件了,我们来看看效果: image.png

⑥现在我们在路由中自动引入的代码,在updateRoutes方法中实现

const routesFile = path.join(srcDir, 'routes', 'index.ts');
// 模块名称(首字母大写,用于组件名)
const capitalizedModuleName =
  moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
const formattedModuleName = moduleName
  .replace(/([A-Z])/g, ' $1') // 在大写字母前添加空格
  .trim() // 移除开头可能的空格
  .split(' ')
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
  .join(' ');

const camelModuleName = moduleName
  .replace(/([A-Z])/g, (match, p1, offset) => {
    return offset === 0 ? p1.toLowerCase() : '-' + p1.toLowerCase();
  })
  .split('-')
  .filter((word) => word) // 过滤空字符串
  .join('-');

const routeEntriesTemplate = `  {
    path: '/${camelModuleName}',
    component: './${capitalizedModuleName}',
    name: '${formattedModuleName}',
  },
  {
    path: '/new-${camelModuleName}/:id',
    component: './${capitalizedModuleName}/components/EditForm',
    name: '${formattedModuleName}',
    hideInMenu: true,
  },
  {
    path: '/update-${camelModuleName}/:id',
    component: './${capitalizedModuleName}/components/EditForm',
    name: '${formattedModuleName}',
    hideInMenu: true,
  },
  {
    path: '/view-${camelModuleName}/:id',
    component: './${capitalizedModuleName}/components/EditForm',
    name: '${formattedModuleName}',
    hideInMenu: true,
  },
`;
const updateRoutes = () => {
      try {
    if (!fs.existsSync(routesFile)) {
      console.error('routes/index.ts 文件不存在!');
      process.exit(1);
    }

    // 读取routes文件内容
    let routesContent = fs.readFileSync(routesFile, 'utf-8');

    // 查找路由数组的结束位置
    const routesArrayEnd = routesContent.lastIndexOf('];');
    if (routesArrayEnd === -1) {
      console.error('routes/index.ts 文件格式不正确,未找到路由数组!');
      process.exit(1);
    }

    // 检查是否已存在任一目标路由
    const targetPaths = [
      `path: '/${moduleName}'`,
      `path: '/new-${moduleName}/:id'`,
      `path: '/update-${moduleName}/:id'`,
      `path: '/view-${moduleName}/:id'`,
    ];
    if (targetPaths.some((path) => routesContent.includes(path))) {
      console.log(`模块 ${moduleName} 的路由已存在,跳过路由更新。`);
      return;
    }

    // 检查最后一个路由条目前是否已有逗号
    const contentBeforeEnd = routesContent.slice(0, routesArrayEnd).trimEnd();
    const needsComma = contentBeforeEnd && !contentBeforeEnd.endsWith(',');

    // 在路由数组结束前插入新路由
    routesContent = `${contentBeforeEnd}${
      needsComma ? ',' : ''
    }\n${routeEntriesTemplate}${routesContent.slice(routesArrayEnd)}`;

    // 写入更新后的routes文件
    fs.writeFileSync(routesFile, routesContent);
    console.log('routes/index.ts 文件更新成功!');
  } catch (err) {
    console.error('更新 routes/index.ts 失败:', err);
    process.exit(1);
  }
}

最后,我们来看一下效果: image.png 更新引用成功