Skip to content

Commit

Permalink
rebase changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Jun 19, 2023
1 parent f0b120b commit 865c217
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 2 deletions.
66 changes: 65 additions & 1 deletion src/resolve.ts
@@ -1,8 +1,9 @@
import { existsSync, realpathSync } from "node:fs";
import { pathToFileURL } from "node:url";
import { joinURL } from "ufo";
import { isAbsolute } from "pathe";
import { isAbsolute, join, normalize } from "pathe";
import { moduleResolve } from "import-meta-resolve";
import { PackageJson, readPackageJSON } from "pkg-types";
import { fileURLToPath, normalizeid } from "./utils";
import { pcall, BUILTIN_MODULES } from "./_utils";

Expand Down Expand Up @@ -148,3 +149,66 @@ export function createResolve(defaults?: ResolveOptions) {
return resolve(id, { url, ...defaults });
};
}

const NODE_MODULES_RE = /^(.+\/node_modules\/)([^/@]+|@[^/]+\/[^/]+)(\/?.*?)?$/;

export function parseNodeModulePath(path: string) {
if (!path) {
return {};
}
const match = NODE_MODULES_RE.exec(normalize(path));
if (!match) {
return {};
}
const [, baseDir, pkgName, subpath] = match;
return {
baseDir,
pkgName,
subpath,
};
}

/** Reverse engineer a subpath export if possible */
export async function resolveSubpath(resolvedPath: string) {
const { pkgName, subpath } = parseNodeModulePath(resolvedPath);

if (!pkgName || !subpath) {
return resolvedPath.replace(/\.[a-z]+$/, "");
}

const { exports } = (await readPackageJSON(resolvedPath)) || {};
const resolvedSubpath =
exports && findSubpath(subpath.replace(/^\//, "./"), exports);

// Fall back to guessing
return resolvedSubpath
? join(pkgName, resolvedSubpath)
: resolvedPath.replace(/\.[a-z]+$/, "");
}

// --- Internal ---

function flattenExports(
exports: Exclude<PackageJson["exports"], string>,
path?: string
) {
return Object.entries(exports).flatMap(([key, value]) =>
typeof value === "string"
? [[path ?? key, value]]
: flattenExports(value, path ?? key)
);
}

function findSubpath(path: string, exports: PackageJson["exports"]) {
const relativePath = path.startsWith(".") ? path : "./" + path;
const _exports = typeof exports === "string" ? { ".": exports } : exports;

if (relativePath in _exports) {
return relativePath;
}

const flattenedExports = flattenExports(_exports);
const [foundPath] =
flattenedExports.find(([_, resolved]) => resolved === path) || [];

Check warning on line 212 in src/resolve.ts

View workflow job for this annotation

GitHub Actions / ci

'_' is defined but never used
return foundPath;
}
43 changes: 42 additions & 1 deletion test/utils.test.ts
@@ -1,5 +1,13 @@
import { fileURLToPath } from "node:url";
import { describe, it, expect } from "vitest";
import { isNodeBuiltin, sanitizeFilePath, getProtocol } from "../src";

import {
isNodeBuiltin,
sanitizeFilePath,
getProtocol,
parseNodeModulePath,
resolveSubpath,
} from "../src";

describe("isNodeBuiltin", () => {
const cases = {
Expand Down Expand Up @@ -58,3 +66,36 @@ describe("getProtocol", () => {
expect(getProtocol("file:///C:/src/a.ts")).to.equal("file");
});
});

describe("parseNodeModulePath", () => {
it("parses paths", () => {
const paths = [
"/src/a/node_modules/thing/dist/index.mjs",
"C:\\src\\a\\node_modules\\thing\\dist\\index.mjs",
];
for (const path of paths) {
expect(parseNodeModulePath(path)).toEqual({
baseDir: expect.stringContaining("/src/a/node_modules/"),
pkgName: "thing",
subpath: "/dist/index.mjs",
});
}
});
});

describe("resolveSubpath", () => {
it("resolves correctly", async () => {
const path = fileURLToPath(
new URL("fixture/node_modules/subpaths/lib/subpath.mjs", import.meta.url)
);
const result = await resolveSubpath(path);
expect(result).toMatchInlineSnapshot('"subpaths/subpath"');
});
it("ignores invalid paths", async () => {
const path = fileURLToPath(
new URL("fixture/subpaths/lib/subpath.mjs", import.meta.url)
);
const result = await resolveSubpath(path);
expect(result).toEqual(path.replace(".mjs", ""));
});
});

0 comments on commit 865c217

Please sign in to comment.