From b5a1eb00bf8a17feda4ed902d6621d9aa7796c9f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 20:21:43 +0200 Subject: [PATCH 1/7] esm: fix emit deprecation on legacy main resolve --- lib/internal/modules/esm/resolve.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 4a30bffbaeabc8..c9a95ecce7430e 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -30,7 +30,7 @@ const { getOptionValue } = require('internal/options'); const policy = getOptionValue('--experimental-policy') ? require('internal/process/policy') : null; -const { sep, relative, toNamespacedPath } = require('path'); +const { sep, relative, toNamespacedPath, resolve } = require('path'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const experimentalNetworkImports = @@ -111,9 +111,9 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { const path = fileURLToPath(url); const pkgPath = fileURLToPath(new URL('.', packageJSONUrl)); const basePath = fileURLToPath(base); - if (main) + if (main && resolve(pkgPath, main) !== path) process.emitWarning( - `Package ${pkgPath} has a "main" field set to ${JSONStringify(main)}, ` + + `Package ${pkgPath} has a "main" field set to "${main}", ` + `excluding the full filename and extension to the resolved file at "${ StringPrototypeSlice(path, pkgPath.length)}", imported from ${ basePath}.\n Automatic extension resolution of the "main" field is ` + @@ -121,7 +121,7 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { 'DeprecationWarning', 'DEP0151', ); - else + else if (!main) process.emitWarning( `No "main" or "exports" field defined in the package.json for ${pkgPath } resolving the main entry point "${ From ac15b0bd2b1075eaba6bb13557f07ee193b35f40 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 21:00:33 +0200 Subject: [PATCH 2/7] add tests --- .../test-esm-extension-lookup-deprecation.mjs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 test/es-module/test-esm-extension-lookup-deprecation.mjs diff --git a/test/es-module/test-esm-extension-lookup-deprecation.mjs b/test/es-module/test-esm-extension-lookup-deprecation.mjs new file mode 100644 index 00000000000000..e4468e502738f6 --- /dev/null +++ b/test/es-module/test-esm-extension-lookup-deprecation.mjs @@ -0,0 +1,103 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as tmpdir from '../common/tmpdir.js'; + +import assert from 'node:assert'; +import { mkdir, writeFile } from 'node:fs/promises'; +import * as path from 'node:path'; +import { execPath } from 'node:process'; +import { describe, it, before } from 'node:test'; + +describe('ESM in main field', { concurrency: true }, () => { + before(() => tmpdir.refresh()); + + it('should handle fully-specified relative path without any warning', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + main: './index.js', + type: 'module', + })); + const { code, stdout, stderr } = await spawnPromisified(execPath, [ + '--input-type=module', + '--eval', 'import "pkg"', + ], { cwd }); + + assert.strictEqual(stderr, ''); + assert.match(stdout, /^Hello World!\r?\n$/); + assert.strictEqual(code, 0); + }); + it('should handle fully-specified absolute path without any warning', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + main: path.join(pkgPath, './index.js'), + type: 'module', + })); + const { code, stdout, stderr } = await spawnPromisified(execPath, [ + '--input-type=module', + '--eval', 'import "pkg"', + ], { cwd }); + + assert.strictEqual(stderr, ''); + assert.match(stdout, /^Hello World!\r?\n$/); + assert.strictEqual(code, 0); + }); + + it('should emit warning when "main" and "exports" are missing', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + type: 'module', + })); + const { code, stdout, stderr } = await spawnPromisified(execPath, [ + '--input-type=module', + '--eval', 'import "pkg"', + ], { cwd }); + + assert.match(stderr, /\[DEP0151\]/); + assert.match(stdout, /^Hello World!\r?\n$/); + assert.strictEqual(code, 0); + }); + it('should emit warning when "main" is a relative path without extension', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + main: 'index', + type: 'module', + })); + const { code, stdout, stderr } = await spawnPromisified(execPath, [ + '--input-type=module', + '--eval', 'import "pkg"', + ], { cwd }); + + assert.match(stderr, /\[DEP0151\]/); + assert.match(stdout, /^Hello World!\r?\n$/); + assert.strictEqual(code, 0); + }); + it('should emit warning when "main" is an absolute path without extension', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + main: pkgPath + 'index', + type: 'module', + })); + const { code, stdout, stderr } = await spawnPromisified(execPath, [ + '--input-type=module', + '--eval', 'import "pkg"', + ], { cwd }); + + assert.match(stderr, /\[DEP0151\]/); + assert.match(stdout, /^Hello World!\r?\n$/); + assert.strictEqual(code, 0); + }); +}); From 94a5ce43e5d4e41bb2b72ba09d30f00bfb775ba3 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 21:02:29 +0200 Subject: [PATCH 3/7] simplify condition --- lib/internal/modules/esm/resolve.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index c9a95ecce7430e..84cc39e8f9daab 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -111,7 +111,16 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { const path = fileURLToPath(url); const pkgPath = fileURLToPath(new URL('.', packageJSONUrl)); const basePath = fileURLToPath(base); - if (main && resolve(pkgPath, main) !== path) + if (!main) { + process.emitWarning( + `No "main" or "exports" field defined in the package.json for ${pkgPath + } resolving the main entry point "${ + StringPrototypeSlice(path, pkgPath.length)}", imported from ${basePath + }.\nDefault "index" lookups for the main are deprecated for ES modules.`, + 'DeprecationWarning', + 'DEP0151', + ); + } else if (resolve(pkgPath, main) !== path) { process.emitWarning( `Package ${pkgPath} has a "main" field set to "${main}", ` + `excluding the full filename and extension to the resolved file at "${ @@ -121,15 +130,7 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { 'DeprecationWarning', 'DEP0151', ); - else if (!main) - process.emitWarning( - `No "main" or "exports" field defined in the package.json for ${pkgPath - } resolving the main entry point "${ - StringPrototypeSlice(path, pkgPath.length)}", imported from ${basePath - }.\nDefault "index" lookups for the main are deprecated for ES modules.`, - 'DeprecationWarning', - 'DEP0151', - ); + } } const realpathCache = new SafeMap(); From c905e5ec710e8531cb28123f353c40ea09fcb4f8 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 21:04:05 +0200 Subject: [PATCH 4/7] add test for falsy `"main"` --- .../test-esm-extension-lookup-deprecation.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/es-module/test-esm-extension-lookup-deprecation.mjs b/test/es-module/test-esm-extension-lookup-deprecation.mjs index e4468e502738f6..9cae7fab405387 100644 --- a/test/es-module/test-esm-extension-lookup-deprecation.mjs +++ b/test/es-module/test-esm-extension-lookup-deprecation.mjs @@ -64,6 +64,24 @@ describe('ESM in main field', { concurrency: true }, () => { assert.match(stdout, /^Hello World!\r?\n$/); assert.strictEqual(code, 0); }); + it('should emit warning when "main" is falsy', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + type: 'module', + main: '', + })); + const { code, stdout, stderr } = await spawnPromisified(execPath, [ + '--input-type=module', + '--eval', 'import "pkg"', + ], { cwd }); + + assert.match(stderr, /\[DEP0151\]/); + assert.match(stdout, /^Hello World!\r?\n$/); + assert.strictEqual(code, 0); + }); it('should emit warning when "main" is a relative path without extension', async () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); From b9cada007511710e49f310b043c20895624f2e4f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 21:06:14 +0200 Subject: [PATCH 5/7] jsdoc --- lib/internal/modules/esm/resolve.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 84cc39e8f9daab..f2787789d5baa6 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -105,6 +105,7 @@ function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, interna * @returns {void} */ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { + console.log({ main }); const format = defaultGetFormatWithoutErrors(url); if (format !== 'module') return; From ee13bbc7693986ec56908154ff95635c796e777c Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 21:06:27 +0200 Subject: [PATCH 6/7] fixup! jsdoc --- lib/internal/modules/esm/resolve.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index f2787789d5baa6..ce5e4e27fd5128 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -101,11 +101,10 @@ function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, interna * @param {URL} url * @param {URL} packageJSONUrl * @param {string | URL | undefined} base - * @param {string} main + * @param {string} [main] * @returns {void} */ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { - console.log({ main }); const format = defaultGetFormatWithoutErrors(url); if (format !== 'module') return; From ee891043c979f09249128d5c59443a003b6d9b34 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 5 Jul 2023 23:24:30 +0200 Subject: [PATCH 7/7] missing `await`s --- .../test-esm-extension-lookup-deprecation.mjs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/es-module/test-esm-extension-lookup-deprecation.mjs b/test/es-module/test-esm-extension-lookup-deprecation.mjs index 9cae7fab405387..e8da1a8b176bc7 100644 --- a/test/es-module/test-esm-extension-lookup-deprecation.mjs +++ b/test/es-module/test-esm-extension-lookup-deprecation.mjs @@ -14,8 +14,8 @@ describe('ESM in main field', { concurrency: true }, () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); await mkdir(pkgPath, { recursive: true }); - writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); - writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ main: './index.js', type: 'module', })); @@ -32,8 +32,8 @@ describe('ESM in main field', { concurrency: true }, () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); await mkdir(pkgPath, { recursive: true }); - writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); - writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ main: path.join(pkgPath, './index.js'), type: 'module', })); @@ -51,8 +51,8 @@ describe('ESM in main field', { concurrency: true }, () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); await mkdir(pkgPath, { recursive: true }); - writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); - writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ type: 'module', })); const { code, stdout, stderr } = await spawnPromisified(execPath, [ @@ -68,8 +68,8 @@ describe('ESM in main field', { concurrency: true }, () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); await mkdir(pkgPath, { recursive: true }); - writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); - writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ type: 'module', main: '', })); @@ -86,8 +86,8 @@ describe('ESM in main field', { concurrency: true }, () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); await mkdir(pkgPath, { recursive: true }); - writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); - writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ main: 'index', type: 'module', })); @@ -104,8 +104,8 @@ describe('ESM in main field', { concurrency: true }, () => { const cwd = path.join(tmpdir.path, Math.random().toString()); const pkgPath = path.join(cwd, './node_modules/pkg/'); await mkdir(pkgPath, { recursive: true }); - writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); - writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await writeFile(path.join(pkgPath, './package.json'), JSON.stringify({ main: pkgPath + 'index', type: 'module', }));