Skip to content

How Do I Fix import.meta.resolve() vs require.resolve() When Migrating to ESM in Node.js?

I was migrating my Babel configuration from CommonJS to ESM, and my build immediately broke. The error message was cryptic:

Error: Cannot find module '/path/to/project/file:///path/to/project/node_modules/@ember/decorator-transforms/runtime'

That file:/// prefix in the path looked wrong. My Babel configuration was trying to resolve the @ember/decorator-transforms runtime path, but something was converting it to a URL instead of a proper filesystem path.

Here’s what my babel.config.cjs looked like before the migration:

babel.config.cjs
const path = require('path');
module.exports = function (api) {
api.cache(true);
return {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-proposal-decorators', { version: '2023-11' }],
['@babel/plugin-transform-class-properties', { loose: true }],
[
'@babel/plugin-transform-runtime',
{
helpers: true,
// This line was the culprit
absoluteRuntime: path.dirname(require.resolve('@ember/decorator-transforms/package.json'))
}
]
]
};
};

The problem? require.resolve() doesn’t exist in ESM modules.

My First Attempt: Direct Replacement

I renamed the file to babel.config.mjs and tried the most obvious replacement:

babel.config.mjs
import path from 'path';
export default function (api) {
api.cache(true);
return {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-proposal-decorators', { version: '2023-11' }],
['@babel/plugin-transform-class-properties', { loose: true }],
[
'@babel/plugin-transform-runtime',
{
helpers: true,
// This doesn't work as expected
absoluteRuntime: path.dirname(import.meta.resolve('@ember/decorator-transforms/package.json'))
}
]
]
};
}

This ran without immediate errors, but when I tried to build, I got that weird path with file:/// prefix again. The Babel plugin was receiving a URL string when it expected a filesystem path.

Understanding the Difference

After some digging through Node.js ESM documentation, I found the key difference:

  • require.resolve() returns an absolute filesystem path as a string: /Users/me/project/node_modules/package/file.js
  • import.meta.resolve() returns a file URL string: file:///Users/me/project/node_modules/package/file.js

Many Node.js tools, including Babel, expect traditional filesystem paths, not URLs. That’s why my build was failing—the tools were treating the URL string as a path, resulting in invalid paths like /path/to/project/file:///actual/path.

The Solution: fileURLToPath

The fix required an additional step: converting the URL to a proper filesystem path using fileURLToPath from Node’s built-in url module.

babel.config.mjs
import path from 'path';
import { fileURLToPath } from 'url';
export default function (api) {
api.cache(true);
return {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-proposal-decorators', { version: '2023-11' }],
['@babel/plugin-transform-class-properties', { loose: true }],
[
'@babel/plugin-transform-runtime',
{
helpers: true,
// The correct pattern for ESM
absoluteRuntime: path.dirname(
fileURLToPath(
import.meta.resolve('@ember/decorator-transforms/package.json')
)
)
}
]
]
};
}

The flow now works like this:

  1. import.meta.resolve() returns: file:///Users/me/project/node_modules/@ember/decorator-transforms/package.json
  2. fileURLToPath() converts it to: /Users/me/project/node_modules/@ember/decorator-transforms/package.json
  3. path.dirname() extracts: /Users/me/project/node_modules/@ember/decorator-transforms

This pattern applies to any ESM configuration file where you need to resolve module paths.

Common Migration Scenarios

This same pattern appears in several build tool configurations:

Webpack configuration:

webpack.config.mjs
import { fileURLToPath } from 'url';
export default {
module: {
rules: [
{
test: /\.js$/,
loader: fileURLToPath(import.meta.resolve('babel-loader'))
}
]
}
};

Jest configuration:

jest.config.mjs
import { fileURLToPath } from 'url';
export default {
moduleNameMapper: {
'^@/(.*)$': fileURLToPath(import.meta.resolve('./src/$1'))
}
};

Monorepo plugin resolution:

vite.config.mjs
import { fileURLToPath } from 'url';
export default {
resolve: {
alias: {
'@shared': fileURLToPath(import.meta.resolve('../shared/src'))
}
}
};

Common Mistakes to Avoid

I made several mistakes during this migration:

Mistake 1: Forgetting fileURLToPath entirely

// Wrong - passes URL string to tools expecting paths
const runtimePath = import.meta.resolve('@ember/decorator-transforms/package.json');

Mistake 2: Using import.meta.resolve outside module scope

// Wrong - import.meta only works at module top level
function getConfig() {
return import.meta.resolve('something'); // Error!
}

Mistake 3: Not importing the url module

// Wrong - fileURLToPath is not a global
const path = fileURLToPath(import.meta.resolve('module')); // ReferenceError

Why This Matters

As the Node.js ecosystem moves toward ESM as the default module system (Node.js has treated .js files as ESM when "type": "module" is in package.json since v12), understanding this migration pattern becomes essential. Build tools, CLI tools, and configuration files all need path resolution, and the ESM way differs fundamentally from CommonJS.

The Ember CLI 6.11 release in March 2026 documented this exact issue when their Babel configuration migration guide included this pattern as a required change. If you’re maintaining build configurations or writing tooling that resolves module paths, this pattern will appear repeatedly.

The Complete Migration Pattern

Here’s the complete before-and-after:

Before: CommonJS (babel.config.cjs)
const path = require('path');
module.exports = function(api) {
return {
plugins: [
['babel-plugin', {
runtimePath: path.dirname(require.resolve('some-package/package.json'))
}]
]
};
};
After: ESM (babel.config.mjs)
import path from 'path';
import { fileURLToPath } from 'url';
export default function(api) {
return {
plugins: [
['babel-plugin', {
runtimePath: path.dirname(
fileURLToPath(import.meta.resolve('some-package/package.json'))
)
}]
]
};
}

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments