mocha: TypeError: Invalid host defined options - Cannot Use TypeScript, JavaScript, ESM, and TS-Node

Hello!

I tried reaching out on Gitter, but wasn’t able to elicit any help.

I am in the process of trying to migrate my team’s code base to TypeScript in small chunks. Which means I need to have JavaScript and TypeScript supported while this is performed. Additionally I am trying to align the standards for that move.

Is there anyone who can provide insight on how to get mocha to run for a typescript project that has Javascript in it using ESM module support?

Also quick disclaimer: This is not the palantir related to Peter Thiel πŸ˜•, My team mates just really like Lord of the Rings and chose a poor name for our project.


I have a directory structure like so All my test files have a .js extension on them

.
β”œβ”€β”€ .mocharc.json
β”œβ”€β”€ package.json
β”œβ”€β”€ register.ts
β”œβ”€β”€ src
β”‚Β Β  └── ...
β”œβ”€β”€ test
β”‚Β Β  β”œβ”€β”€ runner.js
β”‚Β Β  β”œβ”€β”€ tsconfig.json
β”‚Β Β  └── ...
β”œβ”€β”€ tsconfig.json
└── webpack.config.js

I have a package.json file with these contents

{
    "name": "palantir-api",
    // ...
    "scripts": {
        "build": "yarn transpile && webpack",
        "bumpversion": "yarn version --no-git-tag-version --no-commit-hooks",
        "dev": "export DB_HOST=localhost && nodemon ./src/app.js",
        "format": "prettier --write \"./**/*.md\" \"./**/*.json\" \"./**/*.js\" \"./**/*.ts\" \"./**/*.yaml\" \"./**/*.yml\"",
        "open_api": "redoc-cli bundle ./api/palantir_api.yaml --output ./docs/index.html",
        "preopen_api": "node -e \"console.log(require('./package.json').version)\" > api/version.yaml",
        "postbumpversion": "node -e \"console.log(require('./package.json').version)\" > VERSION",
        "prod": "node dist/api.bundle.js",
        "runner": "node --loader ts-node/esm ./test/runner.js",
        "start": "nodemon --exec babel-node ./src/app.js",
        "start_db_server": "pg_ctl -D /usr/local/var/postgres start",
        "stop_db_server": "pg_ctl -D /usr/local/var/postgres stop",
        "transpile": "tsc",
        "test": "yarn runner --t system",
        "unit_test": "nyc yarn runner --t unit"
    },
    // ...
    "dependencies": {
        "@babel/core": "^7.10.4",
        "@babel/node": "^7.10.4",
        "@babel/plugin-proposal-class-properties": "^7.10.4",
        "@babel/preset-env": "^7.10.4",
        "@hapi/joi": "^17.1.1",
        "@types/dotenv": "^8.2.0",
        "@types/express-xml-bodyparser": "^0.3.1",
        "bcrypt": "^5.0.0",
        "cors": "^2.8.5",
        "express": "^4.17.1",
        "express-validator": "^6.6.1",
        "express-winston": "^4.0.3",
        "express-xml-bodyparser": "^0.3.0",
        "jsonwebtoken": "^8.5.1",
        "nodemon": "^2.0.4",
        "pg": "^8.2.1",
        "serve-favicon": "^2.5.0",
        "trim-request-body": "^1.0.1",
        "uuid": "^8.2.0",
        "winston": "^3.3.3"
    },
    "devDependencies": {
        "@babel/plugin-transform-runtime": "^7.10.4",
        "@babel/polyfill": "^7.10.4",
        "@babel/preset-react": "^7.10.4",
        "@types/pg": "^7.14.10",
        "babel-loader": "^8.1.0",
        "babel-plugin-syntax-dynamic-import": "^6.18.0",
        "babel-plugin-transform-runtime": "^6.23.0",
        "chai": "^4.2.0",
        "chai-http": "^4.3.0",
        "esm": "^3.2.25",
        "faker": "^5.1.0",
        "mocha": "^8.0.1",
        "mocha-junit-reporter": "^2.0.0",
        "mocha-multi-reporters": "^1.1.7",
        "nyc": "^15.1.0",
        "palantir-test-data-helpers": "^0.0.4",
        "prettier": "^2.2.1",
        "redoc-cli": "^0.9.8",
        "reify": "^0.20.12",
        "sinon": "^9.0.2",
        "ts-node-dev": "1.1.1",
        "typescript": "4.1.3",
        "webpack": "^4.43.0",
        "webpack-cli": "^3.3.12",
        "webpack-node-externals": "^1.7.2",
        "yargs": "^15.4.0"
    },
    "type": "module"
}

I have a default tsconfig.json file with these contents

{
    "compilerOptions": {
        "allowJs": true,
        "allowSyntheticDefaultImports": true,
        "baseUrl": ".",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "lib": ["dom", "es6"],
        "module": "es6",
        "moduleResolution": "node",
        "noImplicitAny": true,
        "outDir": "./typescript_build",
        "paths": {
            "*": ["./node_modules/@types", "./custom_types"]
        },
        "resolveJsonModule": true,
        "skipLibCheck": true,
        "strict": true,
        "strictPropertyInitialization": false,
        "target": "es6"
    }
}

Which is inherited by the test/tsconfig.json and looks like this

