import-local库深入探索

748 阅读3分钟

「这是我参与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这个库有帮助。