Overriding `tsconfig.json` for ts-node in mocha - node.js

Is it possible to override which tsconfig.json ts-node uses when called from mocha?
My main tsconfig.json contains "module": "es2015", but I want to use "module": "commonjs" for ts-node only.
I tried this
mocha --compilers ts:ts-node/register,tsx:ts-node/register \
--compilerOptions '{"module":"commonjs"}' \
--require ts-node/register test/**/*.spec.ts*
but it did not work:
SyntaxError: Unexpected token import
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:387:25)
at Module.m._compile (/usr/lib/node_modules/ts-node/src/index.ts:406:23)
at Module._extensions..js (module.js:422:10)
at Object.require.extensions.(anonymous function) [as .tsx] (/usr/lib/node_modules/ts-node/src/index.ts:409:12)
at Module.load (module.js:357:32)
at Function.Module._load (module.js:314:12)
at Module.require (module.js:367:17)
at require (internal/module.js:16:19)
at /usr/lib/node_modules/mocha/lib/mocha.js:222:27
at Array.forEach (native)
at Mocha.loadFiles (/usr/lib/node_modules/mocha/lib/mocha.js:219:14)
at Mocha.run (/usr/lib/node_modules/mocha/lib/mocha.js:487:10)
at Object.<anonymous> (/usr/lib/node_modules/mocha/bin/_mocha:458:18)
at Module._compile (module.js:413:34)
at Object.Module._extensions..js (module.js:422:10)
at Module.load (module.js:357:32)
at Function.Module._load (module.js:314:12)
at Function.Module.runMain (module.js:447:10)
at startup (node.js:146:18)
at node.js:404:3

You need to set the configuration through the TS_NODE_COMPILER_OPTIONS environment variable
Example code on an unix machine:
TS_NODE_COMPILER_OPTIONS='{"module":"commonjs"}' \
mocha --require ts-node/register 'test/**/*.spec.{ts,tsx}'
Explanation extracted from the repository documentation
CLI and Programmatic Options
Environment variable denoted in parentheses.
-T, --transpile-only Use TypeScript's faster transpileModule (TS_NODE_TRANSPILE_ONLY, default: false)
-I, --ignore [pattern] Override the path patterns to skip compilation (TS_NODE_IGNORE, default: /node_modules/)
-P, --project [path] Path to TypeScript JSON project file (TS_NODE_PROJECT)
-C, --compiler [name] Specify a custom TypeScript compiler (TS_NODE_COMPILER, default: typescript)
-D, --ignore-diagnostics [code] Ignore TypeScript warnings by diagnostic code (TS_NODE_IGNORE_DIAGNOSTICS)
-O, --compiler-options [opts] JSON object to merge with compiler options (TS_NODE_COMPILER_OPTIONS)
--files Load files from tsconfig.json on startup (TS_NODE_FILES, default: false)
--pretty Use pretty diagnostic formatter (TS_NODE_PRETTY, default: false)
--skip-project Skip project config resolution and loading (TS_NODE_SKIP_PROJECT, default: false)
--skip-ignore Skip ignore checks (TS_NODE_SKIP_IGNORE, default: false)
--log-error Logs errors of types instead of exit the process (TS_NODE_LOG_ERROR, default: false)
--prefer-ts-exts Re-order file extensions so that TypeScript imports are preferred (TS_NODE_PREFER_TS_EXTS, default: false)

TypeScript allows you to override a configuration file. Rather than hard-code JSON in an environment variable as mentioned in the other solutions, specify the overridden configuration path in the environment. The TS_NODE_PROJECT environment variable can be used for this.
TS_NODE_PROJECT='./tsconfig.commonjs.json'
So if your main config is:
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
}
}
You can create another configuration that overrides the module setting.
tsconfig.commonjs.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
When you run mocha, specify the overridden configuration to use:
"test": "TS_NODE_PROJECT='./tsconfig.commonjs.json' mocha -r ts-node/register test/**/*.spec.ts*"
This makes it very easy to further customize your tsconfig just for mocha testing. You can even run ts-node (outside of mocha) directly specifying that path:
ts-node -P tsconfig.commonjs.json -r myFile.ts

--compilerOptions wont' work.
What you need to do is customize how you register ts-node. My case was a little bit different from yours, I wanted it to use test/tsconfig.json, which contained settings needed by my test code. If I just used --require ts-node/register, it was using a default configuration that did not contain the settings needed to run my tests.
What I did was:
Create a file test/tshook.js. It contains:
require("ts-node").register({
project: "test/tsconfig.json",
});
I edited my test/mocha.opts to have:
--require test/tshook.js
test/**/*.ts
This should will pass the desired setting to ts-node:
require("ts-node").register({
compilerOptions: {
module: "commonjs",
},
});

