「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」
前言
前段时间进行webpack源码学习的时候,发现在webapck-cli中发现使用了import-local库。百度了一下这个库的作用:
当全局node_modules和本地node_modules中,存在相同的库,则优先加载本地node_modules中的库。
源码分析
1. 入口文件
通过package.json中的bin发现,入口在于 fixtures/cli.js,而在于cli.js中只有几行代码,引入了根目录下的index.js。事实上,这个index.js才是我们要找的真正的入口文件。
// package.json
"bin": {
"import-local-fixture": "fixtures/cli.js"
},
// cli.js
const importLocal = require('..');
if (importLocal(__filename)) {
console.log('local');
}
2. filename格式化
判断传入的filename是否是以file://开头,如果是,通过url中的fileURLToPath方法转换为正常使用的path。
**file://**是本地文件传输协议,用于定义URL格式的文件路径。
const normalizedFilename = filename.startsWith('file://')
? fileURLToPath(filename)
: filename;
// 示例
fileURLToPath('file:///C:/path/') => C:\path\ (Windows)
fileURLToPath('file://nas/foo.txt') => \\nas\foo.txt (Windows)
3. 获取模块目录
首先通过path.dirname() 方法返回 path 的目录名。找到filename所在的目录之后,通过pkg-dir这个库来判断filename所在的根项目,或者undefined;如果返回undefined说明,filename不存在。pkg-dir查找模块的时候,会从某个目录开始向上查找,直到找到存在package.json的目录,并返回该目录。如果未找到则返回undefined。
-
pkgDir([cwd]) 返回的是 Promise
-
pkgDir.sync([cwd]) 返回的是结果,类似于async/await
const globalDir = pkgDir.sync(path.dirname(normalizedFilename));
4. 获取相对目录
通过path.relative方法获取globalDir(模块目录)与格式化的filename的相对路径。获取的是filename到globalDir的相对路径。
我这里有个疑问,暂时没有搞清楚。在这一步之前,我认为应该做一下globalDir非空判断。理论上说globalDir有可能是undefined。如果globalDir是undefined的话,这一步会抛出错误:TypeError [ERR_INVALID_ARG_TYPE]: The "from" argument must be of type string. Received undefined。
const relativePath = path.relative(globalDir, normalizedFilename);
5. 获取package.json
通过path.join获取package.json,将globalDir(package.json所在的路径)和package.json拼接起来。然后通过CommonJs的requrie方法,将package.json引入,拿到package.json的内容。
const pkg = require(path.join(globalDir, 'package.json'));
6. 获取本地路径
通过pkg.name获取模块名称,然后将模块名称和相对路径拼接,生成一个基于当前项目的绝对路径。通过resolveCwd.silent来获取本地包文件是否存在。
resolveCwd.silent是import-local的核心部分。
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));
resolveCwd是resolve-cwd库,如果慕课不存在的话,会返回undefined的。具体使用方法可以看一下下面的demo。
const resolveCwd = require('resolve-cwd');
console.log(__dirname);
//=> '/Users/sindresorhus/rainbow'
console.log(process.cwd());
//=> '/Users/sindresorhus/unicorn'
console.log(resolveCwd('./foo'));
//=> '/Users/sindresorhus/unicorn/foo.js'
7. 引入本地模块
如果本地存在该模块,那么就引入本地模块。如果本地模块不存在,那么就是用全局模块。
!filenameInLocalNodeModules && localFile && path.relative(localFile, normalizedFilename) !== '' && require(localFile)
我推测 path.relative(localFile, normalizedFilename) !== '' 是说明normalizedFilename不是本地文件,需要通过处理,使用本地文件,也就是require(localFile)。如果是本地文件,那么就是'',这样的话也就不需要处理了。
好的,import-local的源码到这里就结束了。代码量不多,但是使用多个第三方库,希望这篇文章对大叫了解import-local这个库有帮助。