From ffb1929b6a9b42d9c751723b842a4405154f0d15 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 | 27 ++-- .../test-esm-extension-lookup-deprecation.mjs | 121 ++++++++++++++++++ 2 files changed, 135 insertions(+), 13 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 4a30bffbaeabc8..ce5e4e27fd5128 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 = @@ -101,7 +101,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) { @@ -111,17 +111,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 "${ @@ -130,6 +120,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', + ); + } } const realpathCache = new SafeMap(); 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); + }); +});