Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Using TS + ESM in globalSetup does not pick up correct tsconfig.json module #3328

Closed
santi opened this issue Mar 10, 2022 · 4 comments
Labels

Comments

@santi
Copy link

santi commented Mar 10, 2022

Version

27.1.3

Steps to reproduce

Clone my repo at https://github.com/santi/jest-ts-esm-issue
Tested with NodeJS v.17+

npm install
npm test

Expected behavior

When setting {"compilerOptions": { "module": "es2022" }} in tsconfig.json, I expect that the tests and globalSetup function will run with the given module option while parsing the TS files.

Actual behavior

It seems like the correct module option is used when running tests, as the import.meta.url reference is resolved correctly in the sample test file. However, when registering the globalSetup.ts hook in jest.config.ts, an error is thrown, claiming that TypeScript is running with another module setting (I assume the default ES5, but have not been able to confirm this):

Error: Jest: Got error running globalSetup - /xxx/jest-ts-esm-issue/globalSetup.ts, reason: [TSError: globalSetup.ts:2:15 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node12', or 'nodenext'.

See Debug log below for full stack trace.

Debug log

> jest-ts-esm-issue@1.0.0 test
> NODE_OPTIONS=--experimental-vm-modules jest

(node:56945) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Error: Jest: Got error running globalSetup - /xxx/jest-ts-esm-issue/globalSetup.ts, reason: [TSError: globalSetup.ts:2:15 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node12', or 'nodenext'.

2   console.log(import.meta.url);
                ~~~~~~~~~~~]
    at runGlobalHook (/xxx/jest-ts-esm-issue/node_modules/@jest/core/build/runGlobalHook.js:134:15)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async runJest (/xxx/jest-ts-esm-issue/node_modules/@jest/core/build/runJest.js:369:5)
    at async _run10000 (/xxx/jest-ts-esm-issue/node_modules/@jest/core/build/cli/index.js:320:7)
    at async runCLI (/xxx/jest-ts-esm-issue/node_modules/@jest/core/build/cli/index.js:173:3)
    at async Object.run (/xxx/jest-ts-esm-issue/node_modules/jest-cli/build/cli/index.js:155:37)

Additional context

The same error occurs with module: es2020|esnext|nodenext|etc.... I am a bit unsure if this is an error with ts-jest or jest in general. I've tested with both latest stable Jest (27.5.1) and latest alpha Jest (28.0.0-alpha.7). If this is an issue with Jest, feel free to tell me, and I'll open an issue there instead.

Environment

System:
    OS: macOS 12.2.1
    CPU: (8) arm64 Apple M1
  Binaries:
    Node: 17.0.1 - ~/.nvm/versions/node/v17.0.1/bin/node
    Yarn: 1.22.17 - ~/.nvm/versions/node/v17.0.1/bin/yarn
    npm: 8.1.0 - ~/.nvm/versions/node/v17.0.1/bin/npm
  npmPackages:
    jest: ^27.5.1 => 27.5.1
@eyalroth
Copy link

I think I'm experiencing the issue but from another angle.

I'm using project references within my project, and the actual tests are able import modules with "absolute" paths (not ../) ; however, my global setup code (.ts file) cannot import modules in this way, resulting in Error: Jest: Got error running globalSetup - reason: Cannot find module.

So yeah, it seems like the compiler options are being ignored when running the global setup.


My setup is as follows.

I have two "projects" -- src and test:

// src/tsconfig.json
{
  "extends": "../tsconfig-base.json",
  "compilerOptions": {
    "composite": true
  }
}
// test/jsconfig.json
{
  "extends": "../tsconfig-base.json",
  "references": [
    { "path": "../src" }
  ]
}

Base config:

// tsconfig-base.json
{
  "compilerOptions": {
    "outDir": "out/compiled",
    "sourceMap": true,
    "esModuleInterop": true,
    "lib": [
      "es2020",
      "dom"
    ],
    "module": "commonjs",
    "target": "es2020",
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "rootDir": ".",
    "baseUrl": ".",
    "declaration": true,
    "declarationMap": true,
    "paths": {
      "src/*": ["src/*"],
      "test/*": ["test/*"]
    }
  },
  "exclude": [
    "node_modules"
  ]
}

And root config:

// tsconfig.json
{
  "files": [],
  "include": [],
  "references": [
    { "path": "./src" },
    { "path": "./test" }
  ]
}

Then, I have multiple testing modules (unit, integration, etc) with jest.config.js for each:

// test/unit/jest.config.js
const base = require('../../jest.config.js');
module.exports = {
  ...base,
  testMatch: [
    '<rootDir>/test/unit/**/*.test.ts'
  ],
};

While the base jest.config.js looks like this:

// fix resolution of config/*.json in tests run from IntelliJ
process.env.NODE_CONFIG_DIR = `${__dirname}/config`;

module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
  rootDir: __dirname,
  testMatch: [
    "<rootDir>/test/**/*.test.ts"
  ],
  modulePathIgnorePatterns: ["<rootDir>/out/"],
  moduleNameMapper: {
    "^src/(.*)$": "<rootDir>/src/$1",
    "^test/(.*)$": "<rootDir>/test/$1"
  },
  globals: {
    "ts-jest": {
      tsconfig: "<rootDir>/test/tsconfig.json"
    }
  },
  globalSetup: "<rootDir>/test/utils/global-setup.ts"
};

