跳到主要内容

import-local 库的作用:本地node_modules 存在一个脚手架命令,同时 全局node_modules 中也存在这个脚手架命令的时候,优先选用 本地node_modules 中的版本,否则选用 全局node_modules 中的版本

源码解析

我们先来看一下源码部分:

"use strict";
const path = require("path");
const { fileURLToPath } = require("url");
const resolveCwd = require("resolve-cwd");
const pkgDir = require("pkg-dir");

module.exports = (filename) => {
const normalizedFilename = filename.startsWith("file://")
? fileURLToPath(filename)
: filename;
const globalDir = pkgDir.sync(path.dirname(normalizedFilename));
const relativePath = path.relative(globalDir, normalizedFilename);
const pkg = require(path.join(globalDir, "package.json"));
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));
const localNodeModules = path.join(process.cwd(), "node_modules");

const filenameInLocalNodeModules =
!path.relative(localNodeModules, normalizedFilename).startsWith("..") &&
// On Windows, if `localNodeModules` and `normalizedFilename` are on different partitions, `path.relative()` returns the value of `normalizedFilename`, resulting in `filenameInLocalNodeModules` incorrectly becoming `true`.
path.parse(localNodeModules).root === path.parse(normalizedFilename).root;

return (
!filenameInLocalNodeModules &&
localFile &&
path.relative(localFile, normalizedFilename) !== "" &&
require(localFile)
);
};

我们根据以下步骤一步一步分析它究竟做了些什么 ❓

1. 将传入的 filename 进行格式化

const normalizedFilename = filename.startsWith("file://")
? fileURLToPath(filename)
: filename;
  • 判断传入的 filename 是否是以 file://开头,如果是 -> 通过 url 中的fileURLToPath方法转换为正常使用的 path
  • file:// 是本地文件传输协议,用于定义 URL 格式的文件路径。
const { fileURLToPath } = require("url");

new URL("file:///C:/path/").pathname; // Incorrect: /C:/path/
fileURLToPath("file:///C:/path/"); // Correct: C:\path\ (Windows)

new URL("file://nas/foo.txt").pathname; // Incorrect: /foo.txt
fileURLToPath("file://nas/foo.txt"); // Correct: \\nas\foo.txt (Windows)

new URL("file:///你好.txt").pathname; // Incorrect: /%E4%BD%A0%E5%A5%BD.txt
fileURLToPath("file:///你好.txt"); // Correct: /你好.txt (POSIX)

new URL("file:///hello world").pathname; // Incorrect: /hello%20world
fileURLToPath("file:///hello world"); // Correct: /hello world (POSIX)

2. 通过 pkgDir.sync 获取模块目录

const globalDir = pkgDir.sync(path.dirname(normalizedFilename));
  1. 通过 path.dirname() 方法返回 path 的目录名
  2. 找到 filename 所在的目录之后,通过 pkg-dir 这个库来判断 filename 所在的根项目,或者 undefined(返回**undefined **说明 filename 不存在)
    • pkg-dir 查找模块的时候,会从某个目录开始向上查找,直到找到存在package.json的目录,并返回该目录。如果未找到则返回 undefined

3. 获取 filename 与 globalDir 的相对路径

const relativePath = path.relative(globalDir, normalizedFilename);

通过 path.relative 方法获取 globalDir(模块目录)格式化的filename 的相对路径 === 以 globalDir 为参照到 filename 的路径

4. 获取 filename 所在包的 package.json

const pkg = require(path.join(globalDir, "package.json"));

通过 path.join 获取 package.json,将 globalDir(package.json所在的路径)package.json拼接起来。然后通过 CommonJs 的requrie方法,将package.json引入,拿到package.json的内容。

5. 获取本地路径※

const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));
  1. 通过 pkg.name 获取模块名称
  2. 将模块名称和相对路径拼接,生成一个基于当前项目的绝对路径
  3. 通过resolveCwd.silent来获取本地包文件是否存在
    • resolveCwd.silent() 判断当前本地当前路径下是否有这个文件
  4. resolve-cwd 解析→

6. 引入本地模块

const filenameInLocalNodeModules =
!path.relative(localNodeModules, normalizedFilename).startsWith("..") &&
// On Windows, if `localNodeModules` and `normalizedFilename` are on different partitions, `path.relative()` returns the value of `normalizedFilename`, resulting in `filenameInLocalNodeModules` incorrectly becoming `true`.
path.parse(localNodeModules).root === path.parse(normalizedFilename).root;

return (
!filenameInLocalNodeModules &&
localFile &&
path.relative(localFile, normalizedFilename) !== "" &&
require(localFile)
);

如果本地存在该模块,那么就引入本地模块