Repository: nodejs/node
Pull request: lib: reduce amount of caught URL errors

Since we have `URL.parse()` now, we don't need try/catch with `new URL()`. This will potentially improve the performance of hot paths.

cc @nodejs/url

Changed files:
filename: lib/internal/modules/esm/hooks.js
patch: @@ -33,7 +33,7 @@ const {
   ERR_WORKER_UNSERIALIZABLE_ERROR,
 } = require('internal/errors').codes;
 const { exitCodes: { kUnsettledTopLevelAwait } } = internalBinding('errors');
-const { URL } = require('internal/url');
+const { URLParse } = require('internal/url');
 const { canParse: URLCanParse } = internalBinding('url');
 const { receiveMessageOnPort } = require('worker_threads');
 const {
@@ -403,11 +403,7 @@ class Hooks {
 
     let responseURLObj;
     if (typeof responseURL === 'string') {
-      try {
-        responseURLObj = new URL(responseURL);
-      } catch {
-        // responseURLObj not defined will throw in next branch.
-      }
+      responseURLObj = URLParse(responseURL);
     }
 
     if (responseURLObj?.href !== responseURL) {

filename: lib/internal/modules/esm/loader.js
patch: @@ -29,14 +29,13 @@ const {
   ERR_UNKNOWN_MODULE_FORMAT,
 } = require('internal/errors').codes;
 const { getOptionValue } = require('internal/options');
-const { isURL, pathToFileURL, URL } = require('internal/url');
+const { isURL, pathToFileURL, URLParse } = require('internal/url');
 const { emitExperimentalWarning, kEmptyObject } = require('internal/util');
 const {
   compileSourceTextModule,
   getDefaultConditions,
 } = require('internal/modules/esm/utils');
 const { kImplicitTypeAttribute } = require('internal/modules/esm/assert');
-const { canParse } = internalBinding('url');
 const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding('module_wrap');
 const {
   urlToFilename,
@@ -322,8 +321,9 @@ class ModuleLoader {
   getModuleJobForRequire(specifier, parentURL, importAttributes) {
     assert(getOptionValue('--experimental-require-module'));
 
-    if (canParse(specifier)) {
-      const protocol = new URL(specifier).protocol;
+    const parsed = URLParse(specifier);
+    if (parsed != null) {
+      const protocol = parsed.protocol;
       if (protocol === 'https:' || protocol === 'http:') {
         throw new ERR_NETWORK_IMPORT_DISALLOWED(specifier, parentURL,
                                                 'ES modules cannot be loaded by require() from the network');

filename: lib/internal/source_map/source_map_cache.js
patch: @@ -39,7 +39,7 @@ const kSourceMappingURLMagicComment = /\/[*/]#\s+sourceMappingURL=(?<sourceMappi
 const kSourceURLMagicComment = /\/[*/]#\s+sourceURL=(?<sourceURL>[^\s]+)/g;
 
 const { isAbsolute } = require('path');
-const { fileURLToPath, pathToFileURL, URL } = require('internal/url');
+const { fileURLToPath, pathToFileURL, URL, URLParse } = require('internal/url');
 
 let SourceMap;
 
@@ -209,21 +209,20 @@ function maybeCacheGeneratedSourceMap(content) {
  * @returns {object} deserialized source map JSON object
  */
 function dataFromUrl(sourceURL, sourceMappingURL) {
-  try {
-    const url = new URL(sourceMappingURL);
+  const url = URLParse(sourceMappingURL);
+
+  if (url != null) {
     switch (url.protocol) {
       case 'data:':
         return sourceMapFromDataUrl(sourceURL, url.pathname);
       default:
         debug(`unknown protocol ${url.protocol}`);
         return null;
     }
-  } catch (err) {
-    debug(err);
-    // If no scheme is present, we assume we are dealing with a file path.
-    const mapURL = new URL(sourceMappingURL, sourceURL).href;
-    return sourceMapFromFile(mapURL);
   }
+
+  const mapURL = new URL(sourceMappingURL, sourceURL).href;
+  return sourceMapFromFile(mapURL);
 }
 
 // Cache the length of each line in the file that a source map was extracted

filename: lib/internal/url.js
patch: @@ -1601,6 +1601,7 @@ module.exports = {
   installObjectURLMethods,
   URL,
   URLSearchParams,
+  URLParse: URL.parse,
   domainToASCII,
   domainToUnicode,
   urlToHttpOptions,