{
    "extends": "../tsconfig.json",
    "compilerOptions": {
        "baseUrl": ".",
        "experimentalDecorators": true,
        "isolatedModules": false,
        "paths": {
            "*": ["../node_modules/@types", "../custom_types"]
        },
        "resolveJsonModule": true
    },
    "exclude": ["../node_modules"],
    "include": ["./**/*.ts", "../custom_types"]
}

And then I have a .mocharc.json configured with these options

{
    "extension": ["js", "ts"],
    "resolveJsonModule": true,
    "require": ["./register.ts", "esm", "ts-node/register"]
}

And finally I have a register.ts file with these configurations

import * as tsNode from "ts-node";

import testTSConfig from "./test/tsconfig.json";

tsNode.register({
    files: true,
    project: "./test/tsconfig.json"
});

Additionally there is a webpack file here:

import path from "path";
import nodeExternals from "webpack-node-externals";

module.exports = {
    entry: "./typescript_build/src/app.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "api.bundle.js"
    },
    target: "node",
    mode: "production",
    node: {
        // Need this when working with express, otherwise the build fails
        __dirname: false, // if you don't put this is, __dirname
        __filename: false // and __filename return blank or /
    },
    externals: [nodeExternals()], // Need this to avoid error when working with Express
    module: {
        rules: [
            {
                test: /(\.js[\S]{0,1})$/i,
                exclude: /node_modules/,
                loader: "babel-loader",
                query: {
                    presets: ["@babel/preset-react", "@babel/preset-env"],
                    plugins: ["@babel/proposal-class-properties"]
                }
            }
        ]
    }
};

When I run this command

env TS_NODE_PROJECT=test/tsconfig.json \
yarn run mocha --config ./.mocharc.json --test unit

I am provided this output

 ✘ lknecht ξ‚° ~/Repositories/palantir-api ξ‚° ξ‚  do_not_use/prototyping_mocha_typescript-dev ±✚ ξ‚° ⬑ v12.17.0 ξ‚° env TS_NODE_PROJECT=test/tsconfig.json \
yarn run mocha --config ./.mocharc.json --test system --full-trace

yarn run v1.22.10
$ /Users/lknecht/Repositories/palantir-api/node_modules/.bin/mocha --config ./.mocharc.json --test system --full-trace

TypeError: Invalid host defined options
    at formattedImport (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/esm-utils.js:6:23)
    at Object.exports.requireOrImport (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/esm-utils.js:23:14)
    at Object.exports.loadFilesAsync (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/esm-utils.js:33:34)
    at Mocha.loadFilesAsync (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/mocha.js:431:19)
    at singleRun (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/cli/run-helpers.js:125:15)
    at exports.runMocha (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/cli/run-helpers.js:190:10)
    at Object.exports.handler (/Users/lknecht/Repositories/palantir-api/node_modules/mocha/lib/cli/run.js:362:11)
    at /Users/lknecht/Repositories/palantir-api/node_modules/mocha/node_modules/yargs/lib/command.js:241:49
    at process.runNextTicks [as _tickCallback] (internal/process/task_queues.js:62:5)
    at /Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:34535
    at /Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:34176
    at process.<anonymous> (/Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:34506)
    at Function.<anonymous> (/Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:296856)
    at Function.<anonymous> (/Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:296555)
    at Function.<anonymous> (/Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:284879)
    at Object.apply (/Users/lknecht/Repositories/palantir-api/node_modules/esm/esm.js:1:199341)
    at internal/main/run_main_module.js:17:47
error Command failed with exit code 1.

I have been testing different configurations non-stop trying to get

  • mocha
  • javascript
  • typescript
  • ecma script modules
  • etc

to all play nicely.

By default I CAN get the api to stand itself up, but the second mocha gets involved this blows up! 😦

Can anyone provide any guidance?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 16 (9 by maintainers)

Most upvoted comments

@juergba and @cspotcode There we go! πŸ‘ πŸ‘ πŸ‘ πŸ‘ πŸ‘

The final trick was the requirement of calling loadFilesAsync!

Prior to this change. The previous version called mocha.run after calling addFile and that caused errors to be thrown!

Thank you so much for your time, your patience, the example project, and everything in between!

On node, you have 2x options:

  • Write native ECMAScript module syntax and run it via node’s CommonJS support
  • Write native ECMAScript module syntax and run it via node’s new native ESM support

You should commit to one or the other, and then I can give you a configuration that will work. The first option – using node’s CommonJS support – is by far the recommended option.

If you absolutely must use the second option – native ESM support – then do the following:

Remove the esm module Stop requiring ts-node/register Run mocha like this: TS_NODE_PROJECT=absolutepathtotsconfigfile "NODE_OPTIONS=--loader ts-node/esm" mocha TS_NODE_PROJECT is optional if you want it to load $PWD/tsconfig.json because that file will be auto-detected. You can get rid of register.ts Upgrade your node version. Invalid host defined options is a confusing node error which may be improved in a newer version.

I strongly advise against using the esm package, and certainly not both at the same time. esm was excellent when we were young and beautiful, now you should go with Node’s native ESM implementation.

see ts-node/esm, maybe @cspotcode can give you a hint.

import * as tsNode from 'ts-node'; I don’t know wether ts-node is a CommonJS module. If so, then only default imports are supported.