Trying to use mocha, ES6 modules, and ts-node with the --experimental-loader option

Viewed 2005

I'm trying to get the ts-node option --experimental-loader working along with mocha, and having no luck. Until I started trying to compile ES6 modules, I used to be able to run mocha tests this way:

"test": "nyc --reporter=html mocha --require ts-node/register src/**/*.spec.ts"

That doesn't work anymore when generating ES6 modules.

I'd use the TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' solution for testing, but that won't work for me because of another complication: I'm generating ES6 modules as a first step in my build, but also generating ES5/CommonJS modules using webpack and babel. That last step doesn't work unless I add .js to the end of my local TypeScript import statements.

But adding those .js extensions turns out to break the TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' solution, which will work, however, if I go back and delete all of the .js extensions. I obviously don't want a test and build process where I have to keep going back and forth between adding and removing those extensions.

To simplify for now, I've taken out nyc, and I'm trying to run tests like this:

mocha -r ts-node/register --experimental-loader ./ts-loader.mjs src/**/*.spec.ts

I get no errors this way, but nothing happens either. It's like the src/**/*.spec.ts doesn't exist.

My do-nothing (for now) dummy loader looks like this:

console.log('ts-loader loaded');

export async function resolve(specifier, context, defaultResolve) {
  console.log('resolve');
  return defaultResolve(specifier, context, defaultResolve);
}

export async function getFormat(url, context, defaultGetFormat) {
  console.log('getFormat');
  return defaultGetFormat(url, context, defaultGetFormat);
}

export async function getSource(url, context, defaultGetSource) {
  console.log('getSource');
  return defaultGetSource(url, context, defaultGetSource);
}

export async function transformSource(source, context, defaultTransformSource) {
  console.log('transformSource');
  return defaultTransformSource(source, context, defaultTransformSource);
}

export function getGlobalPreloadCode() {
  console.log('getGlobalPreloadCode');
  return '';
}

I can tell it gets loaded because the 'ts-loader loaded' message appears, but none of the functions ever get called.

I've tried other permutations, but just get errors like src/**/*.spec.ts being treated as a literal file name instead of a glob, or errors about modules not being found.

I was hoping to see my loader invoked for every import being handled, and then figuring out how to manipulate the file extensions, but I haven't managed to get that far yet. Any suggestions?

I'm using node v14.15.1. The full code for my project, with a working build, but broken tests, can be found here: https://github.com/kshetline/tubular_math

1 Answers

I finally found a solution, although it wasn't along the lines I was originally looking for. I gave up on trying to make mocha happy with the extra .js extensions, and found a way to make webpack happy without them. So...

import { Angle, Mode, Unit } from './angle.js';

...went back to...

import { Angle, Mode, Unit } from './angle';

My test script looks like this:

  "scripts": {
    "build": "rimraf dist/ && tsc && webpack && webpack --env target=umd",
    "prepublishOnly": "npm run build",
    "lint": "eslint 'src/**/*.ts'",
    "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter=html mocha --require ts-node/register src/**/*.spec.ts"
  },

And finally, most importantly, I figured out how to make webpack 5.x (4.x didn't have this issue) happy with local JavaScript imports that don't have a .js extension, which webpack now otherwise insists upon if your package.json says "type": "module":

    module: {
      rules: [
        { test: /\.js$/, use: 'babel-loader', resolve: { fullySpecified: false } }
      ]
    }

...where setting fullySpecified to false is the key to the solution.

UPDATE: The above example was done working on a deliberately simple project, something easy for a starter to generate an npm package with ESM modules. Now that I'm trying something a little more advanced, I've run into a snag again running unit tests. As soon as a *.spec.ts file directly or indirectly imports external code, module loading fails. I can only test code with no external dependencies until I figure out how to fix that problem. Apparently using "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' is only letting me go one level deep into fixing the basic problem with running mocha along with ts-node.

Related