@santi
Copy link
Author

santi commented Mar 14, 2022

I spent some time during the weekend to look into this issue, and I think I have finally come up with an explanation and a possible workaround (at least for my use case):

Explanation

The correct tsconfig.json is actually loaded by ts-jest to perform the transformation of TS files (including globalSetup.ts).

However, before performing the transformation, the module setting from the initial tsconfig is overwritten here: ts-compiler.ts. This is due to having the options.supportsStaticESM flag set to false, which comes from hardcoded config in @jest/transform itself. This forces ts-jest to transform the code to CommonJS, which has no way to transform import.meta.url (Note: There are "equivalent" code snippets to import.meta.url, but no default way of transpiling it from ESM to CJS).

Therefore, I think we can conclude that this is an issue with jest, not caused by ts-jest.

Workaround

By using babel-jest instead of ts-jest to transpile the globalSetup.ts file, we can specify how to transform import.meta.url into CJS code with the plugin babel-plugin-transform-import-meta plugin. Babel supports reading TS with the @babel/preset-typescript preset, but it DOES NOT perform type checking. In my case that is less important for running the setup hook, so it is a sacrifice which is okay to make.

With the following config setup, globalSetup.ts should be able to run without problems:
jest.config.ts

export default {
  preset: "ts-jest/presets/default-esm",
  globals: {
    "ts-jest": {
      useESM: true,
    },
  },
  extensionsToTreatAsEsm: [".ts"],
  globalSetup: "<rootDir>/globalSetup.ts",
  transform: { "globalSetup\\.ts$": "babel-jest" },
};

babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env', {
            "targets": {
                "node": "current"
            }
          }],
          "@babel/preset-typescript"
    ],
    "plugins": [
      "babel-plugin-transform-import-meta"
    ]
};

package.json

{
  "scripts": {
    "test": "NODE_OPTIONS=--experimental-vm-modules jest"
  },
  "devDependencies": {
    "@babel/core": "^7.17.5",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-typescript": "^7.16.7",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "babel-plugin-transform-import-meta": "^2.1.1",
    "jest": "^27.5.1",
    "ts-jest": "^27.1.3",
    "typescript": "^4.6.2"
  }
}

@yume-chan
Copy link

yume-chan commented Mar 23, 2022

I believe I also encountered this issue.

I also found that some compiler options seems randomly affects the result.

  • Adding "downlevelIteration": true to tsconfig.base.json breaks tests with ReferenceError: exports is not defined (despite of I'm targeting ESNext so it shouldn't have effect)
  • Adding "module: "Node12" (I'm using this) to tsconfig.test.json fixes tests (despite of it already exists in tsconfig.base.json

@ahnpnl
Copy link
Collaborator

ahnpnl commented Jul 15, 2022

Now this bug is solved in Jest 28 without the need of babel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants