Repository: mochajs/mocha

Problem statement:
`ts-node/register` not supported in 9.0.0
### Prerequisites


- [x] Checked that your issue hasn't already been filed by cross-referencing [issues with the `faq` label](https://github.com/mochajs/mocha/issues?utf8=%E2%9C%93&q=is%3Aissue%20label%3Afaq%20)
- [x] Checked next-gen ES issues and syntax problems by using the same environment and/or transpiler configuration without Mocha to ensure it isn't just a feature that actually isn't supported in the environment in question or a bug in your code.
- [x] 'Smoke tested' the code to be tested by running it outside the real test suite to get a better sense of whether the problem is in the code under test, your usage of Mocha, or Mocha itself
- [x] Ensured that there is no discrepancy between the locally and globally installed versions of Mocha. You can find them with: `node node_modules/.bin/mocha --version`(Local) and `mocha --version`(Global). We recommend that you _not_ install Mocha globally.

### Description

mocha configuration contains `require: ['ts-node/register']`, in order to load the TS loader.
But since `node_modules/ts-node/register` is a folder, the new ESM loader in mocha fails to load, and throws:
`Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '...../node_modules/ts-node/register' is not supported resolving ES modules imported from ...../node_modules/mocha/lib/esm-utils.js`

### Steps to Reproduce


1. `npm i --save-dev ts-node`
2. Add `require:   ['ts-node/register']` to the `.mocharc.yml`

**Expected behavior:** Should work, and at least automatically resolve some well-known loaders like `ts-node`.

**Actual behavior:** Fails miserably.

**Reproduces how often:** 100%

### Versions


- The output of `mocha --version` and `node node_modules/.bin/mocha --version`: 9.0.0
- The output of `node --version`: v15.12.0
- Your operating system
  - name and version: MacOS 11.12.3
  - architecture (32 or 64-bit): M1
- Your shell (e.g., bash, zsh, PowerShell, cmd): bash
- Any code transpiler (e.g., TypeScript, CoffeeScript, Babel) being used (and its version): ts-node 9.1.1



Hints:
Will look into it tonight. Hopefully, just `require`-ing when we get `ERR_UNSUPPORTED_DIR_IMPORT` will solve the problem.
Oh, wait, I see your fix @swansontec. Looks like it _will_ solve it. Thanks!
I'm surprised that this issue is arising only two weeks after publishing v9.0.0. Is nobody using typescript with ts-node?
You are using node v15.12.0 which is end-of-life already. Is v14/v16 also failing?
@swantontec, I tried reproducing it, but it works for me:

```typescript
// x.test.ts
function foo(x: number) {
  console.log('hi')
}

foo(4)
```

```yaml
# .mocharc.yaml
require: ['ts-node/register']
```

```shell
$ npm init -y
$ npm i -D ts-node mocha
$ npx mocha x.test.ts
hi


  0 passing (0ms)
```

I tried both for Node.js v15.12.0, and v16.3.0. Mocha was v9.0.1, and ts-node was 10.0.0 and 9.1.1.

If you could create a repository that reproduces this (including full instructions to reproduce), that would maybe pinpoint the problem?
@danielgindi @swansontec are you requiring the `esm` package?
> @danielgindi @swansontec are you requiring the `esm` package?

Mot specifically. Simply `require: ['ts-node/register']`
Aha! This only affects *old* versions of ts-node. Modern versions, such as the current one, have an `exports` field in their `package.json` file, which solves the problem another way.

