使用import-local可以让全局安装的包使用你项目中自己的版本。意思就是假设你在电脑里全局安装了webpack4,但是项目中使用的是webpack5,当你在项目中使用webpack命令的时候,他会优先使用webpack5版本,当然这个前提是你的node_modules已经安装l webpack。
import-local常常在cli工具中使用,可以方便cli工具开发(可以参考lerna)。使用方式如下:
```
/* eslint-disable import/no-dynamic-require, global-require */
const importLocal = require("import-local");
if (importLocal(__filename)) {
require("npmlog").info("cli", "using local version of lerna");
} else {
require(".")(process.argv.slice(2));
}
```
使用方式很简单,导入import-local,传入当前执行文件的路径,当条件满足执行对应逻辑即可。我们通过调试来分析它的实现原理:
import-local执行时通过传递__filename
作为参数执行。__filename
在node中为当前模块的文件名。 这是当前模块文件的已解析符号链接的绝对路径。例如:从 /Users/mjr
运行 node example.js
console.log(__filename);
// 打印: /Users/mjr/example.js
以lerna为例,在执行lerna命令时,他是先执行node全局lib/node_modules
中的lerna的可执行文件(全局安装的npm包都会安装在node安装目录下的lib/node_modules
文件夹)。
import-local内部源码如下:
'use strict';
const path = require('path');
const resolveCwd = require('resolve-cwd');
const pkgDir = require('pkg-dir');
module.exports = filename => {
const globalDir = pkgDir.sync(path.dirname(filename));
const relativePath = path.relative(globalDir, filename);
const pkg = require(path.join(globalDir, 'package.json'));
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));
// Use `path.relative()` to detect local package installation,
// because __filename's case is inconsistent on Windows
// Can use `===` when targeting Node.js 8
// See https://github.com/nodejs/node/issues/6624
return localFile && path.relative(localFile, filename) !== '' ? require(localFile) : null;
};
我们先看第一行代码const globalDir = pkgDir.sync(path.dirname(filename));
,它使用了path.dirname
处理了传入的filename
参数。path.dirname
为当前模块的目录名,例如:从 /Users/mjr
运行 node example.js
console.log(__dirname);
// 打印: /Users/mjr
我们前面说过__filename
在node中为当前模块的文件名,在lerna中执行,以我的电脑为例,该路径为:/usr/local/lib/node_modules/lerna/cli.js
(具体路径会因你电脑上node的安装目录不同有所差异)。
path.dirname
处理了这个路径返回的就是/usr/local/lib/node_modules/lerna
,然后它将返回路径交给了pkg-dir
处理,pkg-dir
执行找到 Node.js 项目或 npm 包的根目录,在这里就是lerna的安装目录。
globalDir
为/usr/local/lib/node_modules/lerna
接下来在看const relativePath = path.relative(globalDir, filename);
。path.relative()
方法根据当前工作目录返回从 from
到 to
的相对路径。 如果 from
和 to
都解析为相同的路径(在分别调用 path.resolve()
之后),则返回零长度字符串。如果零长度字符串作为 from
或 to
传入,则将使用当前工作目录而不是零长度字符串。
relativePath
为:cli.js
在看const pkg = require(path.join(globalDir, 'package.json'));
,path.join()
方法使用特定于平台的分隔符作为定界符将所有给定的 path
片段连接在一起,然后规范化生成的路径。这里使用path.join
将erna的安装目录globalDir
和package.json
平成特定平台的路径,然后使用require
加载并解析成一个json对象。
require
支持加载的文件类型有三种:
- .js
- .json
- .node
- .js需要使用module.exports/exports导出模块内容
- .json则使用JSON.parse解析
- .node则是通过process.dlopen打开c++插件(C++ AddOns)
require
加载的文件也可以是其他的文件后缀,如:.txt
。当加载的是出上述三种以外的文件后缀时,它会当成js文件解析。
pkg
为package.json
的对象信息。
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));
通过path.join(pkg.name, relativePath)
得到lerna/cli.js
,然后使用resolve-cwd
库的silent方法在当前工作目录中找到lerna/cli.js
,silent方法在可以找到该文件时,返回完整的路径,否则返回undefined,这里返回了/Users/xxx/lerna-v3.22.1/core/lerna/cli.js
,也就是工作目录的lerna可执行文件。
const localNodeModules = path.join(process.cwd(), 'node_modules');
const filenameInLocalNodeModules = !path.relative(localNodeModules, filename).startsWith('..');
通过path.relative
探测本地是否安装的相应的node包。
到了这里,可以看出import-local是通过获取处理各种路径,然后获取本地localFile路径,并根据本地node_modules是否安装,来判断是否优先使用本地版本的包。 相关调试参数如下: