基于NodeJS来模仿require.context模块引入方法

2,922 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

相关简介

  • 前言 之前在使用Vue开发项目的时候,会用到webpack提供的一个Api接口:require.context,基于该Api可以很方便的检索和匹配对应的文件,继而实现模块自动化的导入。
    但是,在node.js里面就没有这个好用的Api方法,所以这里就基于node.js实现一个简单的require.context方法。

  • 介绍 要实现一个Api方法,就得先了解它的基本使用方法,先来看看require.context的基本用法:

const cache = {};

function importAll(r) {
  r.keys().forEach((key) => (cache[key] = r(key)));
}

importAll(require.context('../components/', true, /.js$/));

从上面的代码中,可以看到:require.context可以接受三个参数:

  • directory:第一个参数,表示需要进行匹配的目录;
  • useSubdirectories: 第二个参数,表示是否匹配子级目录;
  • regExp:第三个参数,匹配文件使用的正则表达式。

接着,可以知道该方法返回一个方法对象(上下文模块API),该对象可以通过keys方法遍历出匹配到的文件;并且,通过该对象可以再次执行得到模块内容。

基本实现

知道require.context的基本使用后,就可以基于此进行一个简单的模拟,步骤如下:

  1. 自定义requireContext方法
const fs = require('fs')
const resolve = require('path').resolve

const requireContext = (dirPath, deep = false, reg) => {}
  1. 通过path.resolve方法来解析路径:
const fs = require('fs')
const resolve = require('path').resolve

const requireContext = (dirPath, deep = false, reg) => {
    dirPath = resolve(process.cwd(), dirPath)
}
  1. 定义读取目录的方法,该方法如下:
    1). 通过readdirSync读取目录信息;
    2). 通过statSync在遍历目录信息时,区分出文件和目录格式,并对对应处理;
    3). 通过递归的方式,读取玩目录中的所有内容。 代码实现如下:
const fs = require('fs')
const resolve = require('path').resolve

const readDirSync = dirPath => {
    const result = [], dirs = []
    const files = fs.readdirSync(dirPath)
    files.forEach(file => {
        const stat = fs.statSync(resolve(dirPath, file))
        stat.isDirectory() ? dirs.push(file) : result.push(resolve(dirPath, file))
    })
    dirs.forEach(dir => result.push(...readDirSync(resolve(dirPath, dir))))
    return result
}
  1. 判断是否需要读取子级目录,并做不同处理:
    1). 如果需要读取子级目录,则通过定义好的readdirSync递归处理;
    2). 如果不需要读取子级目录,则直接使用fs.readdirSync读取,并过滤掉目录文件。
const fs = require('fs')
const resolve = require('path').resolve

const requireContext = (dirPath, deep = false, reg) => {
    dirPath = resolve(process.cwd(), dirPath)

    let files = deep
        ? readDirSync(dirPath)
        : fs.readdirSync(dirPath).filter(file => !fs.statSync(resolve(dirPath, file)).isDirectory())
}
  1. 使用正则表达式配合filter方法过滤文件:
const fs = require('fs')
const resolve = require('path').resolve

const requireContext = (dirPath, deep = false, reg) => {
    dirPath = resolve(process.cwd(), dirPath)

    let files = deep
        ? readDirSync(dirPath)
        : fs.readdirSync(dirPath).filter(file => !fs.statSync(resolve(dirPath, file)).isDirectory())
    if (reg instanceof RegExp) {
        files = files.filter(file => reg.test(file))
    }
}
  1. 返回上下文模块对象,并实现keys方法:
const fs = require('fs')
const resolve = require('path').resolve

const requireContext = (dirPath, deep = false, reg) => {
    dirPath = resolve(process.cwd(), dirPath)

    let files = deep
        ? readDirSync(dirPath)
        : fs.readdirSync(dirPath).filter(file => !fs.statSync(resolve(dirPath, file)).isDirectory())
    if (reg instanceof RegExp) {
        files = files.filter(file => reg.test(file))
    }
    const context = file => require(file)
    context.keys = () => files.map(file => resolve(dirPath, file))

    return context
}

实现以上代码后就可以使用了,下面看看使用的例子(simple-require-context是写的一个npmdemo包,这里方便直接下载引入):

const basename = require('path').basename
const requireContext = require('simple-require-context')

const context = requireContext('./demo', false, /\.js$/)
const modules = {}
context.keys().forEach(m => {
  modules[basename(m, '.js')] = context(m)
})


console.log(modules)

文件目录和执行结果如下:

image.png

以上就是一个最简单的实现,完整代码如下:

const fs = require('fs')
const resolve = require('path').resolve

const readDirSync = dirPath => {
  const result = [], dirs = []
  const files = fs.readdirSync(dirPath)
  files.forEach(file => {
    const stat = fs.statSync(resolve(dirPath, file))
    stat.isDirectory() ? dirs.push(file) : result.push(resolve(dirPath, file))
  })
  dirs.forEach(dir => result.push(...readDirSync(resolve(dirPath, dir))))
  return result
}

const requireContext = (dirPath, deep = false, reg) => {
  dirPath = resolve(process.cwd(), dirPath)

  let files = deep ? readDirSync(dirPath) : fs.readdirSync(dirPath).filter(file => !fs.statSync(resolve(dirPath, file)).isDirectory())

  if (reg instanceof RegExp) {
    files = files.filter(file => reg.test(file))
  }

  const context = file => require(file)
  context.keys = () => files.map(file => resolve(dirPath, file))

  return context
}

module.exports = requireContext

除此之外,require.context方法还有其他的一些实现,因为平时没用到就不琢磨了,感兴趣的可以看看。
至此,就已经简单实现了require.context自动模块化引入了:)