However, the latest version of [Sucrase](https://github.com/alangpierce/sucrase#readme) (3.19.0) still has the issue. Here is a minimal reproduction script:

```sh
mkdir mocha-demo; cd mocha-demo
yarn add --dev mocha sucrase
echo "console.log('Hey')" > test.ts
yarn mocha -r sucrase/register test.ts
```

So, this does open up the possibility of patching Sucrase to use `exports`, instead of fixing Mocha itself. Perhaps both would be worthwhile.
> Aha! This only affects *old* versions of ts-node. Modern versions, such as the current one, have an `exports` field in their `package.json` file, which solves the problem another way.


Wait. What would be the correct wait to register ts-node with mocha?
> > Aha! This only affects _old_ versions of ts-node. Modern versions, such as the current one, have an `exports` field in their `package.json` file, which solves the problem another way.
> 
> Wait. What would be the correct wait to register ts-node with mocha?

It works the same as before - you just add `-r ts-node/require`, or put `require: ['ts-node/register']` into a Mocha config file.

The problem is that `import()` does not know how to search the filesystem the way `require()` does. The only way for `import('ts-node/register')` to succeed is if the library provides an explicit [subpath export in `package.json`](https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#packages_subpath_exports). Recent versions of ts-node *do* have the right subpath exports, so `import('ts-node/register')` succeeds!

However, other packages like sucrase do *not* provide subpath exports in their `package.json` file, which means `import('sucrase/register')` will fail. Falling back onto `require('sucrase/register')` will work fine, because `require` will search the filesystem as well. This which is what #4666 achieves - enabling the `require` fallback logic for this case.
Got it. But for commonjs, if there's no package.json, the default of course is `index.js`.
And commonjs is the base module system for nodejs and it does not make sense to remove support for it :)
Well, there's a PR for it so all good!
@swansontec got it. I'll try and reproduce it without specifically using `ts-node`. Thanks for the detailed explanation.
@swansontec yup. got it. Fixing...
Hey, @swansontec, I just saw you have a PR for this. After I made mine... 😂

Unfortunately, your PR doesn't include a test. If you'd rather you add a test to your PR, we can use yours. Otherwise, I'll add mine.

Patch:
diff --git a/lib/esm-utils.js b/lib/esm-utils.js
--- a/lib/esm-utils.js
+++ b/lib/esm-utils.js
@@ -49,7 +49,8 @@ exports.requireOrImport = hasStableEsmImplementation
       } catch (err) {
         if (
           err.code === 'ERR_MODULE_NOT_FOUND' ||
-          err.code === 'ERR_UNKNOWN_FILE_EXTENSION'
+          err.code === 'ERR_UNKNOWN_FILE_EXTENSION' ||
+          err.code === 'ERR_UNSUPPORTED_DIR_IMPORT'
         ) {
           return require(file);
         } else {


Test patch:
diff --git a/test/integration/esm.spec.js b/test/integration/esm.spec.js
--- a/test/integration/esm.spec.js
+++ b/test/integration/esm.spec.js
@@ -1,4 +1,5 @@
 'use strict';
+var path = require('path');
 var helpers = require('./helpers');
 var run = helpers.runMochaJSON;
 var runMochaAsync = helpers.runMochaAsync;
@@ -65,4 +66,19 @@ describe('esm', function() {
       done();
     });
   });
+
+  it('should enable requiring/loading a cjs module with "dir" as filename', async function() {
+    var fixture = 'esm/test-that-uses-dir-cjs-require.fixture.js';
+    const result = await runMochaAsync(
+      fixture,
+      [
+        ...args,
+        '--require',
+        path.resolve(__dirname, './fixtures/esm/dir-cjs-require')
+      ],
+      {stdio: 'pipe'}
+    );
+
+    expect(result, 'to have passed test count', 1);
+  });
 });
diff --git a/test/integration/fixtures/esm/dir-cjs-require/index.js b/test/integration/fixtures/esm/dir-cjs-require/index.js
new file mode 100644
--- /dev/null
+++ b/test/integration/fixtures/esm/dir-cjs-require/index.js
@@ -0,0 +1 @@
+global.testPassesIfThisVariableIsDefined = true
diff --git a/test/integration/fixtures/esm/test-that-uses-dir-cjs-require.fixture.js/index.js b/test/integration/fixtures/esm/test-that-uses-dir-cjs-require.fixture.js/index.js
new file mode 100644
--- /dev/null
+++ b/test/integration/fixtures/esm/test-that-uses-dir-cjs-require.fixture.js/index.js
@@ -0,0 +1,4 @@
+// See https://github.com/mochajs/mocha/issues/4665 for an explanation of this test
+it('should require a dir import', () => {
+  expect(global.testPassesIfThisVariableIsDefined, 'to be', true)
+})