threads.js icon indicating copy to clipboard operation
threads.js copied to clipboard

Question: Jest + Typescript + threads.js how can it work together ?

Open DaniGTA opened this issue 5 years ago • 15 comments

I have implemented threads.js in my "vue + typescript + electron" project and it works, but my test now fails with the error:

pinguSync\src\backend\controller\main-list-manager\worker\main-list-search-id-worker.ts:1
import { expose } from 'threads/worker';
^^^^^^

SyntaxError: Cannot use import statement outside a moduleJest

"worker.ts":

import { expose } from 'threads/worker';
import Series from '../../objects/series';

const worker = function findSeriesById(buffer: Series[], id: string): Series | null {
    return buffer.find(x => x.id === id) ?? null;
};

export type MainListSearchIdWorker = typeof worker;

expose(worker);

"main":

    public static async findSeriesById(id: string): Promise<Series | null> {
        const worker = await spawn(new Worker('./worker/main-list-search-id-worker.ts'));
        return worker(MainListManager.getMainList(), id);
    }

"test":

    describe('test fn: findSeriesById', () => {
        test('should find Series by Id', async () => {
            const series = new Series();
            MainListManager['mainList'].push(series);
            const result = await MainListSearcher.findSeriesById(series.id);
            expect(result?.id).toBe(series.id);
        }); 
        
        test('should return on no result', async () => {
            const result = await MainListSearcher.findSeriesById('id');
            expect(result).toBeNull();
        });
    });

(You can turn Series to a object with just a id for reproducing): {id:''}

JestConfig:

'use strict';


module.exports = {
	'roots': [
		'<rootDir>'
	],
	'testMatch': [
		'**/test/**/*-(test|tests).+(ts|tsx|js)',
	],
	'transform': {
		'\\.(gql|graphql)$': '@jagi/jest-transform-graphql',
		'^.+\\.(ts|tsx)$': 'ts-jest'
	},
	'reporters': [
		'default',
		['jest-html-reporters', {
			'publicPath': './html-report',
			'filename': 'report.html',
			'expand': true
		}]
	],
	'testTimeout': 3500,
	'verbose': false,
	'globals': {
		'ts-jest': {
			babelConfig: true,
		}
	},
	'testEnvironment': 'node',
	'setupFilesAfterEnv': ['<rootDir>/test/test-helper.ts'],
	'maxWorkers': 6
};

DaniGTA avatar Jun 25 '20 16:06 DaniGTA

Hey @DaniGTA!

That's a pretty specific use case and I am not sure I am able to answer that, esp. since I don't do Vue. Your detailed description is a good starting point, though.

Have you tried this yet?

andywer avatar Jun 25 '20 17:06 andywer

Sorry, I forgot to mention that my project is in vue and electron but it is separate from the tests. (Jest works without any electron or vue dependency (and threads.js works with electron+vue (thanks to webpack) but it cant get compiled in jest))

Jest only tests the parts of my program that are only related to typescript, everything else is getting mocked.

So puppeter will not help me in this case.

When i run the jest tests its like running plain typescript. (ts-node)

But i dont know how threads.js works without beeing compiled to js.

DaniGTA avatar Jun 25 '20 17:06 DaniGTA

But i dont know how threads.js works without beeing compiled to js.

You would just use ts-node to run your program, the way you always do. Now when you try to spawn a TypeScript worker in node.js, threads.js will automatically set up the ts-node/register hook in the worker if ts-node is available.

I guess either that mechanic doesn't work in your case for some reason or maybe your TypeScript config (might be a different one for Jest than when running without Jest?) has compilerOptions.module set to es6 or esnext?

Or seen from a different angle: Do you use ES modules in node.js? (I just assumed you would use CommonJS modules)

andywer avatar Jun 26 '20 08:06 andywer

has compilerOptions.module set to es6 or esnext?

esnext

Or seen from a different angle: Do you use ES modules in node.js?

CommonJS modules

Jest and Electron uses the same tsconfig:

tsconfig.ts:

{
  "compilerOptions": {
    "target": "es2019",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata":true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "outDir": "out",
    "types": [
      "webpack-env",
      "jest",
      "node"
    ],
    "lib": [
      "esnext",
      "es6",
      "es7",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ]
}

DaniGTA avatar Jun 26 '20 08:06 DaniGTA

Then that's why it doesn't work: You live in a CommonJS world, but tell the TypeScript compiler to compile to ES modules. Don't know if Jest does some magic on top of that, so that your master thread code runs in the first place, but it cannot work like this.

There is one thing that needs to be fixed in threads.js, though: If you are using ES modules in node.js, you should be able to spawn a TypeScript worker ES module. I think this will break right now in the way that you showed as soon as ES modules and automatic ts-node/register come together.

andywer avatar Jun 26 '20 09:06 andywer

Is there a solution to this? I am on the same problem right now

crubier avatar Sep 19 '20 20:09 crubier

I'm having exactly the same issue with jest + ts-jest + ts-config... I can't really configure it so that it can import ts file for webworker... It'd be nice if the docs included an example repository with typescript + threads.js + jest configured

TeoTN avatar Sep 29 '20 16:09 TeoTN

Just ran into the same issue, typescript workers don't play well with jest.

TheoXD avatar Nov 28 '20 15:11 TheoXD

Same problem for me, is documentation available?

nassissme avatar Dec 23 '20 09:12 nassissme

Sorry, got no solution for Jest ready yet. If anyone got an idea, please share!

andywer avatar Dec 23 '20 22:12 andywer

I got it to work eventually quite a while ago and the thing that did the trick was to add this to tsconfig.json:

  "ts-node": {
    "compilerOptions": {
      "module": "commonjs"
    }
  }

TheoXD avatar Dec 24 '20 19:12 TheoXD

For anyone having this issue, I haven't found a true solution but I was able to get my other tests running by conditionally importing any modules using threads at runtime based on an environment variable. This let me exclude it in the tests.

I also found that setting verbose: false seemed to cause jest to log the error but continue running tests anyway. This did result in a large memory leak though (~500MB per test suite).

MattGson avatar May 28 '21 03:05 MattGson

I faced same "SyntaxError: Cannot use import statement outside a modul" when running on Jest, but I was able solve it with yarn add ts-node --dev following the: You would just use ts-node to run your program, the way you always do. Now when you try to spawn a TypeScript worker in node.js, threads.js will automatically set up the ts-node/register hook in the worker if ts-node is available.

Jest uses ts-jest for transpile while thread.js wants ts-node, so you need to install that in addition (ts-jest won't transpile files for thread.js, only ts-node suppored as mentioned above ) - maybe that's what is causing the confusion.

mfrener avatar Jan 05 '22 22:01 mfrener

Using ts-node slows down running my tests to 3x. So is there another way?

standbyoneself avatar Feb 02 '24 09:02 standbyoneself