I am writing Node.js code (in TypeScript) using ESModules and I need access to __dirname
. In order to access the ESM equivalent of __dirname
in CommonJS, I call dirname(fileURLToPath(import.meta.url))
. I am also writing tests in Jest with TypeScript. Using this guide, I set up Babel. When I run the jest
command, I get
const DIRNAME = (0, _path.dirname)((0, _url.fileURLToPath)(import.meta.url));
^^^^
SyntaxError: Cannot use 'import.meta' outside a module
Here are the files I wrote
someCode.ts
:
import { dirname } from "path";
import { fileURLToPath } from "url";
const DIRNAME = dirname(fileURLToPath(import.meta.url));
export const x = 5;
someCode.test.ts
:
import { x } from "./someCode";
it("Returns 5 as the result", () => {
expect(x).toEqual(5);
});
.babelrc.json
:
{
"presets": [
["@babel/preset-env", { "targets": { "node": "current" } }],
"@babel/preset-typescript"
]
}
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node"
},
"include": ["./src/**/*.ts", "./src/**/*.js"]
}
package.json
:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.7",
"@babel/preset-env": "^7.12.7",
"@babel/preset-typescript": "^7.12.7",
"jest": "^26.6.3",
"typescript": "^4.1.2"
}
}
Environment:
package.json
for module versionsimport. meta is an object available inside of an ES module that contains useful information about the environment in which the module runs. import. meta object is extensible, and the host can write any useful information into it.
In your test files, Jest puts each of these methods and objects into the global environment. You don't have to require or import anything to use them.
When you run jest with a jest. config. ts file it will use ts-node to compile that file, then it will pass it to ts-jest which will compile your tests, then it will pass those . js tests to jest to run it.
Jest is a simple, lightweight testing framework that provides a variety of testing capabilities to JavaScript and TypeScript projects. It provides functionality like assertions, mocking, spies, running tests in parallel, prioritizing failed tests, and coverage analysis.
I ran into this issue recently, as well, and I ended up having to:
import.meta
functionality, into it's own, "utils" file/function.jest.mock("/path/to/that/file.ts")
The process will look something like this:
src/
--/someCode.ts
--/__tests__/
/--/--/someCode.test.ts
// someCode.ts
...
const getApiUrl = () => {
...
const url = import.meta.env.SOME_ENV_VAR_HERE;
...
return url;
};
...
The New File Structure
src/
--/someCode.ts
--/utils.js
--/__mocks__/
--/--/utils.ts
--/__tests__/
--/--/someCome.test.ts
and then in the files themselves:
// someCode.ts
...
import { getApiUrl } from "./utils.ts";
...
// utils.js
export const getApiUrl = () => {...};
and for the tests:
// __mocks__/utils.ts
export const getpiUrl = jest.fn(() => 'some.url');
// __tests__/utils.test.ts
...
jest.mock("src/util.ts");
...
describe(...);
We are using import.meta.url
to make use of web workers, for example using the native worker support in Webpack 5. That works great but fails when running the Jest tests.
babel-vite-preset
does not handle this as far as I can tell. The top answer about extracting and mocking the code that uses import.meta
does work but is unwieldy.
I found the currently best solution is to use babel-plugin-transform-import-meta. That simple plugin replaces the import.meta.url
syntax with Node.js code to retrieve the URL to the current file, so it works nicely in tests. Note that you will only want to have this plugin active when running the tests.
I had the same problem and I fix it with a babel plugin: search-and-replace
In my code I changed all import.meta.url
with import_meta_url
And I add the plugin to babel config to change it by import.meta.url
in development and production and by resolve(__dirname, 'workers')
I was able to do so because it matched all my cases, if your import_meta_url
needs to be replaced with different stings in different scenarios you would need to use import_meta_url_1
import_meta_url_2
etc. for instance
finally my babel.config.js
const { resolve } = require('path');
module.exports = (api) => {
api.cache.using(() => process.env.NODE_ENV);
if (process.env.NODE_ENV === 'test') {
return {
presets: ['next/babel'],
plugins: [
[
'search-and-replace',
{
rules: [
{
searchTemplateStrings: true,
search: 'import_meta_url',
replace: resolve(__dirname, 'workers'),
},
],
},
],
],
};
}
return {
presets: ['next/babel'],
plugins: [
[
'search-and-replace',
{
rules: [
{
searchTemplateStrings: true,
search: 'import_meta_url',
replace: 'import.meta.url',
},
],
},
],
],
};
};
GL
I also ran into this issue. I was able to resolve the issue by dynamically inserting a correct value during babel processing. Instead of the search-and-replace
babel plugin that was suggested, I used the codegen macro for babel.
// someCode.ts
import { dirname } from "path";
import { fileURLToPath } from "url";
import codegen from 'codegen.macro';
const DIRNAME = dirname(fileURLToPath(codegen`module.exports = process.env.NODE_ENV === "test" ? "{ADD VALID VALUE FOR TESTS}" : "import.meta.url"`));
export const x = 5;
I found a solution that is so ridiculous i don't even believed when it worked:
Create a commonjs file, like "currentPath.cjs", Inside it put something like:
module.exports = __dirname;
In your module use:
import * as currentPath from "./currentPath.cjs";
console.log(currentPath.default);
And watch the magic happen. PS: Use the path.normalize() before using it on anything.
babel-vite-preset
I think this one is great.
non-official.
you can choose to use it only babel-plugin-transform-vite-meta-env
plugin or use whole preset and set config like below
{
"presets": [
[
"babel-preset-vite",
{
"env": true, // defaults to true
"glob": false // defaults to true
}
]
]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With