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('文件内容已成功替换!');
      });
    }
  })
});

结语

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

不懂的可以去试试

相关推荐
秦jh_23 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21336 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy37 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞2 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-2 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun2 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss