node如何实现webpack的require.context

158 阅读3分钟

引子

趁着闲暇时间,自己学了点关于Node的知识,主要是困于不会后端,想着利用Node来搭建一个本地服务器,让自己可以摆脱对后端服务器的依赖,也可以学习一些新的知识,拓宽一下自己的领域。

刚开始在写接口的时候,发现了一个很影响代码阅读的问题,就是接口过多,如何将接口进行分模块,代码如下

const express = require('express');

//首先加载express
const app = express()
//端口号
const port = 3000

//这里仅列举发送GET请求
app.get('/student',(req,res) =>{
  const result = [{name: "zhangsan"}];
  res.send(result)
})
// ...其他接口

app.listen(port,() => console.log('server is start,port is', port))

尝试优化

这样适合初学者去练习,当接口过多了,会导致接口篇幅过长,且不利于代码维护,基于此,想着把接口进行模块化处理,将接口分块处理,依次引入,然后就做了如下优化

// node index.js
const express = require('express');

// 引入接口模块
const { API: StudentAPI } = require('./api/student');
const { API: PersonAPI } = require('./api/person');
// ...引入其他模块

//首先加载express
const app = express()
//端口号
const port = 3000

// 接口注册
StudentAPI.use(app)
PersonAPI.ues(app)
// ...注册其他模块接口

app.listen(port,() => console.log('server is start,port is', port))

// student.js接口
exports.API = {
  use(app) {
    //这里仅列举发送GET请求
    app.get('/student',(req,res) =>{
      const result = [{name: "zhangsan"}];
      res.send(result)
    })
    // ...
  }
}

这样乍一看,确实提高了代码的可阅读性,也方便了代码后期可维护性,本来想着可以到此结束,照此拓展其他接口了,但是又想到后期继续加新模块,还要在index.js注册,不想这么麻烦,就想到了之前项目里使用的require.context了。

终极优化

于是开始在Node里搜索require.context,却发现并没有此方法,继续探索,发现了require.context只适用于webpack项目,好家伙,心凉了一大截,就开始探索require.context的实现原理了。

其原理大致如下

  1. 给定文件路径
  2. 读取当前路径下的所有文件,并收集
  3. 将收集的文件依次引入

是不是柳暗花明又一村,心想着利用Node去尝试实现下。此处涉及了Nodefs(文件系统模块)path(路径模块)的内容,不熟悉的小伙伴可以去充电,做一下了解。立即充电

按照这个思路,我又做了代码调整

// autoLoadFiles.js
const fs = require('fs');
const path = require('path');

/**
 * 读取文件列表
 * @param {String} filePath 文件目录
 * @param {Boolean} useSubFilePath 是否读取子目录
 * @param {RegExp} fileReg 文件过滤正则
 * @param {Array} fileList 收集的文件
 * @returns {Array} fileList
 */

function readFileList(filePath, useSubFilePath, fileReg, fileList = []) {
  // 读取当前文件夹下的内容
  const files = fs.readdirSync(filePath);

  files.forEach(item => {
    // 获取当前文件的完整路径
    const curFilePath = path.join(filePath, item);
    // 获取文件stat信息,主要判断当前文件是否是文件或者文件夹
    const fileStat = fs.statSync(curFilePath);
    // 如果是文件,并且满足正则,则收集当前文件路径
    if(fileStat.isFile() && fileReg.test(curFilePath)) {
      fileList.push(curFilePath)
    }
    // 如果是文件夹,并且开启子目录查询,则递归遍历子目录
    if(fileStat.isDirectory() && useSubFilePath) {
      readFileList(curFilePath, useSubFilePath, fileReg, fileList)
    }
  })
  // 返回收集的文件
  return fileList;
}

function getFileName(fileFullPath) {
  return path.basename(fileFullPath).split('.')[0];
}

exports.autoLoadFiles = function(filePath, useSubFilePath, fileReg) {
  const fileList = readFileList(filePath, useSubFilePath, fileReg);
  // 处理收集的文件
  const res = fileList.map(filePath => ({
    fullPath: filePath,
    fileName: getFileName(filePath),
    data: require(filePath) // 引入当前文件
  }));
  // 返回是收集的文件
  return res;
}

// index.js
const express = require('express');
const path = require('path');

const { autoLoadFiles } = require('./autoLoadFiles');

const filePath = path.join(__dirname, '/api');
// 获取指定目录的文件内容
const files = autoLoadFiles(filePath, true, /.js$/);

//首先加载express
const app = express()
//端口号
const port = 3000
// 自动引入app的接口
files.forEach((file) => {
  // 接口注册
  file.data.API.use(app);
})

app.listen(port,() => console.log('server is start,port is', port))

// student.js 接口
exports.API = {
  use(app) {
    //这里仅列举发送GET请求
    app.get('/student',(req,res) =>{
      const result = [{name: "zhangsan"}];
      res.send(result)
    })
  }
}

文档目录如下,仅供参考

目录结构

希望能够帮助到有需求的你,respect!