From 9190ce076c6a5ced968e97eabf4bcbd074ae3dfc Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 8 Sep 2023 15:51:23 -0500 Subject: [PATCH 1/4] feat: implement support for pattern trailers --- src/resolve-dependency.ts | 411 ++++++++++++++++++++++++++++---------- 1 file changed, 309 insertions(+), 102 deletions(-) diff --git a/src/resolve-dependency.ts b/src/resolve-dependency.ts index cf47212c..7c6c303f 100644 --- a/src/resolve-dependency.ts +++ b/src/resolve-dependency.ts @@ -1,84 +1,127 @@ -import { isAbsolute, resolve, sep } from 'path'; -import { builtinModules } from 'module'; -import { Job } from './node-file-trace'; +import { isAbsolute, resolve, sep } from "path"; +import { builtinModules } from "module"; +import { Job } from "./node-file-trace"; // node resolver // custom implementation to emit only needed package.json files for resolver // (package.json files are emitted as they are hit) -export default async function resolveDependency (specifier: string, parent: string, job: Job, cjsResolve = true): Promise { +export default async function resolveDependency( + specifier: string, + parent: string, + job: Job, + cjsResolve = true +): Promise { let resolved: string | string[]; - if (isAbsolute(specifier) || specifier === '.' || specifier === '..' || specifier.startsWith('./') || specifier.startsWith('../')) { - const trailingSlash = specifier.endsWith('/'); - resolved = await resolvePath(resolve(parent, '..', specifier) + (trailingSlash ? '/' : ''), parent, job); - } - else if (specifier[0] === '#') { + if ( + isAbsolute(specifier) || + specifier === "." || + specifier === ".." || + specifier.startsWith("./") || + specifier.startsWith("../") + ) { + const trailingSlash = specifier.endsWith("/"); + resolved = await resolvePath( + resolve(parent, "..", specifier) + (trailingSlash ? "/" : ""), + parent, + job + ); + } else if (specifier[0] === "#") { resolved = await packageImportsResolve(specifier, parent, job, cjsResolve); - } - else { + } else { resolved = await resolvePackage(specifier, parent, job, cjsResolve); } if (Array.isArray(resolved)) { - return Promise.all(resolved.map(resolved => job.realpath(resolved, parent))) - } else if (resolved.startsWith('node:')) { + return Promise.all( + resolved.map((resolved) => job.realpath(resolved, parent)) + ); + } else if (resolved.startsWith("node:")) { return resolved; } else { return job.realpath(resolved, parent); } -}; +} -async function resolvePath (path: string, parent: string, job: Job): Promise { - const result = await resolveFile(path, parent, job) || await resolveDir(path, parent, job); +async function resolvePath( + path: string, + parent: string, + job: Job +): Promise { + const result = + (await resolveFile(path, parent, job)) || + (await resolveDir(path, parent, job)); if (!result) { throw new NotFoundError(path, parent); } return result; } -async function resolveFile (path: string, parent: string, job: Job): Promise { - if (path.endsWith('/')) return undefined; +async function resolveFile( + path: string, + parent: string, + job: Job +): Promise { + if (path.endsWith("/")) return undefined; path = await job.realpath(path, parent); if (await job.isFile(path)) return path; - if (job.ts && path.startsWith(job.base) && path.slice(job.base.length).indexOf(sep + 'node_modules' + sep) === -1 && await job.isFile(path + '.ts')) return path + '.ts'; - if (job.ts && path.startsWith(job.base) && path.slice(job.base.length).indexOf(sep + 'node_modules' + sep) === -1 && await job.isFile(path + '.tsx')) return path + '.tsx'; - if (await job.isFile(path + '.js')) return path + '.js'; - if (await job.isFile(path + '.json')) return path + '.json'; - if (await job.isFile(path + '.node')) return path + '.node'; + if ( + job.ts && + path.startsWith(job.base) && + path.slice(job.base.length).indexOf(sep + "node_modules" + sep) === -1 && + (await job.isFile(path + ".ts")) + ) + return path + ".ts"; + if ( + job.ts && + path.startsWith(job.base) && + path.slice(job.base.length).indexOf(sep + "node_modules" + sep) === -1 && + (await job.isFile(path + ".tsx")) + ) + return path + ".tsx"; + if (await job.isFile(path + ".js")) return path + ".js"; + if (await job.isFile(path + ".json")) return path + ".json"; + if (await job.isFile(path + ".node")) return path + ".node"; return undefined; } -async function resolveDir (path: string, parent: string, job: Job) { - if (path.endsWith('/')) path = path.slice(0, -1); - if (!await job.isDir(path)) return; +async function resolveDir(path: string, parent: string, job: Job) { + if (path.endsWith("/")) path = path.slice(0, -1); + if (!(await job.isDir(path))) return; const pkgCfg = await getPkgCfg(path, job); - if (pkgCfg && typeof pkgCfg.main === 'string') { - const resolved = await resolveFile(resolve(path, pkgCfg.main), parent, job) || await resolveFile(resolve(path, pkgCfg.main, 'index'), parent, job); + if (pkgCfg && typeof pkgCfg.main === "string") { + const resolved = + (await resolveFile(resolve(path, pkgCfg.main), parent, job)) || + (await resolveFile(resolve(path, pkgCfg.main, "index"), parent, job)); if (resolved) { - await job.emitFile(path + sep + 'package.json', 'resolve', parent); + await job.emitFile(path + sep + "package.json", "resolve", parent); return resolved; } } - return resolveFile(resolve(path, 'index'), parent, job); + return resolveFile(resolve(path, "index"), parent, job); } export class NotFoundError extends Error { public code: string; constructor(specifier: string, parent: string) { super("Cannot find module '" + specifier + "' loaded from " + parent); - this.code = 'MODULE_NOT_FOUND'; + this.code = "MODULE_NOT_FOUND"; } } const nodeBuiltins = new Set(builtinModules); -function getPkgName (name: string) { - const segments = name.split('/'); - if (name[0] === '@' && segments.length > 1) - return segments.length > 1 ? segments.slice(0, 2).join('/') : null; +function getPkgName(name: string) { + const segments = name.split("/"); + if (name[0] === "@" && segments.length > 1) + return segments.length > 1 ? segments.slice(0, 2).join("/") : null; return segments.length ? segments[0] : null; } -type PackageTarget = string | PackageTarget[] | { [key: string]: PackageTarget } | null; +type PackageTarget = + | string + | PackageTarget[] + | { [key: string]: PackageTarget } + | null; interface PkgCfg { name: string | undefined; @@ -87,40 +130,51 @@ interface PkgCfg { imports: { [key: string]: PackageTarget }; } -async function getPkgCfg (pkgPath: string, job: Job): Promise { - const pjsonSource = await job.readFile(pkgPath + sep + 'package.json'); +async function getPkgCfg( + pkgPath: string, + job: Job +): Promise { + const pjsonSource = await job.readFile(pkgPath + sep + "package.json"); if (pjsonSource) { try { return JSON.parse(pjsonSource.toString()); - } - catch (e) {} + } catch (e) {} } return undefined; } -function getExportsTarget(exports: PackageTarget, conditions: string[], cjsResolve: boolean): string | null | undefined { - if (typeof exports === 'string') { +function getExportsTarget( + exports: PackageTarget, + conditions: string[], + cjsResolve: boolean +): string | null | undefined { + if (typeof exports === "string") { return exports; - } - else if (exports === null) { + } else if (exports === null) { return exports; - } - else if (Array.isArray(exports)) { + } else if (Array.isArray(exports)) { for (const item of exports) { const target = getExportsTarget(item, conditions, cjsResolve); - if (target === null || typeof target === 'string' && target.startsWith('./')) + if ( + target === null || + (typeof target === "string" && target.startsWith("./")) + ) return target; } - } - else if (typeof exports === 'object') { + } else if (typeof exports === "object") { for (const condition of Object.keys(exports)) { - if (condition === 'default' || - condition === 'require' && cjsResolve || - condition === 'import' && !cjsResolve || - conditions.includes(condition)) { - const target = getExportsTarget(exports[condition], conditions, cjsResolve); - if (target !== undefined) - return target; + if ( + condition === "default" || + (condition === "require" && cjsResolve) || + (condition === "import" && !cjsResolve) || + conditions.includes(condition) + ) { + const target = getExportsTarget( + exports[condition], + conditions, + cjsResolve + ); + if (target !== undefined) return target; } } } @@ -128,56 +182,161 @@ function getExportsTarget(exports: PackageTarget, conditions: string[], cjsResol return undefined; } -function resolveExportsImports (pkgPath: string, obj: PackageTarget, subpath: string, job: Job, isImports: boolean, cjsResolve: boolean): string | undefined { +function patternKeyCompare(a: string, b: string) { + const aPatternIndex = a.indexOf("*"); + const bPatternIndex = b.indexOf("*"); + const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; + const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; + if (baseLenA > baseLenB) return -1; + if (baseLenB > baseLenA) return 1; + if (aPatternIndex === -1) return 1; + if (bPatternIndex === -1) return -1; + if (a.length > b.length) return -1; + if (b.length > a.length) return 1; + return 0; +} + +function resolveExportsImports( + pkgPath: string, + obj: PackageTarget, + subpath: string, + job: Job, + isImports: boolean, + cjsResolve: boolean +): string | undefined { let matchObj: { [key: string]: PackageTarget }; if (isImports) { - if (!(typeof obj === 'object' && !Array.isArray(obj) && obj !== null)) + if (!(typeof obj === "object" && !Array.isArray(obj) && obj !== null)) return undefined; matchObj = obj; - } else if (typeof obj === 'string' || Array.isArray(obj) || obj === null || - typeof obj === 'object' && Object.keys(obj).length && Object.keys(obj)[0][0] !== '.') { - matchObj = { '.' : obj }; + } else if ( + typeof obj === "string" || + Array.isArray(obj) || + obj === null || + (typeof obj === "object" && + Object.keys(obj).length && + Object.keys(obj)[0][0] !== ".") + ) { + matchObj = { ".": obj }; } else { matchObj = obj; } if (subpath in matchObj) { - const target = getExportsTarget(matchObj[subpath], job.conditions, cjsResolve); - if (typeof target === 'string' && target.startsWith('./')) + const target = getExportsTarget( + matchObj[subpath], + job.conditions, + cjsResolve + ); + if (typeof target === "string" && target.startsWith("./")) return pkgPath + target.slice(1); } - for (const match of Object.keys(matchObj).sort((a, b) => b.length - a.length)) { - if (match.endsWith('*') && subpath.startsWith(match.slice(0, -1))) { - const target = getExportsTarget(matchObj[match], job.conditions, cjsResolve); - if (typeof target === 'string' && target.startsWith('./')) - return pkgPath + target.slice(1).replace(/\*/g, subpath.slice(match.length - 1)); + + let bestMatch = ""; + let bestMatchSubpath; + + for (const match of Object.keys(matchObj)) { + const patternIndex = match.indexOf("*"); + if ( + patternIndex !== -1 && + subpath.startsWith(match.slice(0, patternIndex)) + ) { + const patternTrailer = match.slice(patternIndex + 1); + if ( + subpath.length >= match.length && + subpath.endsWith(patternTrailer) && + patternKeyCompare(bestMatch, match) === 1 && + match.lastIndexOf("*") === patternIndex + ) { + bestMatch = match; + bestMatchSubpath = subpath.slice( + patternIndex, + subpath.length - patternTrailer.length + ); + } + } else if ( + match[match.length - 1] === "/" && + subpath.startsWith(match) && + patternKeyCompare(bestMatch, match) === 1 + ) { + bestMatch = match; + bestMatchSubpath = subpath.slice(match.length); + } + } + + if (bestMatch) { + const target = getExportsTarget( + matchObj[bestMatch], + job.conditions, + cjsResolve + ); + if (typeof target === "string" && target.startsWith("./")) { + const patternIndex = target.indexOf("*"); + if (patternIndex !== -1 && target.lastIndexOf("*") === patternIndex) { + return ( + target.substring(0, patternIndex) + + bestMatchSubpath + + target.substring(patternIndex + 1) + ); + } + return target; } - if (!match.endsWith('/')) - continue; + } + + for (const match of Object.keys(matchObj).sort( + (a, b) => b.length - a.length + )) { + if (!match.endsWith("/")) continue; if (subpath.startsWith(match)) { - const target = getExportsTarget(matchObj[match], job.conditions, cjsResolve); - if (typeof target === 'string' && target.endsWith('/') && target.startsWith('./')) + const target = getExportsTarget( + matchObj[match], + job.conditions, + cjsResolve + ); + if ( + typeof target === "string" && + target.endsWith("/") && + target.startsWith("./") + ) return pkgPath + target.slice(1) + subpath.slice(match.length); } } return undefined; } -async function packageImportsResolve (name: string, parent: string, job: Job, cjsResolve: boolean): Promise { - if (name !== '#' && !name.startsWith('#/') && job.conditions) { +async function packageImportsResolve( + name: string, + parent: string, + job: Job, + cjsResolve: boolean +): Promise { + if (name !== "#" && !name.startsWith("#/") && job.conditions) { const pjsonBoundary = await job.getPjsonBoundary(parent); if (pjsonBoundary) { const pkgCfg = await getPkgCfg(pjsonBoundary, job); const { imports: pkgImports } = pkgCfg || {}; if (pkgCfg && pkgImports !== null && pkgImports !== undefined) { - let importsResolved = resolveExportsImports(pjsonBoundary, pkgImports, name, job, true, cjsResolve); + let importsResolved = resolveExportsImports( + pjsonBoundary, + pkgImports, + name, + job, + true, + cjsResolve + ); if (importsResolved) { if (cjsResolve) - importsResolved = await resolveFile(importsResolved, parent, job) || await resolveDir(importsResolved, parent, job); - else if (!await job.isFile(importsResolved)) + importsResolved = + (await resolveFile(importsResolved, parent, job)) || + (await resolveDir(importsResolved, parent, job)); + else if (!(await job.isFile(importsResolved))) throw new NotFoundError(importsResolved, parent); if (importsResolved) { - await job.emitFile(pjsonBoundary + sep + 'package.json', 'resolve', parent); + await job.emitFile( + pjsonBoundary + sep + "package.json", + "resolve", + parent + ); return importsResolved; } } @@ -187,12 +346,17 @@ async function packageImportsResolve (name: string, parent: string, job: Job, cj throw new NotFoundError(name, parent); } -async function resolvePackage (name: string, parent: string, job: Job, cjsResolve: boolean): Promise { +async function resolvePackage( + name: string, + parent: string, + job: Job, + cjsResolve: boolean +): Promise { let packageParent = parent; - if (nodeBuiltins.has(name)) return 'node:' + name; - if (name.startsWith('node:')) return name; + if (nodeBuiltins.has(name)) return "node:" + name; + if (name.startsWith("node:")) return name; - const pkgName = getPkgName(name) || ''; + const pkgName = getPkgName(name) || ""; // package own name resolution let selfResolved: string | undefined; @@ -201,51 +365,92 @@ async function resolvePackage (name: string, parent: string, job: Job, cjsResolv if (pjsonBoundary) { const pkgCfg = await getPkgCfg(pjsonBoundary, job); const { exports: pkgExports } = pkgCfg || {}; - if (pkgCfg && pkgCfg.name && pkgCfg.name === pkgName && pkgExports !== null && pkgExports !== undefined) { - selfResolved = resolveExportsImports(pjsonBoundary, pkgExports, '.' + name.slice(pkgName.length), job, false, cjsResolve); + if ( + pkgCfg && + pkgCfg.name && + pkgCfg.name === pkgName && + pkgExports !== null && + pkgExports !== undefined + ) { + selfResolved = resolveExportsImports( + pjsonBoundary, + pkgExports, + "." + name.slice(pkgName.length), + job, + false, + cjsResolve + ); if (selfResolved) { if (cjsResolve) - selfResolved = await resolveFile(selfResolved, parent, job) || await resolveDir(selfResolved, parent, job); - else if (!await job.isFile(selfResolved)) + selfResolved = + (await resolveFile(selfResolved, parent, job)) || + (await resolveDir(selfResolved, parent, job)); + else if (!(await job.isFile(selfResolved))) throw new NotFoundError(selfResolved, parent); } if (selfResolved) - await job.emitFile(pjsonBoundary + sep + 'package.json', 'resolve', parent); + await job.emitFile( + pjsonBoundary + sep + "package.json", + "resolve", + parent + ); } } } let separatorIndex: number; const rootSeparatorIndex = packageParent.indexOf(sep); - while ((separatorIndex = packageParent.lastIndexOf(sep)) > rootSeparatorIndex) { + while ( + (separatorIndex = packageParent.lastIndexOf(sep)) > rootSeparatorIndex + ) { packageParent = packageParent.slice(0, separatorIndex); - const nodeModulesDir = packageParent + sep + 'node_modules'; + const nodeModulesDir = packageParent + sep + "node_modules"; const stat = await job.stat(nodeModulesDir); if (!stat || !stat.isDirectory()) continue; const pkgCfg = await getPkgCfg(nodeModulesDir + sep + pkgName, job); const { exports: pkgExports } = pkgCfg || {}; - if (job.conditions && pkgExports !== undefined && pkgExports !== null && !selfResolved) { + if ( + job.conditions && + pkgExports !== undefined && + pkgExports !== null && + !selfResolved + ) { let legacyResolved; if (!job.exportsOnly) - legacyResolved = await resolveFile(nodeModulesDir + sep + name, parent, job) || await resolveDir(nodeModulesDir + sep + name, parent, job); - let resolved = resolveExportsImports(nodeModulesDir + sep + pkgName, pkgExports, '.' + name.slice(pkgName.length), job, false, cjsResolve); + legacyResolved = + (await resolveFile(nodeModulesDir + sep + name, parent, job)) || + (await resolveDir(nodeModulesDir + sep + name, parent, job)); + let resolved = resolveExportsImports( + nodeModulesDir + sep + pkgName, + pkgExports, + "." + name.slice(pkgName.length), + job, + false, + cjsResolve + ); if (resolved) { if (cjsResolve) - resolved = await resolveFile(resolved, parent, job) || await resolveDir(resolved, parent, job); - else if (!await job.isFile(resolved)) + resolved = + (await resolveFile(resolved, parent, job)) || + (await resolveDir(resolved, parent, job)); + else if (!(await job.isFile(resolved))) throw new NotFoundError(resolved, parent); } if (resolved) { - await job.emitFile(nodeModulesDir + sep + pkgName + sep + 'package.json', 'resolve', parent); + await job.emitFile( + nodeModulesDir + sep + pkgName + sep + "package.json", + "resolve", + parent + ); if (legacyResolved && legacyResolved !== resolved) return [resolved, legacyResolved]; return resolved; } - if (legacyResolved) - return legacyResolved; - } - else { - const resolved = await resolveFile(nodeModulesDir + sep + name, parent, job) || await resolveDir(nodeModulesDir + sep + name, parent, job); + if (legacyResolved) return legacyResolved; + } else { + const resolved = + (await resolveFile(nodeModulesDir + sep + name, parent, job)) || + (await resolveDir(nodeModulesDir + sep + name, parent, job)); if (resolved) { if (selfResolved && selfResolved !== resolved) return [resolved, selfResolved]; @@ -258,9 +463,11 @@ async function resolvePackage (name: string, parent: string, job: Job, cjsResolv return job.paths[name]; } for (const path of Object.keys(job.paths)) { - if (path.endsWith('/') && name.startsWith(path)) { + if (path.endsWith("/") && name.startsWith(path)) { const pathTarget = job.paths[path] + name.slice(path.length); - const resolved = await resolveFile(pathTarget, parent, job) || await resolveDir(pathTarget, parent, job); + const resolved = + (await resolveFile(pathTarget, parent, job)) || + (await resolveDir(pathTarget, parent, job)); if (!resolved) { throw new NotFoundError(name, parent); } From b3917e3fd7cdc99a185c78e3c0954b2c5464493b Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 8 Sep 2023 21:59:38 -0500 Subject: [PATCH 2/4] fix pattern trailer matching and add test --- src/resolve-dependency.ts | 3 ++- test/unit/exports-wildcard-trailer/f.js | 1 + test/unit/exports-wildcard-trailer/foo.js | 1 + test/unit/exports-wildcard-trailer/foobar.js | 1 + test/unit/exports-wildcard-trailer/input.js | 2 ++ test/unit/exports-wildcard-trailer/output.js | 5 ++++ .../exports-wildcard-trailer/package.json | 24 +++++++++++++++++++ 7 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 test/unit/exports-wildcard-trailer/f.js create mode 100644 test/unit/exports-wildcard-trailer/foo.js create mode 100644 test/unit/exports-wildcard-trailer/foobar.js create mode 100644 test/unit/exports-wildcard-trailer/input.js create mode 100644 test/unit/exports-wildcard-trailer/output.js create mode 100644 test/unit/exports-wildcard-trailer/package.json diff --git a/src/resolve-dependency.ts b/src/resolve-dependency.ts index 7c6c303f..fafd37be 100644 --- a/src/resolve-dependency.ts +++ b/src/resolve-dependency.ts @@ -274,7 +274,8 @@ function resolveExportsImports( const patternIndex = target.indexOf("*"); if (patternIndex !== -1 && target.lastIndexOf("*") === patternIndex) { return ( - target.substring(0, patternIndex) + + pkgPath + + target.substring(1, patternIndex) + bestMatchSubpath + target.substring(patternIndex + 1) ); diff --git a/test/unit/exports-wildcard-trailer/f.js b/test/unit/exports-wildcard-trailer/f.js new file mode 100644 index 00000000..5039a381 --- /dev/null +++ b/test/unit/exports-wildcard-trailer/f.js @@ -0,0 +1 @@ +export var x = 'x'; diff --git a/test/unit/exports-wildcard-trailer/foo.js b/test/unit/exports-wildcard-trailer/foo.js new file mode 100644 index 00000000..5039a381 --- /dev/null +++ b/test/unit/exports-wildcard-trailer/foo.js @@ -0,0 +1 @@ +export var x = 'x'; diff --git a/test/unit/exports-wildcard-trailer/foobar.js b/test/unit/exports-wildcard-trailer/foobar.js new file mode 100644 index 00000000..9d1b9688 --- /dev/null +++ b/test/unit/exports-wildcard-trailer/foobar.js @@ -0,0 +1 @@ +export var y = 'y'; diff --git a/test/unit/exports-wildcard-trailer/input.js b/test/unit/exports-wildcard-trailer/input.js new file mode 100644 index 00000000..6b4e2021 --- /dev/null +++ b/test/unit/exports-wildcard-trailer/input.js @@ -0,0 +1,2 @@ +import { y } from "y/foob.js"; +console.log(y); diff --git a/test/unit/exports-wildcard-trailer/output.js b/test/unit/exports-wildcard-trailer/output.js new file mode 100644 index 00000000..a8a31ac1 --- /dev/null +++ b/test/unit/exports-wildcard-trailer/output.js @@ -0,0 +1,5 @@ +[ + "test/unit/exports-wildcard-trailer/foobar.js", + "test/unit/exports-wildcard-trailer/input.js", + "test/unit/exports-wildcard-trailer/package.json" +] diff --git a/test/unit/exports-wildcard-trailer/package.json b/test/unit/exports-wildcard-trailer/package.json new file mode 100644 index 00000000..874656e6 --- /dev/null +++ b/test/unit/exports-wildcard-trailer/package.json @@ -0,0 +1,24 @@ +{ + "name": "y", + "type": "module", + "exports": { + "./*.js": { + "module": "./*.js", + "default": { + "node": "./*b.js" + } + }, + "./foo*.js": { + "module": "./foo*.js", + "default": { + "node": "./foo*ar.js" + } + }, + "./fo*.js": { + "module": "./fo*.js", + "default": { + "node": "./fo*ba.js" + } + } + } +} From 556fa63e604d650b7c0758c46115228f26022521 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 8 Sep 2023 22:09:29 -0500 Subject: [PATCH 3/4] update logic from node's resolve implementation --- src/resolve-dependency.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve-dependency.ts b/src/resolve-dependency.ts index fafd37be..204d28c4 100644 --- a/src/resolve-dependency.ts +++ b/src/resolve-dependency.ts @@ -222,7 +222,7 @@ function resolveExportsImports( matchObj = obj; } - if (subpath in matchObj) { + if (subpath in matchObj && !subpath.includes('*') && !subpath.endsWith('/')) { const target = getExportsTarget( matchObj[subpath], job.conditions, From 2ad1c3d93731e65803fb5f249cc563baf929f97d Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 8 Sep 2023 22:20:00 -0500 Subject: [PATCH 4/4] revert accidental format changes --- src/resolve-dependency.ts | 345 +++++++++++--------------------------- 1 file changed, 100 insertions(+), 245 deletions(-) diff --git a/src/resolve-dependency.ts b/src/resolve-dependency.ts index 204d28c4..9cfc6062 100644 --- a/src/resolve-dependency.ts +++ b/src/resolve-dependency.ts @@ -1,127 +1,84 @@ -import { isAbsolute, resolve, sep } from "path"; -import { builtinModules } from "module"; -import { Job } from "./node-file-trace"; +import { isAbsolute, resolve, sep } from 'path'; +import { builtinModules } from 'module'; +import { Job } from './node-file-trace'; // node resolver // custom implementation to emit only needed package.json files for resolver // (package.json files are emitted as they are hit) -export default async function resolveDependency( - specifier: string, - parent: string, - job: Job, - cjsResolve = true -): Promise { +export default async function resolveDependency (specifier: string, parent: string, job: Job, cjsResolve = true): Promise { let resolved: string | string[]; - if ( - isAbsolute(specifier) || - specifier === "." || - specifier === ".." || - specifier.startsWith("./") || - specifier.startsWith("../") - ) { - const trailingSlash = specifier.endsWith("/"); - resolved = await resolvePath( - resolve(parent, "..", specifier) + (trailingSlash ? "/" : ""), - parent, - job - ); - } else if (specifier[0] === "#") { + if (isAbsolute(specifier) || specifier === '.' || specifier === '..' || specifier.startsWith('./') || specifier.startsWith('../')) { + const trailingSlash = specifier.endsWith('/'); + resolved = await resolvePath(resolve(parent, '..', specifier) + (trailingSlash ? '/' : ''), parent, job); + } + else if (specifier[0] === '#') { resolved = await packageImportsResolve(specifier, parent, job, cjsResolve); - } else { + } + else { resolved = await resolvePackage(specifier, parent, job, cjsResolve); } if (Array.isArray(resolved)) { - return Promise.all( - resolved.map((resolved) => job.realpath(resolved, parent)) - ); - } else if (resolved.startsWith("node:")) { + return Promise.all(resolved.map(resolved => job.realpath(resolved, parent))) + } else if (resolved.startsWith('node:')) { return resolved; } else { return job.realpath(resolved, parent); } -} +}; -async function resolvePath( - path: string, - parent: string, - job: Job -): Promise { - const result = - (await resolveFile(path, parent, job)) || - (await resolveDir(path, parent, job)); +async function resolvePath (path: string, parent: string, job: Job): Promise { + const result = await resolveFile(path, parent, job) || await resolveDir(path, parent, job); if (!result) { throw new NotFoundError(path, parent); } return result; } -async function resolveFile( - path: string, - parent: string, - job: Job -): Promise { - if (path.endsWith("/")) return undefined; +async function resolveFile (path: string, parent: string, job: Job): Promise { + if (path.endsWith('/')) return undefined; path = await job.realpath(path, parent); if (await job.isFile(path)) return path; - if ( - job.ts && - path.startsWith(job.base) && - path.slice(job.base.length).indexOf(sep + "node_modules" + sep) === -1 && - (await job.isFile(path + ".ts")) - ) - return path + ".ts"; - if ( - job.ts && - path.startsWith(job.base) && - path.slice(job.base.length).indexOf(sep + "node_modules" + sep) === -1 && - (await job.isFile(path + ".tsx")) - ) - return path + ".tsx"; - if (await job.isFile(path + ".js")) return path + ".js"; - if (await job.isFile(path + ".json")) return path + ".json"; - if (await job.isFile(path + ".node")) return path + ".node"; + if (job.ts && path.startsWith(job.base) && path.slice(job.base.length).indexOf(sep + 'node_modules' + sep) === -1 && await job.isFile(path + '.ts')) return path + '.ts'; + if (job.ts && path.startsWith(job.base) && path.slice(job.base.length).indexOf(sep + 'node_modules' + sep) === -1 && await job.isFile(path + '.tsx')) return path + '.tsx'; + if (await job.isFile(path + '.js')) return path + '.js'; + if (await job.isFile(path + '.json')) return path + '.json'; + if (await job.isFile(path + '.node')) return path + '.node'; return undefined; } -async function resolveDir(path: string, parent: string, job: Job) { - if (path.endsWith("/")) path = path.slice(0, -1); - if (!(await job.isDir(path))) return; +async function resolveDir (path: string, parent: string, job: Job) { + if (path.endsWith('/')) path = path.slice(0, -1); + if (!await job.isDir(path)) return; const pkgCfg = await getPkgCfg(path, job); - if (pkgCfg && typeof pkgCfg.main === "string") { - const resolved = - (await resolveFile(resolve(path, pkgCfg.main), parent, job)) || - (await resolveFile(resolve(path, pkgCfg.main, "index"), parent, job)); + if (pkgCfg && typeof pkgCfg.main === 'string') { + const resolved = await resolveFile(resolve(path, pkgCfg.main), parent, job) || await resolveFile(resolve(path, pkgCfg.main, 'index'), parent, job); if (resolved) { - await job.emitFile(path + sep + "package.json", "resolve", parent); + await job.emitFile(path + sep + 'package.json', 'resolve', parent); return resolved; } } - return resolveFile(resolve(path, "index"), parent, job); + return resolveFile(resolve(path, 'index'), parent, job); } export class NotFoundError extends Error { public code: string; constructor(specifier: string, parent: string) { super("Cannot find module '" + specifier + "' loaded from " + parent); - this.code = "MODULE_NOT_FOUND"; + this.code = 'MODULE_NOT_FOUND'; } } const nodeBuiltins = new Set(builtinModules); -function getPkgName(name: string) { - const segments = name.split("/"); - if (name[0] === "@" && segments.length > 1) - return segments.length > 1 ? segments.slice(0, 2).join("/") : null; +function getPkgName (name: string) { + const segments = name.split('/'); + if (name[0] === '@' && segments.length > 1) + return segments.length > 1 ? segments.slice(0, 2).join('/') : null; return segments.length ? segments[0] : null; } -type PackageTarget = - | string - | PackageTarget[] - | { [key: string]: PackageTarget } - | null; +type PackageTarget = string | PackageTarget[] | { [key: string]: PackageTarget } | null; interface PkgCfg { name: string | undefined; @@ -130,51 +87,40 @@ interface PkgCfg { imports: { [key: string]: PackageTarget }; } -async function getPkgCfg( - pkgPath: string, - job: Job -): Promise { - const pjsonSource = await job.readFile(pkgPath + sep + "package.json"); +async function getPkgCfg (pkgPath: string, job: Job): Promise { + const pjsonSource = await job.readFile(pkgPath + sep + 'package.json'); if (pjsonSource) { try { return JSON.parse(pjsonSource.toString()); - } catch (e) {} + } + catch (e) {} } return undefined; } -function getExportsTarget( - exports: PackageTarget, - conditions: string[], - cjsResolve: boolean -): string | null | undefined { - if (typeof exports === "string") { +function getExportsTarget(exports: PackageTarget, conditions: string[], cjsResolve: boolean): string | null | undefined { + if (typeof exports === 'string') { return exports; - } else if (exports === null) { + } + else if (exports === null) { return exports; - } else if (Array.isArray(exports)) { + } + else if (Array.isArray(exports)) { for (const item of exports) { const target = getExportsTarget(item, conditions, cjsResolve); - if ( - target === null || - (typeof target === "string" && target.startsWith("./")) - ) + if (target === null || typeof target === 'string' && target.startsWith('./')) return target; } - } else if (typeof exports === "object") { + } + else if (typeof exports === 'object') { for (const condition of Object.keys(exports)) { - if ( - condition === "default" || - (condition === "require" && cjsResolve) || - (condition === "import" && !cjsResolve) || - conditions.includes(condition) - ) { - const target = getExportsTarget( - exports[condition], - conditions, - cjsResolve - ); - if (target !== undefined) return target; + if (condition === 'default' || + condition === 'require' && cjsResolve || + condition === 'import' && !cjsResolve || + conditions.includes(condition)) { + const target = getExportsTarget(exports[condition], conditions, cjsResolve); + if (target !== undefined) + return target; } } } @@ -196,42 +142,26 @@ function patternKeyCompare(a: string, b: string) { return 0; } -function resolveExportsImports( - pkgPath: string, - obj: PackageTarget, - subpath: string, - job: Job, - isImports: boolean, - cjsResolve: boolean -): string | undefined { +function resolveExportsImports (pkgPath: string, obj: PackageTarget, subpath: string, job: Job, isImports: boolean, cjsResolve: boolean): string | undefined { let matchObj: { [key: string]: PackageTarget }; if (isImports) { - if (!(typeof obj === "object" && !Array.isArray(obj) && obj !== null)) + if (!(typeof obj === 'object' && !Array.isArray(obj) && obj !== null)) return undefined; matchObj = obj; - } else if ( - typeof obj === "string" || - Array.isArray(obj) || - obj === null || - (typeof obj === "object" && - Object.keys(obj).length && - Object.keys(obj)[0][0] !== ".") - ) { - matchObj = { ".": obj }; + } else if (typeof obj === 'string' || Array.isArray(obj) || obj === null || + typeof obj === 'object' && Object.keys(obj).length && Object.keys(obj)[0][0] !== '.') { + matchObj = { '.' : obj }; } else { matchObj = obj; } - if (subpath in matchObj && !subpath.includes('*') && !subpath.endsWith('/')) { - const target = getExportsTarget( - matchObj[subpath], - job.conditions, - cjsResolve - ); - if (typeof target === "string" && target.startsWith("./")) + if (subpath in matchObj && !subpath.includes('*')) { + const target = getExportsTarget(matchObj[subpath], job.conditions, cjsResolve); + if (typeof target === 'string' && target.startsWith('./')) return pkgPath + target.slice(1); } + let bestMatch = ""; let bestMatchSubpath; @@ -284,60 +214,33 @@ function resolveExportsImports( } } - for (const match of Object.keys(matchObj).sort( - (a, b) => b.length - a.length - )) { - if (!match.endsWith("/")) continue; + for (const match of Object.keys(matchObj).sort((a, b) => b.length - a.length)) { + if (!match.endsWith('/')) + continue; if (subpath.startsWith(match)) { - const target = getExportsTarget( - matchObj[match], - job.conditions, - cjsResolve - ); - if ( - typeof target === "string" && - target.endsWith("/") && - target.startsWith("./") - ) + const target = getExportsTarget(matchObj[match], job.conditions, cjsResolve); + if (typeof target === 'string' && target.endsWith('/') && target.startsWith('./')) return pkgPath + target.slice(1) + subpath.slice(match.length); } } return undefined; } -async function packageImportsResolve( - name: string, - parent: string, - job: Job, - cjsResolve: boolean -): Promise { - if (name !== "#" && !name.startsWith("#/") && job.conditions) { +async function packageImportsResolve (name: string, parent: string, job: Job, cjsResolve: boolean): Promise { + if (name !== '#' && !name.startsWith('#/') && job.conditions) { const pjsonBoundary = await job.getPjsonBoundary(parent); if (pjsonBoundary) { const pkgCfg = await getPkgCfg(pjsonBoundary, job); const { imports: pkgImports } = pkgCfg || {}; if (pkgCfg && pkgImports !== null && pkgImports !== undefined) { - let importsResolved = resolveExportsImports( - pjsonBoundary, - pkgImports, - name, - job, - true, - cjsResolve - ); + let importsResolved = resolveExportsImports(pjsonBoundary, pkgImports, name, job, true, cjsResolve); if (importsResolved) { if (cjsResolve) - importsResolved = - (await resolveFile(importsResolved, parent, job)) || - (await resolveDir(importsResolved, parent, job)); - else if (!(await job.isFile(importsResolved))) + importsResolved = await resolveFile(importsResolved, parent, job) || await resolveDir(importsResolved, parent, job); + else if (!await job.isFile(importsResolved)) throw new NotFoundError(importsResolved, parent); if (importsResolved) { - await job.emitFile( - pjsonBoundary + sep + "package.json", - "resolve", - parent - ); + await job.emitFile(pjsonBoundary + sep + 'package.json', 'resolve', parent); return importsResolved; } } @@ -347,17 +250,12 @@ async function packageImportsResolve( throw new NotFoundError(name, parent); } -async function resolvePackage( - name: string, - parent: string, - job: Job, - cjsResolve: boolean -): Promise { +async function resolvePackage (name: string, parent: string, job: Job, cjsResolve: boolean): Promise { let packageParent = parent; - if (nodeBuiltins.has(name)) return "node:" + name; - if (name.startsWith("node:")) return name; + if (nodeBuiltins.has(name)) return 'node:' + name; + if (name.startsWith('node:')) return name; - const pkgName = getPkgName(name) || ""; + const pkgName = getPkgName(name) || ''; // package own name resolution let selfResolved: string | undefined; @@ -366,92 +264,51 @@ async function resolvePackage( if (pjsonBoundary) { const pkgCfg = await getPkgCfg(pjsonBoundary, job); const { exports: pkgExports } = pkgCfg || {}; - if ( - pkgCfg && - pkgCfg.name && - pkgCfg.name === pkgName && - pkgExports !== null && - pkgExports !== undefined - ) { - selfResolved = resolveExportsImports( - pjsonBoundary, - pkgExports, - "." + name.slice(pkgName.length), - job, - false, - cjsResolve - ); + if (pkgCfg && pkgCfg.name && pkgCfg.name === pkgName && pkgExports !== null && pkgExports !== undefined) { + selfResolved = resolveExportsImports(pjsonBoundary, pkgExports, '.' + name.slice(pkgName.length), job, false, cjsResolve); if (selfResolved) { if (cjsResolve) - selfResolved = - (await resolveFile(selfResolved, parent, job)) || - (await resolveDir(selfResolved, parent, job)); - else if (!(await job.isFile(selfResolved))) + selfResolved = await resolveFile(selfResolved, parent, job) || await resolveDir(selfResolved, parent, job); + else if (!await job.isFile(selfResolved)) throw new NotFoundError(selfResolved, parent); } if (selfResolved) - await job.emitFile( - pjsonBoundary + sep + "package.json", - "resolve", - parent - ); + await job.emitFile(pjsonBoundary + sep + 'package.json', 'resolve', parent); } } } let separatorIndex: number; const rootSeparatorIndex = packageParent.indexOf(sep); - while ( - (separatorIndex = packageParent.lastIndexOf(sep)) > rootSeparatorIndex - ) { + while ((separatorIndex = packageParent.lastIndexOf(sep)) > rootSeparatorIndex) { packageParent = packageParent.slice(0, separatorIndex); - const nodeModulesDir = packageParent + sep + "node_modules"; + const nodeModulesDir = packageParent + sep + 'node_modules'; const stat = await job.stat(nodeModulesDir); if (!stat || !stat.isDirectory()) continue; const pkgCfg = await getPkgCfg(nodeModulesDir + sep + pkgName, job); const { exports: pkgExports } = pkgCfg || {}; - if ( - job.conditions && - pkgExports !== undefined && - pkgExports !== null && - !selfResolved - ) { + if (job.conditions && pkgExports !== undefined && pkgExports !== null && !selfResolved) { let legacyResolved; if (!job.exportsOnly) - legacyResolved = - (await resolveFile(nodeModulesDir + sep + name, parent, job)) || - (await resolveDir(nodeModulesDir + sep + name, parent, job)); - let resolved = resolveExportsImports( - nodeModulesDir + sep + pkgName, - pkgExports, - "." + name.slice(pkgName.length), - job, - false, - cjsResolve - ); + legacyResolved = await resolveFile(nodeModulesDir + sep + name, parent, job) || await resolveDir(nodeModulesDir + sep + name, parent, job); + let resolved = resolveExportsImports(nodeModulesDir + sep + pkgName, pkgExports, '.' + name.slice(pkgName.length), job, false, cjsResolve); if (resolved) { if (cjsResolve) - resolved = - (await resolveFile(resolved, parent, job)) || - (await resolveDir(resolved, parent, job)); - else if (!(await job.isFile(resolved))) + resolved = await resolveFile(resolved, parent, job) || await resolveDir(resolved, parent, job); + else if (!await job.isFile(resolved)) throw new NotFoundError(resolved, parent); } if (resolved) { - await job.emitFile( - nodeModulesDir + sep + pkgName + sep + "package.json", - "resolve", - parent - ); + await job.emitFile(nodeModulesDir + sep + pkgName + sep + 'package.json', 'resolve', parent); if (legacyResolved && legacyResolved !== resolved) return [resolved, legacyResolved]; return resolved; } - if (legacyResolved) return legacyResolved; - } else { - const resolved = - (await resolveFile(nodeModulesDir + sep + name, parent, job)) || - (await resolveDir(nodeModulesDir + sep + name, parent, job)); + if (legacyResolved) + return legacyResolved; + } + else { + const resolved = await resolveFile(nodeModulesDir + sep + name, parent, job) || await resolveDir(nodeModulesDir + sep + name, parent, job); if (resolved) { if (selfResolved && selfResolved !== resolved) return [resolved, selfResolved]; @@ -464,11 +321,9 @@ async function resolvePackage( return job.paths[name]; } for (const path of Object.keys(job.paths)) { - if (path.endsWith("/") && name.startsWith(path)) { + if (path.endsWith('/') && name.startsWith(path)) { const pathTarget = job.paths[path] + name.slice(path.length); - const resolved = - (await resolveFile(pathTarget, parent, job)) || - (await resolveDir(pathTarget, parent, job)); + const resolved = await resolveFile(pathTarget, parent, job) || await resolveDir(pathTarget, parent, job); if (!resolved) { throw new NotFoundError(name, parent); }