在 NodeJS 中我们常常会写下面这种代码:

1
const userService = require('../../../model/User/service')

../../.. 这种代码过多,既不好看也容易出错。在 AdonisJSwebpack 的启发下,我发现可以使用 global 全局变量来解决这个问题。

require 加载原理解析

总所周知,Node.js 使用的模块格式为 CommonJS

在查阅 NodeJS 文档 以后,require 根据以下规则加载模块:

Y 目录下 require(X) 时:

  1. 如果 X 是内置模块,加载该模块;
  2. 如果 X/ 开头,设置 Y 为文件系统的根部 (root);
  3. X./ , / , ../ 开头时,加载该文件或者加载该目录;
  4. 否则加载 node_modules 目录下的模块: LOAD_NODE_MODULES(X, dirname(Y))

其中,require 还受环境变量 NODE_PATH 的影响。该变量是一个以 : 为分隔符的绝对路径列表,如果上述步骤未找到所需的模块时,Node.js 会读取该变量并从这些路径中搜索模块。

简化模块路径

因此简化这种 require('../../xxx') 代码有多种方法,一个是另外写一个 global 变量用于包装一下 require 函数,也可以直接设置 NODE_PATH 环境变量。Better local require() paths for Node.js 有所有简化路径的方法。

用过 webpack 的人估计都听说过 resolve.alias 这个配置项,我们可以参照它来实现一个类似的 local require。webpack resolve.alias 的用法如下:

1
2
3
4
5
6
alias: {
  '~': path.resolve(__dirname, 'src/')
}

// component.js
const api = require('~api')

我们的初始实现代码如下:

1
2
3
4
5
6
7
8
// global-require.js
global.appRequire = function appRequire(module) {
  const idx = module.indexOf('~')
  if (idx === 0) {
    return require(`${__dirname}/${module.substring(idx+1)}`)
  }
  return require(module)
}

然后在代码入口 最先 加载 global-require.jsappRequire 注入 global 变量即可生效。

1
2
3
require('./global-require')

const app = appRequire('~app')

完善

在使用 global-require.js 时,使用 mocha 等单元测试工具会报错,我们可以在 mocha 启动时将 appRequire 注入到全局变量中:

1
mocha -r ./global-require.js tests/