Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -1472,7 +1472,10 @@ function getPathFromURLWin32(url) {
}
}
pathname = SideEffectFreeRegExpPrototypeSymbolReplace(FORWARD_SLASH, pathname, '\\');
pathname = decodeURIComponent(pathname);
// Fast-path: if there is no percent-encoding, avoid decodeURIComponent.
if (StringPrototypeIndexOf(pathname, '%') !== -1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why indexOf?

Suggested change
if (StringPrototypeIndexOf(pathname, '%') !== -1) {
if (StringPrototypeIncludes(pathname, '%')) {

Copy link
Member Author

@gurgunday gurgunday Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a convention from Fastify, we prefered indexOf as it was faster until node 22. The difference was even bigger in earlier node versions.

For instance, in node 20:

Starting benchmark...
Searching for "lazy dog" in "The quick brown fox jumps over the lazy dog. This is a long sentence used for benchmarking string search methods."

String#includes x 952,187,610 ops/sec ±0.81% (98 runs sampled)
String#indexOf x 960,298,473 ops/sec ±0.16% (102 runs sampled)

-----------------------------------
Fastest is String#indexOf

benchmark:

const Benchmark = require('benchmark');

// --- Setup ---
const suite = new Benchmark.Suite;

// The string to search within
const text = 'The quick brown fox jumps over the lazy dog. This is a long sentence used for benchmarking string search methods.';

// The substring to find
const substring = 'lazy dog';

// --- Benchmark Suite ---
console.log('Starting benchmark...');
console.log(`Searching for "${substring}" in "${text}"\n`);

suite
  .add('String#includes', function() {
    text.includes(substring);
  })
  .add('String#indexOf', function() {
    text.indexOf(substring) !== -1;
  })
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('\n-----------------------------------');
    console.log('Fastest is ' + this.filter('fastest').map('name'));
    console.log('-----------------------------------');
  })
  .run();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can use includes though if you're fine with the small difference for node 20

Copy link
Member Author

@gurgunday gurgunday Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In latest V8 it seems to have gone away though, so I changed to includes:

url/whatwg-url-to-and-from-path.js n=5000000 input="file:///dev/null" method="fileURLToPath": 3,903,087.8889524657
url/whatwg-url-to-and-from-path.js n=5000000 input="file:///dev/null?key=param&bool" method="fileURLToPath": 3,469,377.354475161
url/whatwg-url-to-and-from-path.js n=5000000 input="file:///dev/null?key=param&bool#hash" method="fileURLToPath": 3,498,714.659701892

pathname = decodeURIComponent(pathname);
}
if (hostname !== '') {
// If hostname is set, then we have a UNC path
// Pass the hostname through domainToUnicode just in case
Expand Down Expand Up @@ -1578,7 +1581,8 @@ function getPathFromURLPosix(url) {
}
}
}
return decodeURIComponent(pathname);
// Fast-path: if there is no percent-encoding, avoid decodeURIComponent.
return StringPrototypeIndexOf(pathname, '%') !== -1 ? decodeURIComponent(pathname) : pathname;
}

function getPathBufferFromURLPosix(url) {
Expand Down
Loading