React根据文件目录自动生成路由

前言

之前写博客时,都是一个功能一个文件夹,需要呈现哪个功能时就引入一下,再注释掉其他的引入。现在打算升级一下,引入路由,并根据目录下的文件自动生成路由表,这样就只要在对应的文件夹下面穿创建文件,路由表就会自动更新。

正文

原理很简单,只要在编译前去检查路由表是否需要更新,需要更新就运行更新路由脚本

这就是自动生成的效果

配置webpack

因为我们需要在编译前去检查路由表是否需要更新,所以我们需要去写一个webpackplugin干这件事,就需要我们能够修改webpack配置。

修改webpack配置不会的话,可以去看看 React配置路径别名、ESLint设置、git commit前规范检查 - 掘金

配置文件内容修改为

js 复制代码
//craco.config.js
const path = require('path');
const { spawnSync } = require('child_process');


class MyBeforeCompilePlugin {
  apply(compiler) {
    compiler.hooks.beforeCompile.tapAsync('beforeCompile', (params, callback) => {
      const scriptPath = path.resolve(__dirname, 'beforeCompileScript.js');
      const result = spawnSync('node', [scriptPath]);
      if (result.error) {
        console.error('运行脚本时出错:', result.error);
      } else {
        console.log('beforeCompile 脚本运行成功!');
      }
      callback()
    });
  }
}
module.exports = {
  webpack: {
    plugins: [
      new MyBeforeCompilePlugin()
    ],
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
  },
  devServer: {
    port: 8080,
  },
};

确定路由生成规则

  1. 只要在src/pages下创建XXX目录,并在下创建index.tsx文件,就会生成对应路由。
  2. 可能还有其他的需求,还可以在目录下创建pageConfig.ts(这个名称可以随便取,但是要跟脚本中的名称对应),用来配置页面中其他的参数,根据需求来。这边我是生成了一个标题的字段,比如
js 复制代码
//src/pages/home/pageConfig.ts
export default {
  title: "首页",
}

编写脚本

scr下创建router文件夹,并在下面创建index.js文件 我们脚本修改的是childrenList的值。

js 复制代码
import App from '../App.tsx'
import { createBrowserRouter } from 'react-router-dom'
let childrenList = [];

let children = childrenList.map((item) => {
  return {
    element: item.element,
    path: item.path,
  }
})

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children
  }
])

let routeList = childrenList

export {
  routeList
}

export default router

在根目录下创建beforeCompileScript.js脚本文件

js 复制代码
let path = require('path');
const fs = require('fs');

/**
* @description 读取文件夹下面的文件夹名称
* @param path 文件夹路径 string
* @returns 返回一个promise对象,resolve返回文件夹名称数组
*/
const readdir = (path) => {
  return new Promise((resolve, reject) => {
    fs.readdir(path, { withFileTypes: true }, (err, files) => {
      if (err) {
        reject(err);
      }
      const folderNames = files.filter(file => file.isDirectory()).map(folder => folder.name);
      return resolve(folderNames);
    })
  })
}

/**
* @description 同步读取文件内容
* @param path 文件路径 string
* @returns 返回文件内容 string
*/
const readFile = (path) => {
  return fs.readFileSync(path, (err, data) => {
    if (err) {
      return '';
    }
    return data;
  })
}

/** 路由文件地址*/
let routerPath = path.join(__dirname, 'src', 'router', 'index.js');
// 读取文件内容
fs.readFile(routerPath, 'utf-8', (err, data) => {
  if (err) {
    console.error('读取文件时出错:', err);
    return;
  }
  let startInd = data.indexOf('childrenList');
  //从开始下标开始寻找
  let resultStartInd = data.indexOf('[', startInd);
  let resultEndInd = data.indexOf(']', startInd);
  /** 需要替换的str*/
  let replaceStr = data.substring(resultStartInd, resultEndInd + 1);
  /** 原本文件中的路由列表*/
  let originalList = replaceStr
  originalList = originalList.replace(/\</g, '"<')
  originalList = originalList.replace(/\/\>/g, '/>"')
  originalList = JSON.parse(originalList);

  /** 是否需要更新路由*/
  let isChange = false;
  /** 替换的路由列表*/
  let replacingContent = [];

  readdir(path.join(__dirname, 'src', 'pages')).then(res => {
    let content = {}
    res.forEach((name) => {
      content = {};
      try {
        let filePath = path.join(__dirname, 'src', 'pages', name, 'pageConfig.ts')
        let fileData = readFile(filePath).toString();
        let startInd = fileData.indexOf('{')
        let endInd = fileData.lastIndexOf('}');
        let resultData = fileData.substring(startInd, endInd + 1);

        //正则去除空白符
        resultData = resultData.replace(/\s/g, '')
        //正则 ' 变为 "
        resultData = resultData.replace(/\'/g, '"')
        //正则去掉}前面的,
        resultData = resultData.replace(/\,(?=\})/g, '')
        //为key加上双引号
        resultData = resultData.replace(/([a-zA-Z0-9]+?):/g, '"$1":');

        resultData = JSON.parse((resultData))
        content = {
          ...resultData,
          path: `${name}`,
          element: `<${name.charAt(0).toUpperCase() + name.slice(1)} />`,
        }
        replacingContent.push(content);
      } catch (error) {
        content = {
          path: `${name}`,
          element: `<${name.charAt(0).toUpperCase() + name.slice(1)} />`,
        }
        replacingContent.push(content);
      }
    })
    return res
  }).then((res) => {
    if (replacingContent.length !== originalList.length) {
      isChange = true;
    }

    if (!isChange) {
      for (let i = 0; i < replacingContent.length; i++) {
        let keys = Object.keys(replacingContent[i]);
        /** 需要检查的key列表*/
        let checkKeys = keys.filter((item) => {
          return !['element', 'path'].includes(item)
        })
        /** 检查长度是否一样*/
        if (keys.length != Object.keys(originalList[i]).length) {
          isChange = true;
          break;
        }

        /** 检查key对相应的value是否一致*/
        for (let j = 0; j < keys.length; j++) {
          if (checkKeys.includes(keys[j]) && replacingContent[i][keys[j]] != originalList[i][keys[j]]) {
            isChange = true;
            break;
          }
        }
      }
    }

    if (isChange) {
      // 替换文件内容
      let updatedContent = data.replace(replaceStr, JSON.stringify(
        replacingContent
      ));
      //去除导入的组件,同时去掉回车符
      updatedContent = updatedContent.replace(/import.*from.*@.*\S/g, ' ')
      updatedContent = updatedContent.trim();

      // import 导入组件
      res.forEach((name) => {
        updatedContent = `import ${name.charAt(0).toUpperCase() + name.slice(1)} from '@/pages/${name}/index.tsx'\n`.concat(updatedContent)
      })

      updatedContent = updatedContent.replace(/\"\</g, '<')
      updatedContent = updatedContent.replace(/\>\"/g, '>')

      // 将更新后的内容写入文件
      fs.writeFile(routerPath, updatedContent, 'utf-8', (err) => {
        if (err) {
          console.error('写入文件时出错:', err);
          return;
        }
        console.log('文件内容已成功替换!');
      });
    }
  })
});

结语

经以上步骤,不管在运行项目还是打包项目时,都会先检查路由是否需要更新,需要则更新路由表。

不懂的可以去试试

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax