Skip to content

Commit d31e359

Browse files
committed
Reinstated existsSync approach and added suggested improvements
1 parent 6e3fe6a commit d31e359

File tree

2 files changed

+65
-33
lines changed

2 files changed

+65
-33
lines changed

lib/get-exports.js

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const getEsmExports = require('./get-esm-exports.js')
44
const { parse: parseCjs } = require('cjs-module-lexer')
5-
const { readFileSync } = require('fs')
5+
const { readFileSync, existsSync } = require('fs')
66
const { builtinModules } = require('module')
77
const { fileURLToPath, pathToFileURL } = require('url')
88
const { dirname, join } = require('path')
@@ -32,13 +32,9 @@ const urlsBeingProcessed = new Set() // Guard against circular imports.
3232
* Once the package.json file has been found, we extract the file path from the specifier
3333
* @param {string} specifier The specifier that is being search for inside the imports object
3434
* @param {URL|string} fromUrl The url from which the search starts from
35-
* @returns file to url to file export
35+
* @returns object with url and resolvedExport
3636
*/
3737
function resolvePackageImports (specifier, fromUrl) {
38-
if (!specifier.startsWith('#')) {
39-
return null
40-
}
41-
4238
try {
4339
const fromPath = fileURLToPath(fromUrl)
4440
let currentDir = dirname(fromPath)
@@ -47,47 +43,42 @@ function resolvePackageImports (specifier, fromUrl) {
4743
while (currentDir !== dirname(currentDir)) {
4844
const packageJsonPath = join(currentDir, 'package.json')
4945

50-
let packageJson
51-
try {
52-
packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
53-
} catch (error) {
54-
if (error.code === 'ENOENT') {
55-
currentDir = dirname(currentDir)
56-
continue
57-
}
58-
throw error
59-
}
60-
61-
if (packageJson) {
46+
if (existsSync(packageJsonPath)) {
47+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
6248
if (packageJson.imports && packageJson.imports[specifier]) {
6349
const imports = packageJson.imports[specifier]
6450

6551
// Look for path inside packageJson
66-
let resolvedPath
52+
let resolvedExport
6753
if (Object.prototype.toString.call(imports) === '[object Object]') {
68-
const requireSpecifier = imports.require
69-
const importSpecifier = imports.import
54+
const requireExport = imports.require
55+
const importExport = imports.import
7056
// look for the possibility of require and import which is standard for CJS/ESM
71-
if (requireSpecifier || importSpecifier) {
57+
if (requireExport || importExport) {
7258
// trying to resolve based on order of importance
73-
resolvedPath = requireSpecifier.node || requireSpecifier.default || importSpecifier.node || importSpecifier.default
59+
resolvedExport = requireExport.node || requireExport.default || importExport.node || importExport.default
7460
} else if (imports.node || imports.default) {
75-
resolvedPath = imports.node || imports.default
61+
resolvedExport = imports.node || imports.default
7662
}
7763
} else if (typeof imports === 'string') {
78-
resolvedPath = imports
64+
resolvedExport = imports
7965
}
8066

81-
if (resolvedPath) {
82-
return pathToFileURL(require.resolve(resolvedPath, { paths: [currentDir] })).href
67+
if (resolvedExport) {
68+
const url = resolvedExport.startsWith('.')
69+
? pathToFileURL(join(currentDir, resolvedExport))
70+
: fromUrl
71+
return { url, export: resolvedExport }
8372
}
8473
}
8574
// return if we find a package.json but did not find an import
8675
return null
8776
}
77+
78+
currentDir = dirname(currentDir)
8879
}
8980
} catch (error) {
90-
throw new Error(`Failed to find sub path export: ${specifier}`)
81+
throw new Error(`Failed to find export: ${specifier}`)
9182
}
9283

9384
return null
@@ -113,18 +104,19 @@ async function getCjsExports (url, context, parentLoad, source) {
113104
re = './'
114105
}
115106

116-
let newUrl
117107
// Entries in the import field should always start with #
118108
if (re.startsWith('#')) {
119-
newUrl = resolvePackageImports(re, url)
120-
if (!newUrl) {
109+
const resolvedSpecifier = resolvePackageImports(re, url)
110+
if (!resolvedSpecifier) {
121111
// Unable to resolve specifier import
122112
return
123113
}
124-
} else {
125-
newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href
114+
re = resolvedSpecifier.export
115+
url = resolvedSpecifier.url
126116
}
127117

118+
const newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href
119+
128120
if (newUrl.endsWith('.node') || newUrl.endsWith('.json')) {
129121
return
130122
}

test/hook/testin-specifier.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { execSync } from 'child_process';
2+
import { writeFileSync } from 'fs';
3+
4+
const timings = [];
5+
const command = 'npm run test:single test/hook/specifier-imports.mjs'; // Replace with your actual command
6+
7+
console.log('Running test 100 times...');
8+
9+
for (let i = 1; i <= 100; i++) {
10+
process.stdout.write(`\rRun ${i}/100`);
11+
12+
try {
13+
const output = execSync(command, { encoding: 'utf-8' });
14+
15+
// Extract the milliseconds value
16+
const match = output.match(/resolvePackageImports took ([\d.]+) milliseconds/);
17+
if (match) {
18+
timings.push(parseFloat(match[1]));
19+
}
20+
} catch (error) {
21+
console.error(`\nError on run ${i}:`, error.message);
22+
}
23+
}
24+
25+
console.log('\n\nDone!');
26+
console.log(`Timings:`, timings);
27+
28+
// Save to file
29+
writeFileSync('timing-results.txt', timings.join('\n'));
30+
31+
// Calculate statistics
32+
const avg = timings.reduce((a, b) => a + b, 0) / timings.length;
33+
const min = Math.min(...timings);
34+
const max = Math.max(...timings);
35+
36+
console.log(`\nStatistics:`);
37+
console.log(`Average: ${avg} ms`);
38+
console.log(`Min: ${min} ms`);
39+
console.log(`Max: ${max} ms`);
40+
console.log(`\nResults saved to timing-results.txt`);

0 commit comments

Comments
 (0)