Node v13 / Jest / ES6 — native support for modules without babel or esm - node.js

Is it possible to test ES6 Modules with Jest without esm or babel? Since node v13 supports es6 natively have tried:
//package.json
{
…
"type": "module"
…
}
//__tests__/a.js
import Foo from '../src/Foo.js';
$ npx jest
Jest encountered an unexpected token
…
Details:
/home/node/xxx/__tests__/a.js:1
import Foo from '../src/Foo.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
When babel is added a transpiler, it works, but can es6 modules be used natively as well?

Yes, it is possible from jest#25.4.0. From this version, there is a native support of esm, so you will not have to transpile your code with babel anymore.
It is not documented yet, but according to this issue you have to do 3 easy steps to achieve that (At the time of writing this answer):
Make sure you don't transform away import statements by setting transform: {} in your jest config file
Run node#^12.16.0 || >=13.2.0 with --experimental-vm-modules flag
Run your test with jest-environment-node or jest-environment-jsdom-sixteen.
So your jest config file should contain at least this:
export default {
testEnvironment: 'jest-environment-node',
transform: {}
...
};
And to set --experimental-vm-modules flag, you will have to run Jest from package.json as follows (I hope this will change in the future):
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
I hope, this answer was helpful to you.

Note that this is is still experimental, but we have documented how to test this, so there's hopefully less confusion.
https://jestjs.io/docs/en/ecmascript-modules
The steps in https://stackoverflow.com/a/61653104/1850276 are correct

I followed the tips provided in the accepted answer, but I added the property "type": "module" in my package.json in order to jest works properly. This is what I done:
In package.json:
"devDependencies": {
"jest": "^26.1.0",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-environment-node": "^26.1.0"
},
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
},
"type": "module",
"jest": {
"transform": {},
"testEnvironment": "jest-environment-jsdom-sixteen"
}

To run jest from "jest" extension in VSCode with "--experimental-vm-modules" flags, put this config in your global or workspaces settings.json:
"jest.nodeEnv": {
"NODE_OPTIONS": "--experimental-vm-modules"
}

In addition to #Radovan Kuka's answer, here's how to run Jest with ES modules, using npx:
"test:monitoring": "npx --node-arg=--experimental-vm-modules jest -f monitoring.test.js --detectOpenHandles",
The benefit is that one doesn't need to provide the absolute node_modules path.

Without Babel, here's a complete, minimal example that works on recent Jest versions. Run with npm test.
$ tree -I node_modules
.
├── package.json
├── src
│   └── foo.js
└── __tests__
└── foo.spec.js
package.json:
{
"type": "module",
"scripts": {
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
},
"devDependencies": {
"jest": "^29.3.1"
}
}
src/foo.js:
export const bar = () => 42;
__tests__/foo.spec.js:
import {bar} from "../src/foo";
describe("foo.bar()", () => {
it("should return 42", () => {
expect(bar()).toBe(42);
});
});
The secret sauce is in the package.json: "type": "module" and NODE_OPTIONS=--experimental-vm-modules jest.
If you want to add a mock, it's a bit complicated. See this answer.

Related

Jest fails when script imports external library

