From 9fdbd994ae67b4e963e87f0407747128497afc4a Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 19 Jan 2023 16:27:18 +0100 Subject: [PATCH 1/6] esm: skip file: URL conversion to path when possible --- lib/internal/modules/esm/get_format.js | 32 ++++++++++++++++++++++--- lib/internal/modules/esm/translators.js | 7 ++---- test/parallel/test-esm-url-extname.js | 27 +++++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 test/parallel/test-esm-url-extname.js diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index a9653d43173c05..3ee3881f7d188d 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -4,9 +4,10 @@ const { ObjectPrototypeHasOwnProperty, PromisePrototypeThen, PromiseResolve, + StringPrototypeCharCodeAt, StringPrototypeSlice, } = primordials; -const { basename, extname, relative } = require('path'); +const { basename, relative } = require('path'); const { getOptionValue } = require('internal/options'); const { extensionFormatMap, @@ -17,6 +18,7 @@ const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { URL, fileURLToPath } = require('internal/url'); +const assert = require('internal/assert'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; const protocolHandlers = { @@ -41,6 +43,29 @@ function getDataProtocolModuleFormat(parsed) { return mimeToFormat(mime); } +const DOT_CODE = 46; +const SLASH_CODE = 47; + +/** + * Returns the file extension from a file: URL. Should give similar result than + * require('node:path').extname(require('node:url').fileURLToPath(url)). + * @param {URL} url A file: URL. + * @returns {string} + */ +function extname(url) { + const { pathname } = url; + for (let i = pathname.length - 1; i > 0; i--) { + switch (StringPrototypeCharCodeAt(pathname, i)) { + case SLASH_CODE: + return ''; + + case DOT_CODE: + return StringPrototypeCharCodeAt(pathname, i - 1) === SLASH_CODE ? '' : StringPrototypeSlice(pathname, i); + } + } + return ''; +} + /** * @param {URL} url * @param {{parentURL: string}} context @@ -48,8 +73,7 @@ function getDataProtocolModuleFormat(parsed) { * @returns {string} */ function getFileProtocolModuleFormat(url, context, ignoreErrors) { - const filepath = fileURLToPath(url); - const ext = extname(filepath); + const ext = extname(url); if (ext === '.js') { return getPackageType(url) === 'module' ? 'module' : 'commonjs'; } @@ -59,6 +83,7 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { // Explicit undefined return indicates load hook should rerun format check if (ignoreErrors) { return undefined; } + const filepath = fileURLToPath(url); let suggestion = ''; if (getPackageType(url) === 'module' && ext === '') { const config = getPackageScopeConfig(url); @@ -119,4 +144,5 @@ module.exports = { defaultGetFormat, defaultGetFormatWithoutErrors, extensionFormatMap, + extname, }; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index e185e29ad046f3..267d89f1d44730 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -35,8 +35,7 @@ const { Module: CJSModule, cjsParseCache, } = require('internal/modules/cjs/loader'); -const internalURLModule = require('internal/url'); -const { fileURLToPath, URL } = require('url'); +const { fileURLToPath, URL } = require('internal/url'); let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); @@ -147,9 +146,7 @@ translators.set('commonjs', async function commonjsStrategy(url, source, isMain) { debug(`Translating CJSModule ${url}`); - let filename = internalURLModule.fileURLToPath(new URL(url)); - if (isWindows) - filename = StringPrototypeReplaceAll(filename, '/', '\\'); + const filename = fileURLToPath(new URL(url)); if (!cjsParse) await initCJSParse(); const { module, exportNames } = cjsPreparseModuleExports(filename); diff --git a/test/parallel/test-esm-url-extname.js b/test/parallel/test-esm-url-extname.js new file mode 100644 index 00000000000000..3f1c55d5f07697 --- /dev/null +++ b/test/parallel/test-esm-url-extname.js @@ -0,0 +1,27 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('node:assert'); +const path = require('node:path'); +const { extname } = require('node:internal/modules/esm/get_format'); +const { fileURLToPath } = require('node:url'); + +[ + 'file:///path/to/file', + 'file:///path/to/file.ext', + 'file:///path.to/file.ext', + 'file:///path.to/file', + 'file:///path.to/.file', + 'file:///path.to/.file.ext', + 'file:///path/to/f.ext', + 'file:///path/to/..ext', + 'file:///path/to/..', + 'file:///file', + 'file:///file.ext', + 'file:///.file', + 'file:///.file.ext', +].forEach((input) => { + const inputAsURL = new URL(input); + const inputAsPath = fileURLToPath(inputAsURL); + assert.strictEqual(extname(inputAsURL), path.extname(inputAsPath)); +}); From e28b07441b4bacdbdd8c9672bd64a4cded897b7b Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 18 Mar 2023 19:05:57 +0100 Subject: [PATCH 2/6] fix lint --- lib/internal/modules/esm/get_format.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 3ee3881f7d188d..499455f69be2f4 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -18,7 +18,6 @@ const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { URL, fileURLToPath } = require('internal/url'); -const assert = require('internal/assert'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; const protocolHandlers = { From dd073836599e16ad18fbacfbca3396baa47c952f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 19 Mar 2023 12:07:27 +0100 Subject: [PATCH 3/6] Update lib/internal/modules/esm/get_format.js --- lib/internal/modules/esm/get_format.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 499455f69be2f4..e0690f7e1ab363 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -46,7 +46,7 @@ const DOT_CODE = 46; const SLASH_CODE = 47; /** - * Returns the file extension from a file: URL. Should give similar result than + * Returns the file extension from a URL. Should give similar result to * require('node:path').extname(require('node:url').fileURLToPath(url)). * @param {URL} url A file: URL. * @returns {string} From 0d5e2f48a808f1e0f7b094416c87cd444c845dd1 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 19 Mar 2023 12:08:06 +0100 Subject: [PATCH 4/6] Update lib/internal/modules/esm/get_format.js --- lib/internal/modules/esm/get_format.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index e0690f7e1ab363..807a3aaf3c8bbf 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -48,7 +48,7 @@ const SLASH_CODE = 47; /** * Returns the file extension from a URL. Should give similar result to * require('node:path').extname(require('node:url').fileURLToPath(url)). - * @param {URL} url A file: URL. + * @param {URL} url * @returns {string} */ function extname(url) { From 51c366b944d8fe5161f0da55b14e2a08e6c8b468 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 19 Mar 2023 12:10:16 +0100 Subject: [PATCH 5/6] Update lib/internal/modules/esm/get_format.js --- lib/internal/modules/esm/get_format.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 807a3aaf3c8bbf..d8cfb6df710540 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -47,7 +47,8 @@ const SLASH_CODE = 47; /** * Returns the file extension from a URL. Should give similar result to - * require('node:path').extname(require('node:url').fileURLToPath(url)). + * `require('node:path').extname(require('node:url').fileURLToPath(url))` + * when used with a `file:` URL. * @param {URL} url * @returns {string} */ From f6f7fc8db62eb85628be60c2ab73223ed86da81f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 19 Mar 2023 13:41:17 +0100 Subject: [PATCH 6/6] use Windows-compatible URLs --- test/parallel/test-esm-url-extname.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/parallel/test-esm-url-extname.js b/test/parallel/test-esm-url-extname.js index 3f1c55d5f07697..d40601243e609a 100644 --- a/test/parallel/test-esm-url-extname.js +++ b/test/parallel/test-esm-url-extname.js @@ -7,19 +7,19 @@ const { extname } = require('node:internal/modules/esm/get_format'); const { fileURLToPath } = require('node:url'); [ - 'file:///path/to/file', - 'file:///path/to/file.ext', - 'file:///path.to/file.ext', - 'file:///path.to/file', - 'file:///path.to/.file', - 'file:///path.to/.file.ext', - 'file:///path/to/f.ext', - 'file:///path/to/..ext', - 'file:///path/to/..', - 'file:///file', - 'file:///file.ext', - 'file:///.file', - 'file:///.file.ext', + 'file:///c:/path/to/file', + 'file:///c:/path/to/file.ext', + 'file:///c:/path.to/file.ext', + 'file:///c:/path.to/file', + 'file:///c:/path.to/.file', + 'file:///c:/path.to/.file.ext', + 'file:///c:/path/to/f.ext', + 'file:///c:/path/to/..ext', + 'file:///c:/path/to/..', + 'file:///c:/file', + 'file:///c:/file.ext', + 'file:///c:/.file', + 'file:///c:/.file.ext', ].forEach((input) => { const inputAsURL = new URL(input); const inputAsPath = fileURLToPath(inputAsURL);