View on GitHub

Yinjie - GitHub.io

Welcome to the Yinjie's notes

NODE_PATH 对 node module 加载环境的重新规划

一个自认为的不爽点

自开始接触 node ,就被 node 的那个模块加载(引用)的方式一真膈应到现在: 对于非 node_modules 为什么不能像 PHP 甚至 JAVA 那样,默认指定一个模块加载的 basepath,而是只能用 ‘./’ 、’../’ 的相对路径来引用。 这样的话,引用路径有一定的耦合性,在代码结构重构的时候会带来很大不便。 (好,高大尚的理由说完了。。。 再说这样引用也真不好看啊,太 LOW 了点吧~ 在 require 的时候还好,现在用 import 真是不太协调啊~)

之前自创的非主流方法

在用 commonjs-module 的情况下,因为 require 支持动态引用,所以可以在程序入口处设置一个 globalBASE_PATH, 这样,在以后每个模块的引用时,都用基于这个 base_path 的路径:


global.BASE_PATH = __dirname;

---

const module1 = require(global.BASE_PATH + '/dir/module1');

当然,这个不是这篇文章的重点,而且在 ES6 下,import 不支持动态引用,这种方法也就自然无效了。

完美的解决文案

过了好长时间我也再没有去关注这个问题,直到半年前写一个项目,要写一个调用 webpack 的 node 脚本,在看官网api时,顺便又看了一下 module 篇章。 多看了几眼就发现新大陆了:

这里面有提到,如果有设置 NODE_PATH,node 加载非相对/绝对路径时,就会以所设定的 NODE_PATH 做为根路径,进行模块加载。

感觉就是我之前想要的那种情况啊~ 有希望了!

先搞一个简单的试验环境如下:

alt

main.js

console.log(process.env);
const module2 = require('dir2/module2');

module2('main message');

module2.js

const module1_1 = require('dir1/dir1_1/module1_1');

module.exports = function (msg) {
    module1_1(msg);
    console.log(`This is ${msg} from module2 ~`);
}

module1-1.js

module.exports = function (msg) {
    console.log(`This is ${msg} from module1-1 ~`);
}

接下来就是改 NODE_PATH 来试试行不行了,先打印 process.env 来看看默认的值是什么。

也可以在 shell 里直接输出 $NODE_PATH,结果也是空。

里面就没有 NODE_PATH 这一项,难怪之前的 node 压根就不能以非相对路径的形式来调用模块了。那我们就给它加一个~~~。

粗暴直接的全局环境变量修改

NODE_PATH 肯定是一个环境变量了,那么最简单直接的方法就是在系统的环境变量中添加上这么一项就OK了。

找到自己用户的 .bashrc 文件,添加以下内容:

export NODE_PATH=/home/username/www/

再来试一下,还真行,不再显示 Error: Cannot find module 'dir2/module2' 这个讨厌的错误了。 说明 node 本身就提供了这么一种相对于某个路径的加载方式,只是大多数人都忽略了。

当然,这么粗暴的修改全局的环境变量,不是一个好办法,不可能你所有部署的 node 代码都适合于这个基本路径。下面就再介绍两种“动态”设置 NODE_PATH 的方法。

声明临时的环境变量

将不稳定的环境变量写死是挺不靠谱的,但是我们又需要它,那就只能是搞一下临时的了。

linux 下有两个命令 setdeclare 都可以在执行环境中写入环境变量,再通过连续命令符 && 将命令连接起来就行。很简单,如下:

declare -x NODE_PATH=/Users/babytree/Documents/temp&& node main.js
或是
set NODE_PATH=/Users/babytree/Documents/temp&& node main.js

这样的话,每次启动时只需要在启动命令前加上 setdeclare 命令就行,或是将这个写进 package.jsonscript 里,直接 run 就好。

设置子进程的环境变量

还有一种方法也是我现在实际用的方法,通过 child process 启动系统时动态写入一个 NODE_PATH

node 中 child_process.execchild_process.spawnchild_process.fork 三个启动子进程的方法都可以带如下参数:

  • cwd: Current working directory of the child process
  • env: Environment key-value pairs

其中的 env 就是给子进程设置环境信息的参数,我们只需在这里加上想要的 NODE_PATH 属性,就可以了。

const env = process.env;
env.NODE_PATH = path.resolve(__dirname, '../xxx');

child_process.spawn('node', ['--harmony', '--debug'], {env: env});

总结

通过如上的方法,你就可以在 node 中上用“绝对”路径的方法来引用模块了,在代码重构、项目代码共享方面都会有较大的进步空间。 而且,可以提供版本切换等需要,个人感觉还是很有实用价值的。

以后还是得仔细点看文档,在搞定这个问题的同时,我也解决了一个之前个人感觉挺恶心的问题: nodejs V6 不能用 import/export 的优雅解决方法