From d9a800ee5c0f901af9fd25e7c3885f5fd2da4e1f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 11 Jul 2023 18:49:06 +0200 Subject: [PATCH] esm: fix emit deprecation on legacy main resolve PR-URL: https://github.com/nodejs/node/pull/48664 Refs: https://github.com/nodejs/node/pull/48325 Reviewed-By: Yagiz Nizipli Reviewed-By: Rich Trott Reviewed-By: Rafael Gonzaga --- lib/internal/modules/esm/resolve.js | 25 ++-- .../test-esm-extension-lookup-deprecation.mjs | 121 ++++++++++++++++++ 2 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 test/es-module/test-esm-extension-lookup-deprecation.mjs diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 619b9531e9037b..7006887a6fe2ba 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -118,7 +118,7 @@ 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) { @@ -128,17 +128,7 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { const path = fileURLToPath(url); const pkgPath = fileURLToPath(new URL('.', packageJSONUrl)); const basePath = fileURLToPath(base); - if (main) - process.emitWarning( - `Package ${pkgPath} has a "main" field set to ${JSONStringify(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 ` + - 'deprecated for ES modules.', - 'DeprecationWarning', - 'DEP0151', - ); - else + if (!main) { process.emitWarning( `No "main" or "exports" field defined in the package.json for ${pkgPath } resolving the main entry point "${ @@ -147,6 +137,17 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { '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 "${ + StringPrototypeSlice(path, pkgPath.length)}", imported from ${ + basePath}.\n Automatic extension resolution of the "main" field is ` + + 'deprecated for ES modules.', + 'DeprecationWarning', + 'DEP0151', + ); + } } /** 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..e8da1a8b176bc7 --- /dev/null +++ b/test/es-module/test-esm-extension-lookup-deprecation.mjs @@ -0,0 +1,121 @@ +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 }); + 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', + })); + 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 }); + 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', + })); + 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 }); + 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, [ + '--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 falsy', async () => { + const cwd = path.join(tmpdir.path, Math.random().toString()); + const pkgPath = path.join(cwd, './node_modules/pkg/'); + await mkdir(pkgPath, { recursive: true }); + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await 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/'); + await mkdir(pkgPath, { recursive: true }); + await writeFile(path.join(pkgPath, './index.js'), 'console.log("Hello World!")'); + await 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 }); + 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', + })); + 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); + }); +});