In package.json - scripts section:
"test": "TS_NODE_PROJECT=src mocha"
picks up my tsconfig.json in the src directory of my project, overriding the default tsconfig.json.
OP can achieve same by using test instead of src

This worked for me on windows
set TS_NODE_COMPILER_OPTIONS={\"module\":\"commonjs\"} && mocha -r ts-node/register test/unit/*.test.ts
This was the error that prompted me to use that solution
(function (exports, require, module, __filename, __dirname) { import 'mocha';

You can also use ts-mocha (https://www.npmjs.com/package/ts-mocha)
Example
package.json
"test": "ts-mocha -p test/tsconfig.cjs.json test/**/*.test.ts"
test/tsconfig.cjs.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

While there doesn't seem to be a unified linux/windows command line phrasing that works, you can set the compiler-options from the command line. In my case, much like the OP, I have a tsconfig.json with a module: esnext. I was able to override on the command line:
Ran on Windows, w/ts-node installed globally, but in different shell types:
bash/mingw64:
ts-node --compiler-options={"module":"commonJS"} xxx.ts
cmd:
ts-node --compiler-options={\"module\":\"commonJS\"} xxx.ts

On mac
"test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --require ts-node/register test/**/*.ts",

This doesn't address the core original question, but goes to the heart of getting mocha with typescript working, so I'll post it here in case it helps others.
We found that some ridiculous backslash escaping was needed to make this work on Windows. It works fine on Mac too, even though Mac doesn't need the same escaping.
Then, additional ts-node require directives were critical.
File package.json:
{
"scripts": {
...
"test": "cross-env TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\"} mocha"
},
...
"mocha": {
"require": [
"ts-node/register",
"tsconfig-paths/register"
],
"watch-files": [
"./src/**/*.ts",
"./tests/**/*.spec.ts"
],
"spec": "./tests/**/*.spec.ts"
}

Related

Unexpected token export {} after importing cjs file in Typescript+ESM project

EDIT
Changing module to Node16 inside compilerOptions fixed it.
more info https://github.com/microsoft/TypeScript/issues/50647
I have a pretty simple typescript+esm project with a CJS dependency, so my code looks like this:
index.ts
import myFunc from "./myfile.cjs"
(...)
I also have the compilerOptions (inside tsconfig.json) allowSyntheticDefaultImportsand esModuleInterop set
But when I try to run it using ts-node (node --loader ts-node/esm --experimental-specifier-resolution=node src/index.ts) it fails with a syntax error saying that my cjs file, myfile.cjs has an unexpected token export (export {};)
It is clear to me that something (ts-node probably) is trying to convert this into an ESM module, but it just creates a syntax error and breaks the code. Obviously, I'm not adding it there manually.
Command used: node --loader ts-node/esm --experimental-specifier-resolution=node src/index.ts
Complete error log:
export {};
^^^^^^
SyntaxError: Unexpected token 'export'
at Object.compileFunction (node:vm:352:18)
at wrapSafe (node:internal/modules/cjs/loader:1027:15)
at Module._compile (node:internal/modules/cjs/loader:1063:27)
at Module.m._compile (/Users/cjg/Git/ast/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Object.require.extensions.<computed> [as .js] (/Users/cjg/Git/ast/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:170:29)
at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"baseUrl": "./src",
"module": "es2020",
"outDir": "./dist",
"allowJs": true,
"target": "es2020",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
},
"include": [
"./src/**/*"
]
}
ts-node version: 10.9.1
node version: 17.9.0
tsc version: 4.7.4
Does anyone have a clue?
Thanks in advance!
There are several threads on this when generating code for a ".ts" file, even for a commonjs target. From what I can tell, typescript developers think the generated export {}; is a feature in those files and provide the rationale (which many disagree with).
From your description, and I tested the same, they're doing the same in very specifically (.cjs) files.
The solution for now is to remove the line in your build. Maybe using gulp-replace?

Electron cannot open shared object file from node_modules folder

On electron, the node module vosk needs to access some shared objects located in node_modules/vosk/lib/.
The issue I am having right now is that, when I do require('vosk') in my main.js and try to execute my AppImage file, I get:
A JavaScript error occurred in the main process
Uncaught Exception:
Error: Dynamic Linking Error: /tmp/.mount_CantooClaxGf/resources/app.asar/node_modules/vosk/lib/linux-x86_64/libvosk.so: Cannot open the shared object: It's not a folder
at new DynamicLibrary (/tmp/.mount_CantooClaxGf/resources/app.asar/node_modules/ffi-napi/lib/dynamic_library.js:75:11)
at Object.Library (/tmp/.mount_CantooClaxGf/resources/app.asar/node_modules/ffi-napi/lib/library.js:47:10)
at Object.<anonymous> (/tmp/.mount_CantooClaxGf/resources/app.asar/node_modules/vosk/index.js:24:21)
at Module._compile (internal/modules/cjs/loader.js:1145:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1166:10)
at Module.load (internal/modules/cjs/loader.js:981:32)
at Module._load (internal/modules/cjs/loader.js:881:14)
at Function.Module._load (electron/js2c/asar.js:769:28)
at Module.require (internal/modules/cjs/loader.js:1023:19)
at require (internal/modules/cjs/helpers.js:77:18)
I tried to add vosk to the files in the build:
"build": {
"files": [
"dist/**/*",
"src/assets/icons/*",
"electron.js",
"package.json",
"assets/models/*",
"node_modules/vosk/lib/*"
],
I can now see the files in the app.asar.unpacked/node_modules/vosk/lib/ folder, but when executing the app, I'm still having the same error.
I found this answer mentioning a hack, but it didn't solve my issue and I still have the exact same error.
How am I supposed to package the shared objects in a way that vosk will find them?
I could solve the issue with this config for electron, putting all the dependencies of vosk in the extraResources field:
"build": {
"extraResources": [
"node_modules/at-least-node/**/*",
"node_modules/builder-util-runtime/**/*",
"node_modules/debug/**/*",
"node_modules/ffi-napi/**/*",
"node_modules/fs-extra/**/*",
"node_modules/get-symbol-from-current-process-h/**/*",
"node_modules/get-uv-event-loop-napi-h/**/*",
"node_modules/graceful-fs/**/*",
"node_modules/jsonfile/**/*",
"node_modules/ms/**/*",
"node_modules/node-addon-api/**/*",
"node_modules/node-gyp-build/**/*",
"node_modules/ref-napi/**/*",
"node_modules/ref-struct-di/**/*",
"node_modules/sax/**/*",
"node_modules/universalify/**/*",
"assets/models/**/*"
],
"files": [
"dist/**/*",
"src/assets/icons/*",
"electron.js",
"package.json"
],
I also needed this lib.
It's now working as expected
I solved by updating my electron-builder configuration in package.json to be
{
"build": {
"asar": true,
"asarUnpack": [
"node_modules"
],
}
}
Then ensuring the unpacked path was used instead:
var dirPath = __dirname.includes('.asar') ? __dirname.replace('.asar', '.asar.unpacked') : __dirname;

How to setup an npm test via mocha?

I use Angular + TypeScript + Electron and I try to add tests to my application. I installed mocha and added the following line to package.json:
"test": "mocha './**/*.spec.ts'",
I have a app.component.spec.ts file in my source directory but executing npm run test fails with the following exception:
> mocha './**/*.spec.ts'
src\app\app.component.spec.ts:1
import { TestBed, waitForAsync } from '#angular/core/testing';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at wrapSafe (internal/modules/cjs/loader.js:931:16)
at Module._compile (internal/modules/cjs/loader.js:979:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1035:10)
at Module.load (internal/modules/cjs/loader.js:879:32)
Can anyone explain me the context, and how I could fix this issue?
P.S. In similar posts the module in tsconfig.json needed to be set to commonjs which I did, but that didn't solve the issue:
{
"compileOnSave": false,
"compilerOptions": {
"module": "commonjs",
...
}

Cannot find Typescript module even though tsc successfully manages to resolve it

I have a Node.js project written in Typescript which is expected to run as a CLI, and am having trouble to import a module located out of the node_modules directory using an absolute path (relative paths work fine). It might be important to mention that I am using the oclif framework for building my CLI.
My project is organized as follows:
cli
|--node_modules
|--src
|--my-module.ts
|--subdir
|--index.ts
Within my-module.ts I have:
export class MyClass {
myClassFcn(s: string) {
return 'result'
}
}
The index.ts script contains something like:
import {MyClass} = require('my-module')
When I try to execute my app with ts-node, I get
(node:10423) [MODULE_NOT_FOUND] Error Plugin: cli: Cannot find module 'my-module'
module: #oclif/config#1.6.17
task: toCached
plugin: cli
root: /home/eschmidt/Workspace/cli
Error Plugin: cli: Cannot find module 'my-module'
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:571:15)
at Function.Module._load (internal/modules/cjs/loader.js:497:25)
at Module.require (internal/modules/cjs/loader.js:626:17)
at require (internal/modules/cjs/helpers.js:20:18)
at Object.<anonymous> (/home/eschmidt/Workspace/cli/src/commands/create/index.ts:5:1)
at Module._compile (internal/modules/cjs/loader.js:678:30)
at Module.m._compile (/home/eschmidt/Workspace/cli/node_modules/ts-node/src/index.ts:403:23)
at Module._extensions..js (internal/modules/cjs/loader.js:689:10)
at Object.require.extensions.(anonymous function) [as .ts] (/home/eschmidt/Workspace/cli/node_modules/ts-node/src/index.ts:406:12)
at Module.load (internal/modules/cjs/loader.js:589:32)
module: #oclif/config#1.6.17
task: toCached
plugin: my-plugin
root: /home/eschmidt/Workspace/cli
What I can't understand is that when I run tsc --traceResolution the module is correctly resolved:
======== Module name 'my-module' was successfully resolved to '/home/eschmidt/Workspace/cli/src/my-module.ts'. ========
My tsconfig.json file contains:
{
"compilerOptions": {
"declaration": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"module": "commonjs",
"sourceMap": true,
"outDir": "./lib",
"pretty": true,
"rootDirs": [
"./src/"
],
"strict": true,
"target": "es2017",
"baseUrl": "src"
},
"include": [
"./src/**/*"
]
}
I would greatly appreciate it if anyone could shed some light on this issue, or at least suggest where to look for further help. In case more details are needed, please let me know.
Thanks in advance!
It turns out that the problem was due to the fact that although both tsc and ts-node use baseUrl for absolute path resolution, neither of them perform any type of actual mapping from absolute to relative paths in the generated Javascript code. In other words, both the transpiled JS files and the code produced internally by ts-node end up having:
import {MyClass} = require('my-module')
whereas I was expecting them to contain something like:
import {MyClass} = require('../my-module')
which prevented node's module loader from finding the module. ts-node also did not work, I believe, because there was simply no tsconfig.json file to indicate the path mappings.
Although confusing IMO, and not properly documented, this is expected behavior, though, as discussed here. As of now, absolute to relative path mapping is not supported by Typescript (see https://github.com/Microsoft/TypeScript/issues/15479).
In order to avoid the situation known as path hell, which means having very deep relative import paths, I found module-alias and tsmodule-alias to be very useful. These modules alter the behavior of the module loader so that it automatically maps aliases to relative paths.
For more information about the problem, refer to this issue on Github.
Another solution that may be relevant is to run node with NODE_PATH=dist node dist/index.js. This essentially specifies to node what path each absolute import (relative to baseUrl) should use

How to get semantic/gulp/webpack to work in electron-react-boilerplate

I am trying to port a react app to electron using electron-react-boilerplate but I'm using semantic-ui which suggested being set up with gulp. electron-react-boilerplate uses webpack to handle all of its packaging and I can't get webpack +gulp to work so everything will package in the electron app.
I'm trying this link that explains how to pipe webpack configs through gulp tasks but I'm getting an "unexpected token import" error from the webpack config.
.babelrc
{
"presets": ["es2015", "stage-0", "react"],
"plugins": ["add-module-exports"],
"env": {
"production": {
"presets": ["react-optimize"],
"plugins": ["babel-plugin-dev-expression"]
},
"development": {
"plugins": ["tcomb"],
"presets": ["react-hmre"]
},
"test": {
"plugins": [
["webpack-loaders", { "config": "webpack.config.test.js", "verbose": false }]
]
}
}
}
simple gulpfile.js straight from the link above:
var gulp = require('gulp');
var webpack = require('webpack-stream');
gulp.task('default', function() {
return gulp.src('src/entry.js')
.pipe(webpack())
.pipe(gulp.dest('dist/'));
});
the webpack dev file is here,
note some of the names are changed between this boilerplate version and my project but otherwise it's the same.
the error:
Configurator>gulp
[09:52:40] Using gulpfile C:\git\Configurator\gulpfile.js
[09:52:40] Starting 'default'...
[09:52:40] 'default' errored after 8.28 ms
[09:52:40] C:\git\Configurator\webpack.config.development.js:7
import webpack from 'webpack';
^^^^^^
SyntaxError: Unexpected token import
at Object.exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:513:28)
at Object.Module._extensions..js (module.js:550:10)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Gulp.<anonymous> (C:\git\Configurator\gulpfile.js:7:18)
at module.exports (C:\git\Configurator\node_modules\orchestrator\lib\runTask.js:34:7)
the internet tells me that getting rid of the import errors should be as simple as using the 'es2015' preset in .babelrc but it's there and it's not helping.
I can get the dev server to work in the electron app with the semantic-ui stuff after the initial gulp build for semantic, but for some reason it doesn't build into the electron package when I try to package an installer to deploy this thing.
When I run the electron app dev server through webpack it works fine though, except I get two errors:
cannot set property exports of undefined
and
locals[0] does not appear to be a module object with hot module replacement API enabled
the latter stacktrace goes back to some semantic imports in one of my react files.
I'm totally at a loss as to how to make all this stuff work together.

Resources