esbuild-jest
esbuild-jest copied to clipboard
TypeError: Property typeName of TSTypeReference expected node to be of a type ["TSEntityName"] but instead got "MemberExpression"
I tried replacing ts-jest
in our project with esbuild-jest
. However all tests that transitively reference the history
library fail with:
TypeError: ....test.ts: Property typeName of TSTypeReference expected node to be of a type ["TSEntityName"] but instead got "MemberExpression"
I reduced it to a minimal example that passes without problems in ts-jest
but fails with esbuild-jest
:
import type { History } from 'history';
// Commenting the next line makes the test pass
type Alias = History;
test('bug', () => {
// Removing one of the characters in the below comment makes the test pass
// ock(
console.log('Hello world!')
});
Both of the changes outlined in the comments make the test pass 🤨
Full sample can be found here: esbuild-jest-bug.zip
Output: (Click to expand)
❯ yarn test-ts-jest
yarn run v1.22.5
$ jest --config jest.config.ts-jest.js
PASS ./Sample.test.ts
✓ bug (15 ms)
console.log
Hello world!
at Object.<anonymous> (Sample.test.ts:9:10)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.224 s, estimated 3 s
Ran all test suites.
✨ Done in 2.01s.
❯ yarn test-esbuild-jest
yarn run v1.22.5
$ jest --config jest.config.esbuild-jest.js
FAIL ./Sample.test.ts
● Test suite failed to run
TypeError: .../esbuild-jest-bug/Sample.test.ts: Property typeName of TSTypeReference expected node to be of a type ["TSEntityName"] but instead got "MemberExpression"
at Object.validate (node_modules/@babel/types/lib/definitions/utils.js:132:11)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 1.149 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
@DreierF are you using typescript with interface or type?
try to install "@babel/preset-typescript" then add this to your package.json or babel config
"babel": { "presets": [ "@babel/preset-typescript" ] }
Not sure what you mean with "typescript with interface or type". Also I don't see why the Typescript preset should be necessary as esbuild can transpile ts way faster then babel can, which is the reason why I'm trying to get esbuild-jest to work in the first place. From looking over the code I found this line which sounds suspiciously related to the problem:
https://github.com/aelbore/esbuild-jest/blob/f80eb9900550f146f7e09e67bda3cea83ee60b19/src/index.ts#L33
This seems to be responsible for handling jest.mock calls, but is way underspecified. The original (non-minimal code) had a regular function there called block
.
On 0.4.0 everything works as expected
@DreierF the above minimal example works fine for me, you only need to install babel presets typescript if you have jest.mocks because esbuild doesnt support jest.mocks thats why it needs to have babel jest to do it. by the way what version of esbuild-jest you are using?
if you are using [email protected] it only invoke or call babel-jest if you have jest.mock
what i meant is if you are using typescript with "type" and "interface" word meaning its reserve word of typescript
Thanks! I got you now.
I'm aware that type
and interface
are keywords in Typescript, but I thought ESBuild would strip them away during the transpilation to JS.
The thing is that I'm not using jest.mock
at all, but esbuild-jest
believes I do because the code contains the ock(
fragment, which happens to appear at various places in the form of block()
calls in my code, which in turn is a React Router API.
Version 0.4.0 of esbuild-jest
did work without @babel/preset-typescript
, but 0.5.0 needs @babel/preset-typescript
. Would be nice if it wouldn't (as long as jest.mock is not used), but I'm ok with that. IMO it should be documented in the README, that @babel/preset-typescript
is required now when using jest.mock
and the heuristics could be made smarter to only detect actual jest.mock
calls.
@DreierF you only need @babel/preset-typescript if you are using jest.mock with typescript that uses "interface" or "type" if not you dont need it please see this repository https://github.com/aelbore/esbuild-jest-repro
and yes your right it should be documented i will update the README file thanks yup theres a TODO in this esbuild-jest i will use babel transformer to transform jest.mock into functions and then hoist it. it is currently in progress, if you are not using jest.mock i thin [email protected] is a stable one
@DreierF if you are not using jest.mock [email protected]
is pretty much stable :)
@aelbore You shouldn't need @babel/preset-typescript
even if you're using jest.mock with typescript. the babel parser can parse it just fine. This issue appears to be something else.
Here's an additional anecdote:
A test using jest.mock
worked fine on my machine (Mac) but failed on CircleCI with the error above.
Adding a babel.config.js with module.exports = {presets: ["@babel/preset-typescript"]};
fixed it on CircleCI.
I'm on esbuild-jest@^0.5.0
I'm using esbuild-jest@~0.5.0
, and I get this issue when I run jest with the --coverage
flag enabled.
Have the same issue. Sadly.
Isn't the usage of Babel anywhere in the pipeline basically cancels out esbuild
benefits to 0? Did anyone measure an impact?
Ok, I see that it check whether there is ock(
in the file and only then transpires it down using babel. So it shouldn't be that big but some gains will be there probably.
@threepointone this hack is definitely good, but it seems that it doesn't work well with Typescript in all cases as I have exactly the same issue with the different snippet.
import { UrqlQueryResult, UrqlMutationResult } from '../useUrqlEnchanceHook'
const createState = (
props?: Partial<UrqlQueryResult<any> | UrqlMutationResult<any, any>>,
): UrqlQueryResult<any> => {
return {
stale: false,
fetching: false,
error: undefined,
data: null,
...props,
}
}
test('test', () => {})
Doesn't fail.
Adding // jest.mock(
Comment fails it
So this snipped fails:
import { UrqlQueryResult, UrqlMutationResult } from '../useUrqlEnchanceHook'
// jest.mock('
const createState = (
props?: Partial<UrqlQueryResult<any> | UrqlMutationResult<any, any>>,
): UrqlQueryResult<any> => {
return {
stale: false,
fetching: false,
error: undefined,
data: null,
...props,
}
}
test('test', () => {})
With this error:
● Test suite failed to run
TypeError: /home/alos/Projects/LiveFlow/app-frontend/libraries/hooks/common/src/__tests__/useUrqlEnchanceHook.test.ts: Property typeName of TSTypeReference expected node to be of a type ["TSEntityName"] but instead got "MemberExpression"
at Object.validate (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/types/lib/definitions/utils.js:132:11)
at validateField (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/types/lib/validators/validate.js:24:9)
at Object.validate (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/types/lib/validators/validate.js:17:3)
at NodePath._replaceWith (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/traverse/lib/path/replacement.js:179:7)
at NodePath.replaceWith (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/traverse/lib/path/replacement.js:161:8)
at Object.ReferencedIdentifier (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/helper-module-transforms/lib/rewrite-live-references.js:179:14)
at Object.newFn (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/traverse/lib/visitors.js:216:17)
at NodePath._call (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/traverse/lib/path/context.js:55:20)
at NodePath.call (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/traverse/lib/path/context.js:42:17)
at NodePath.visit (../../../common/temp/node_modules/.pnpm/@babel/[email protected]/node_modules/@babel/traverse/lib/path/context.js:92:31)
So by looking at stack trace we can be sure that it's definitely related to Babel and it needs to be fixed somehow.
Managed to get rid of this error by moving babelTransform
hack after the esbuild transform, which is, surely, destroys source maps for all the test files where there is jest.mock
. If there are no mocks, then all good with them.
Like that:
let result = esbuild.transformSync(sources.code, {
loader,
format: (options === null || options === void 0 ? void 0 : options.format) || 'cjs',
target: (options === null || options === void 0 ? void 0 : options.target) || 'es2018',
...(options === null || options === void 0 ? void 0 : options.jsxFactory) ? {
jsxFactory: options.jsxFactory
} : {
},
...(options === null || options === void 0 ? void 0 : options.jsxFragment) ? {
jsxFragment: options.jsxFragment
} : {
},
...sourcemaps
});
if (sources.code.indexOf("ock(") >= 0 || (opts === null || opts === void 0 ? void 0 : opts.instrument)) {
const source = require('./transformer').babelTransform({
sourceText: result.code,
sourcePath: filename,
config,
options: opts
});
result.code = source;
}
So here is my lifehack to you folks. If you want to speedup Jest with esbuild
, just don't, too many hacks to make mocks working :)
Just use this https://github.com/alangpierce/sucrase/tree/main/integrations/jest-plugin instead.
Surcrase is mega quick and is similar to Babel and works no problem with Jest via this plugin. It will require you to hoist jest.mock
calls yourself which is a bit annoying. But there is a PR opened that should fix that.
https://github.com/alangpierce/sucrase/pull/540
Thanks for effort folks, but it's far from being stable :(
a workaround is to avoid named imports of types, so instead of
import { UrqlQueryResult, UrqlMutationResult } from '../useUrqlEnchanceHook'
do
import * as UseUrqlEnchanceHook from '../useUrqlEnchanceHook'
and qualify the usages.
It looks like the source of the failure is here in @babel/helper-module-transforms
, where a ReferencedIdentifier
is matched and replaced with a MemberExpression
(computed by buildImportReference
). Typescript type annotations match ReferencedIdentifier
and are mistakenly replaced. I filed a bug against Babel.
It is pretty weird that the string ock(
triggers this; I stumbled across it with a method named isBlock
.
The issue with ock(
was introduced in this PR: https://github.com/aelbore/esbuild-jest/pull/20/files#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80R33
Have the same issue. Sadly. Isn't the usage of Babel anywhere in the pipeline basically cancels out
esbuild
benefits to 0? Did anyone measure an impact?
kind of hard to avoid when jest depends on babel core itself, feels bad.