esm: bypass CJS loader in default load under `--default-type=module`

This allows user to opt-out from using the monkey-patchable CJS loader,
even to load CJS modules.

PR-URL: https://github.com/nodejs/node/pull/50004
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
pull/50657/head
Antoine du Hamel 2023-11-10 10:20:46 +02:00 committed by GitHub
parent 3e14cfbbcf
commit 0dfc59e4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 26 deletions

View File

@ -622,7 +622,8 @@ Omitting vs providing a `source` for `'commonjs'` has very different effects:
registered hooks. This behavior for nullish `source` is temporary — in the
future, nullish `source` will not be supported.
The Node.js internal `load` implementation, which is the value of `next` for the
When `node` is run with `--experimental-default-type=commonjs`, the Node.js
internal `load` implementation, which is the value of `next` for the
last hook in the `load` chain, returns `null` for `source` when `format` is
`'commonjs'` for backward compatibility. Here is an example hook that would
opt-in to using the non-default behavior:

View File

@ -18,6 +18,8 @@ const policy = getOptionValue('--experimental-policy') ?
null;
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const defaultType =
getOptionValue('--experimental-default-type');
const { Buffer: { from: BufferFrom } } = require('buffer');
@ -140,7 +142,7 @@ async function defaultLoad(url, context = kEmptyObject) {
// Now that we have the source for the module, run `defaultGetFormat` again in case we detect ESM syntax.
format ??= await defaultGetFormat(urlInstance, contextToPass);
if (format === 'commonjs' && contextToPass !== context) {
if (format === 'commonjs' && contextToPass !== context && defaultType !== 'module') {
// For backward compatibility reasons, we need to discard the source in
// order for the CJS loader to re-fetch it.
source = null;

View File

@ -1,31 +1,101 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { describe, it } from 'node:test';
import { match, strictEqual } from 'node:assert';
import { deepStrictEqual, match, strictEqual } from 'node:assert';
describe('--experimental-default-type=module should not affect the interpretation of files with unknown extensions',
{ concurrency: true }, () => {
it('should error on an entry point with an unknown extension', async () => {
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
fixtures.path('es-modules/package-type-module/extension.unknown'),
]);
describe('--experimental-default-type=module', { concurrency: true }, () => {
describe('should not affect the interpretation of files with unknown extensions', { concurrency: true }, () => {
it('should error on an entry point with an unknown extension', async () => {
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
fixtures.path('es-modules/package-type-module/extension.unknown'),
]);
match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
strictEqual(stdout, '');
strictEqual(code, 1);
strictEqual(signal, null);
});
match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
strictEqual(stdout, '');
strictEqual(code, 1);
strictEqual(signal, null);
});
it('should error on an import with an unknown extension', async () => {
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'),
]);
it('should error on an import with an unknown extension', async () => {
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'),
]);
match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
strictEqual(stdout, '');
strictEqual(code, 1);
strictEqual(signal, null);
});
});
match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
strictEqual(stdout, '');
strictEqual(code, 1);
strictEqual(signal, null);
});
});
it('should affect CJS .js files (imported, required, entry points)', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
fixtures.path('es-modules/package-type-commonjs/echo-require-cache.js'),
]);
deepStrictEqual(result, {
code: 0,
stderr: '',
stdout: 'undefined\n',
signal: null,
});
});
it('should affect .cjs files that are imported', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
'-e',
`import ${JSON.stringify(fixtures.fileURL('es-module-require-cache/echo.cjs'))}`,
]);
deepStrictEqual(result, {
code: 0,
stderr: '',
stdout: 'undefined\n',
signal: null,
});
});
it('should affect entry point .cjs files (with no hooks)', async () => {
const { stderr, stdout, code } = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
fixtures.path('es-module-require-cache/echo.cjs'),
]);
strictEqual(stderr, '');
match(stdout, /^undefined\n$/);
strictEqual(code, 0);
});
it('should affect entry point .cjs files (when any hooks is registered)', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
'--import',
'data:text/javascript,import{register}from"node:module";register("data:text/javascript,");',
fixtures.path('es-module-require-cache/echo.cjs'),
]);
deepStrictEqual(result, {
code: 0,
stderr: '',
stdout: 'undefined\n',
signal: null,
});
});
it('should not affect CJS from input-type', async () => {
const { stderr, stdout, code } = await spawnPromisified(process.execPath, [
'--experimental-default-type=module',
'--input-type=commonjs',
'-p',
'require.cache',
]);
strictEqual(stderr, '');
match(stdout, /^\[Object: null prototype\] \{\}\n$/);
strictEqual(code, 0);
});
});

View File

@ -0,0 +1 @@
console.log(require.cache);

View File

@ -0,0 +1 @@
console.log(require.cache);