I just started testing with Jest for the first time. This is a very simple Typescript utils library. I basically followed the installation guide, and my first two tests passed. Yay. The next function I tested imported a few functions from lodash-es, and failed (error below).
This is how I added jest:
pnpm add -D jest ts-jest #types/jest
pnpx ts-jest config:init
My jest.config.js file, as generated by pnpx ts-jest config:init, except that I had to change the second line to export default, because my package.json specifies this to be a module.
/** #type {import('ts-jest').JestConfigWithTsJest} */
export default {
preset: 'ts-jest',
testEnvironment: 'node',
transformIgnorePatterns: [
"node_modules/(?!(lodash-es)/)"
],
moduleNameMapper: {
'lodash-es': '../node_modules/lodash-es/lodash.js',
},
}
I've tried adding transformIgnorePatterns and moduleNameMapper, based on others with similar issues. But no luck.
The error message:
D:\Dev\NodeJs\vexna\packages\util>pnpm test
> #vexna/util#1.0.0 test D:\Dev\NodeJs\vexna\packages\util
> jest
PASS test/reverseString.test.ts
PASS test/hash.test.ts
FAIL test/uid.test.ts
● Test suite failed to run
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Details:
D:\Dev\NodeJs\vexna\node_modules\.pnpm\lodash-es#4.17.21\node_modules\lodash-es\lodash.js:10
export { default as add } from './add.js';
^^^^^^
SyntaxError: Unexpected token 'export'
> 1 | import { now, random, padStart, padEnd } from "lodash-es"
| ^
2 | import { reverseString } from "./reverseString"
3 |
4 |
at Runtime.createScriptFromCode (../../node_modules/.pnpm/jest-runtime#29.4.1/node_modules/jest-runtime/build/index.js:1598:14)
at Object.<anonymous> (src/uid.ts:1:1)
at Object.<anonymous> (test/uid.test.ts:1:1)
Test Suites: 1 failed, 2 passed, 3 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.494 s
Ran all test suites.
 ELIFECYCLE  Test failed. See above for more details.
A few things, this project exists in pnpm monorepo, in case it matters.
This is the package.json:
{
"name": "#vexna/util",
"version": "1.0.0",
"description": "Generic utilities, uses lodash",
"private": true,
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"scripts": {
"build": "rimraf dist && tsc && rollup -c rollup.config.js",
"test": "jest",
"pretest": "npm run build"
},
"devDependencies": {
"#rollup/plugin-typescript": "^11.0.0",
"#types/jest": "^29.4.0",
"#types/lodash-es": "^4.17.6",
"jest": "^29.4.1",
"lodash-es": "^4.17.21",
"rimraf": "^4.1.2",
"rollup": "^3.12.1",
"ts-jest": "^29.0.5",
"typescript": "^4.9.5"
},
"files": [
"dist"
],
"peerDependencies": {
"lodash": "^4.17.21"
}
}
I suspect that perhaps Jest goes off into the boonies because I have externalized lodash?
Or perhaps I should add Babel? But could just as well be something else completely.
Some help is much appreciated!
Use Vitest.
Jest is quite painful to install correctly, whereas Vitest works out of the box without any config. This is my personal experience. It took me days to get Jest to sort-of work. I had Vitest working immediately after installation.
I don't want to hate on Jest, I actually think it's a wonderful and intuitive testing tool. But ultimately Vitest "Just Works" (tm), and you can use the same simple Jest style API we all have come to love.
Steps:
pnpm remove jest ts-jest #types/jest
pnpm add -D vite vitest
Delete jest.config.js, and create vite.config.ts:
/// <reference types="vitest" />
import { defineConfig } from 'vite'
export default defineConfig({
test: {
/* for example, use global to avoid globals imports (describe, test, expect): */
// globals: true,
},
})
Add to your tests:
import { assert, expect, test } from 'vitest'
Update your package.json:
"test": "vitest",

Nodemon and Jest, only supported when running Babel asynchronously

I'm trying to run Jest and this error keeps preventing me from running any tests:
Error while loading config -
You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously.
at loadCjsOrMjsDefault (node_modules/#babel/core/lib/config/files/module-types.js:59:13)
at loadCjsOrMjsDefault.next (<anonymous>)
at readConfigJS (node_modules/#babel/core/lib/config/files/configuration.js:174:47)
at readConfigJS.next (<anonymous>)
at Function.<anonymous> (node_modules/#babel/core/lib/gensync-utils/async.js:16:3)
at evaluateSync (node_modules/gensync/index.js:251:28)
at Function.sync (node_modules/gensync/index.js:89:14)
at sync (node_modules/#babel/core/lib/gensync-utils/async.js:56:25)
at sync (node_modules/gensync/index.js:182:19)
I'm using nodemon and sucrase to run my server, if that's relevant.
My babel config
module.exports = {
presets: [
[
'#babel/preset-env',
{
targets: {
node: 'current'
}
}
]
]
};
My package.json
{
"type": "module",
"scripts": {
"dev": "nodemon src/server.js",
"test": "jest"
}
}
I think the problem is that your package.json says you are using ES6 modules, but your Babel config is using module.exports which is CommonJS (not ES6 modules).
I renamed my babel.config.js to babel.config.cjs and this fixed the problem. I guess you could also change module.exports to export default but I haven't tried this.

Where does jest localStorage implementation come from?

I've created a small node app:
# package.json
{
"name": "testapp",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "jest"
},
"devDependencies": {
"jest": "^24.9.0"
}
}
# index.test.js
test('localStorage', () => {
localStorage.setItem('foo', 'bar');
expect(localStorage.getItem('foo')).toBe('bar');
});
npm test runs without errors.
Searching on StackOverflow for "localStorage jest" leads to questions that deal with mocking localStorage; and I was under impression that Storage is a browser API that is not available in node.
So how come this works?
This is because by default jest uses jsdom as its own test environment (note that you can modify this behaviour).
I think jest is switched to new version of jsdom library

Use flag `--experimental-worker` with babel-node

babel-node --experimental-worker app.js
Using this command for starting my project in development mode. Output is:
error: unknown option--experimental-worker'
config .babelrc:
{ "presets": [ ["env", { "targets": { "node": "current" } }], "stage-0", "flow" ], "plugins": [ "transform-decorators-legacy", "transform-flow-strip-types" ] }
This flag is needed to use worker threads. Using babel 6.26
I just ran into this today and replied to the issue on GitHub here. I've pasted my fix below:
I was using Nodemon, and it turns out that there's an option to
include environment variables as NODE_OPTIONS in the nodemon.json
file. This did the trick for me:
{
"watch": ["server"],
"ext": "js",
"env": {
"NODE_OPTIONS": "--experimental-worker"
},
"exec": "babel-node server/server.js"
}
How to integrate Nodemon + worker_threads into a normal NodeJS app:
Set up Nodemon
Add a nodemon.json file to the root folder (example here)
Add this to nodemon.json:
"env": {
"NODE_OPTIONS": "--experimental-worker"
}
If you're setting up Nodemon for the first time, I found
this tutorial very helpful.
The idea is to split your command into two phases:
Phase 1:
babel app.js --out-file app-compiled.js
And phase 2:
node --experimental-worker app-compiled.js
In npm scripts you can then combine the two:
"scripts": {
"pre-build": "babel ./app.js --out-file ./app-compiled.js",
"serve": "yarn pre-build && node --experimental-worker ./app-compiled.js"
}
It not actually for me already. I am refused to use nodemon and run my code with command
node --experimental-worker -r babel-register $NODE_DEBUG_OPTION app.js
It`s help me use exeprimental workers with babel, but with nodemon - not

Setup Babel/Browserify build for (private) npm module

I have an npm module my_lib that is providing some objects I would like available for use by several downstream modules. my_lib is written in ES6, and uses babel to transpile. It lives in a private github repo accessible to the building hosts of the downstream modules. It is important that the downstream modules consuming my_lib are able to import symbols from my_lib like any other node module.
I would like to be able to write import { libA, libB } from 'my_lib'; in any of the downstream modules and have libA defined as expected by my_lib, assuming that downstream module's package.json includes in dependencies an entry like "my_lib": "orgname/reponame#branchname"
I have an entry in my_lib package.json under scripts like so :
"postinstall" : "npm run build",
"build": "mkdir ./dist && browserify ./src/index.js -t babelify -t envify -o index.js",`
where ./src/index.js is
// Entry point for bundling
export * as libB from "../sublib1/adapter";
export * as libA from "../sublib2/adapter";
If necessary, I could call import {libA, libB} from my_lib/dist/bundle, though this is somewhat undesirable. The main thing is to make all the names I'm exporting from my_lib available in the downstream modules. I'm probably confused about what's going on with browserify and babel. I'm perfectly happy to change the postinstall/build hooks to whatever form is more convenient.
Currently, in the downstream package (an express.js app), I have the line import * as adapters from my_lib; Running npm install on the express app completes without error. I see the index.js file I expect in node_modules/my_lib. When I break in my app after the import (or require for that matter), the adapters object exists, but none of the libB or libA on the object. It seems to be a pure vanilla JS object with no members but __proto__.
Normally in this case i will just use babel and avoid browserify. I use something like this in package.json:
{
"main": "dist/index.js",
"scripts": {
"postinstall": "babel src/ -d dist/"
},
"dependencies": {
"babel-cli": "^6.9.0",
"babel-preset-es2015": "^6.9.0"
}
}
Notice that the main entry point for the package is pointing to dist and not to src. This is to make import my_lib and require('my_lib') (both work well) to use the compiled version of the library.
Additionally this is the content of the .babelrc file that it is side by side with package.json
{
"presets": ["es2015"]
}
I use babel and browserify together.
My package.json
{
"name": "my-project",
"version": "1.0.0",
"scripts": {
"build:js": "watchify assets/src/js/main_1.js -o 'exorcist assets/dist/js/main_1.js.map > assets/dist/js/main_1.js' -d -t [babelify --presets [latest]]",
"build:scss": "node-sass -w -r assets/src/scss/ -o assets/dist/css/",
"build": "npm run build:scss & npm run build:js"
},
"devDependencies": {
"babel-cli": "^6.0.0",
"babel-preset-latest": "^6.16.0",
"babelify": "^7.3.0",
"browserify": "^13.1.1",
"exorcist": "^0.4.0",
"node-sass": "^4.5.0",
"watchify": "^3.7.0"
},
"browserify": {
"transform": [
"babelify"
]
}
}
Actual version of package.json: https://gist.github.com/artamonovdev/5f24aaca504e4d1b299bba0413f0a57d

Resources