路径 alias 在 Node 中的最佳姿势
import config from '../../../src/config'
这个 ../../../
我越看越觉得它不顺眼,众所周知,(有追求的)程序员是很懒的,能不多写绝不多写,能用程序解决的绝不自己动手,现在就想办法干掉它。
一般而言,正常流程是
- google / bing
- 愣着干嘛,赶紧抄啊
但是,作为一个有追求的程序员,应该做的是:
根据当前自己已有的知识体系,先想想有哪几种解决方案,然后再去查找业界的解决方案
这样,我们才能不断完善自己的知识体系,培养足够的思维能力。
熟悉 webpack
的都知道它提供了一个叫 resolve
的功能,可以解决这个问题,但是如果项目中没有使用 webpack
或者我就是不想用 webpack
怎么办?
想法
我们来想想有没有其他解决方案,假定当前项目目录结构为
├── src | ├── config | └── controller ├── node_modules ├── index.js └── package.json
1. 全局变量
第一反应,在 Node 程序里面,我们可以直接通过
global.BASE_PATH = __dirname + '/'
之后就可以这么使用
import config from BASE_PATH + 'src/config'
emmmm,太丑了,而且也没方便到哪去,抬走
2. symlink
再一想,想起来在系统中有一个符号链接的东西,再利用 Node 的模块加载机制,我们可以在项目的 node_modules
下建一个链接到项目目录的符号链接。
-
Linux / MacOS (其实就是 unix 内核的系统)
ln -nsf node_modules src
-
Windows
mklink \D src node_modules
然后就可以这么使用
import config from 'src/config'
看起来不错,但是也有一些槽点
- 不同平台创建命令不一致
- 每个clone仓库的人都要手动添加符号链接
既然有槽点,那就解决一下,印象中 Node 的 fs 模块提供了创建 symlink 的方法: fs.symlinkSync
,我们把具体代码写出来,第一个问题迎刃而解。
const src='../src' const dir='node_modules/src' const fs = require('fs') fs.exists(dir, function (e) { e || fs.symlinkSync(src, dir, 'dir') })
第二个问题就简单了,我们利用 package.json scripts 中的 postinstall
钩子,它会在 npm run install
之前被执行,我们把前面写好的代码浓缩一下放进去用 node 执行即可。
{ "scripts": { "postinstall": "node -e \"var src='../src',dir='node_modules/src',fs=require('fs');fs.exists(dir,function(e){e||fs.symlinkSync(src,dir,'dir')});\"" } }
到这里看起来就很不错了,不过仔细一想,还有个小问题。当我们想再加一个 alias,比如 @controller -> src/controller
的时候,喔霍,又得改 postinstall
脚本。
更好一点的做法是将 postinstall
中的脚本抽出来保存成一个单独的文件,但是还是无法避免新增时要修改脚本的问题。
我们可以再通过命令行参数来解决这个问题,但是需要引入新的命令,整个流程就比较繁琐了,新人接手可能还要花点时间才能搞懂这里的逻辑。
3. 环境变量
想了想,又想起来一个知识点,我们可以通过设置环境变量 NODE_PATH
指向对应的路径
export NODE_PATH='项目路径'
之后就可以直接使用
import config from 'src/config'
原理和Node模块加载机制有关,感兴趣的自行查阅,这里就不多说了。
这个方案的缺点也很明显,没错, NODE_PATH
只有一个,因此生成的 alias 只有该路径下的最顶层,没办法添加 src/controller -> controller
这样的 alias。
业界方案
自己的思考大概结束了,我们来看看业界的实现方案。搜了一圈之后,聚焦在一个轮子上: module-alias
看了下源码是通过 hack 原生模块 module
的方法实现的,Node在引入模块时其实就是调用的 module
模块。具体源码就不多说了,我们直接来看最佳实践(我认为的)
module-alias 提供了两种 alias 添加模式
-
配置package.json
通过配置
_moduleAliases
字段添加 alias// package.json { "_moduleAliases": { "@src": "src", "@config": "src/config" } }
// index.js import 'module-alias/register'
-
在项目代码中添加
import { addAlias } from 'module-alias' addAlias('@root', __dirname) addAliases({ '@src': __dirname + 'src', '@config': __dirname + 'src/config' })
之后就可以使用配置的 alias 进行开发了,但是还有一个问题,我们在 VSCode 中的引用没办法点击跳转,引用的模块提示也不能正常加载。
这是因为 VSCode 并不知道这个路径解析的配置,所以需要额外添加配置。只需要配置一下 jsconfig.json
(使用 typescript 的修改 tsconfig.json
)的 compilerOptions
{ "compilerOptions": { "moduleResolution": "node", "baseUrl": ".", "rootDir": ".", "paths": { "@config": ["src/config"], "@src/*": ["src/*"] } } }
需要注意的是,jsconfig.json更改后需要重新打开 VSCode 配置才会生效
以上配置完即可愉快的进行开发了,提高效率、治好强迫症的同时代码提示、引用跳转等都可以正常使用。