npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

bidirectional-resolve

v2.0.3

Published

Resolve a package entry point to a file path (like require.resolve/import.meta.resolve) OR a file path to a package entry point

Downloads

147

Readme

Black Lives Matter! Last commit timestamp Codecov Source license Uses Semantic Release!

NPM version Monthly Downloads

bidirectional-resolve

This package allows you to resolve a given package entry point (e.g. mdast-util-from-markdown in import('mdast-util-from-markdown')) into a file path (e.g. ./node_modules/mdast-util-from-markdown/lib/index.js).

import {
  flattenPackageJsonSubpathMap,
  resolveExportsTargetsFromEntryPoint
} from 'bidirectional-resolve';

const entrypoint = 'mdast-util-from-markdown';

const { exports: packageJsonExports } = await readJsonFile(
  // There are several ways to grab a package's package.json file
  `${entrypoint}/package.json`
);

const flatExports = flattenPackageJsonSubpathMap({ map: packageJsonExports });

const nodeModulesPaths = resolveExportsTargetsFromEntryPoint({
  flattenedExports: flatExports,
  entrypoint,
  conditions: ['types', 'require', 'import', 'node']
});

console.log(nodeModulesPaths); // => ['./node_modules/mdast-util-from-markdown/lib/index.js']

This is similar to what is returned by require.resolve in CJS contexts, or import.meta.resolve in ESM contexts, and there are several other libraries that accomplish some form of this.

What makes bidirectional-resolve special is that, unlike prior art, it can also reverse a given file path (e.g. ./node_modules/mdast-util-from-markdown/lib/index.js) back into an entry point (e.g. mdast-util-from-markdown).

import {
  flattenPackageJsonSubpathMap,
  resolveEntryPointsFromExportsTarget
} from 'bidirectional-resolve';

const precariousNodeModulesImportPath =
  './node_modules/mdast-util-from-markdown/lib/index.js';

const { exports: packageJsonExports } = await readJsonFile(
  await packageUp({ cwd: path.dirname(precariousNodeModulesImportPath) })
);

const flatExports = flattenPackageJsonSubpathMap({ map: packageJsonExports });

const entrypoints = resolveEntryPointsFromExportsTarget({
  flattenedExports: flatExports,
  precariousNodeModulesImportPath,
  conditions: ['types', 'require', 'import', 'node']
});

console.log(entrypoints); // => ['mdast-util-from-markdown']

As the above examples demonstrate, bidirectional-resolve supports bidirectional conditional resolution of entry points in both exports and imports package.json fields.

Deriving a package's entry point from one of its internal file paths satisfies a variety of use cases. For instance, bidirectional-resolve can be used to work around strange behavior in the TypeScript compiler—behavior exhibited since version 3.9 (2020) and still happening as of 5.7 (2025)—where tsc sometimes emits definition files containing relative paths precariously pointing to files inside the nearest node_modules directory.

This is not ideal for several reasons, including the fact that package managers like NPM frequently hoist packages in unpredictable ways, especially in monorepos, which will silently break these hardcoded import paths. As part of a post-emit step, bidirectional-resolve can be used to turn these hardcoded paths back into their more resilient entrypoint forms.


Install

To install:

npm install bidirectional-resolve

Usage

This package exports five functions:

flattenPackageJsonSubpathMap

API reference

Flattens entry points within a package.json imports/exports map into a one-dimensional array of subpath-target mappings.

Each resolver function consumes a flattened array of subpath mappings. This function takes the pain out of generating such mappings.

Example

const flattenedExports = flattenPackageJsonSubpathMap({
  map: packageJson.exports
});

resolveEntryPointsFromExportsTarget

API reference

Given target and conditions, this function returns an array of zero or more entry points that are guaranteed to resolve to target when the exact conditions are active in the runtime. This is done by reverse-mapping target using exports from package.json. exports is assumed to be valid.

Entry points are sorted in the order they're encountered with the caveat that exact subpaths always come before subpath patterns. Note that, if target contains one or more asterisks, the subpaths returned by this function will also contain an asterisk.

The only other time this function returns a subpath with an asterisk is if the subpath is a "many-to-one" mapping; that is: the subpath has an asterisk but its target does not.

For instance:

{
  "exports": {
    "many-to-one-subpath-returned-with-asterisk-1/*": "target-with-no-asterisk.js",
    "many-to-one-subpath-returned-with-asterisk-2/*": null
  }
}

In this case, the asterisk can be replaced with literally anything and it would still match. Hence, the replacement is left up to the caller.

Example

const entrypoints = resolveEntryPointsFromExportsTarget({
  flattenedExports,
  target,
  conditions,
  includeUnsafeFallbackTargets,
  replaceSubpathAsterisks
});

resolveExportsTargetsFromEntryPoint

API reference

Given entryPoint and conditions, this function returns an array of zero or more targets that entryPoint is guaranteed to resolve to when the exact conditions are active in the runtime. This is done by mapping entryPoint using exports from package.json. exports is assumed to be valid.

Example

const targets = resolveExportsTargetsFromEntryPoint({
  flattenedExports,
  entryPoint,
  conditions,
  includeUnsafeFallbackTargets
});

resolveEntryPointsFromImportsTarget

API reference

Given target and conditions, this function returns an array of zero or more entry points that are guaranteed to resolve to target when the exact conditions are active in the runtime. This is done by reverse-mapping target using imports from package.json. imports is assumed to be valid.

Entry points are sorted in the order they're encountered with the caveat that exact subpaths always come before subpath patterns. Note that, if target contains one or more asterisks, the subpaths returned by this function will also contain an asterisk.

The only other time this function returns a subpath with an asterisk is if the subpath is a "many-to-one" mapping; that is: the subpath has an asterisk but its target does not.

For instance:

{
  "imports": {
    "many-to-one-subpath-returned-with-asterisk-1/*": "target-with-no-asterisk.js",
    "many-to-one-subpath-returned-with-asterisk-2/*": null
  }
}

In this case, the asterisk can be replaced with literally anything and it would still match. Hence, the replacement is left up to the caller.

Example

const entrypoints = resolveEntryPointsFromImportsTarget({
  flattenedImports,
  target,
  conditions,
  includeUnsafeFallbackTargets,
  replaceSubpathAsterisks
});

resolveImportsTargetsFromEntryPoint

API reference

Given entryPoint and conditions, this function returns an array of zero or more targets that entryPoint is guaranteed to resolve to when the exact conditions are active in the runtime. This is done by mapping entryPoint using imports from package.json. imports is assumed to be valid.

Example

const targets = resolveImportsTargetsFromEntryPoint({
  flattenedImports,
  entryPoint,
  conditions,
  includeUnsafeFallbackTargets
});

Appendix

Further documentation can be found under docs/.

Published Package Details

This is a CJS2 package with statically-analyzable exports built by Babel for use in Node.js versions that are not end-of-life. For TypeScript users, this package supports both "Node10" and "Node16" module resolution strategies.

That means both CJS2 (via require(...)) and ESM (via import { ... } from ... or await import(...)) source will load this package from the same entry points when using Node. This has several benefits, the foremost being: less code shipped/smaller package size, avoiding dual package hazard entirely, distributables are not packed/bundled/uglified, a drastically less complex build process, and CJS consumers aren't shafted.

Each entry point (i.e. ENTRY) in package.json's exports[ENTRY] object includes one or more export conditions. These entries may or may not include: an exports[ENTRY].types condition pointing to a type declaration file for TypeScript and IDEs, a exports[ENTRY].module condition pointing to (usually ESM) source for Webpack/Rollup, a exports[ENTRY].node and/or exports[ENTRY].default condition pointing to (usually CJS2) source for Node.js require/import and for browsers and other environments, and other conditions not enumerated here. Check the package.json file to see which export conditions are supported.

Note that, regardless of the { "type": "..." } specified in package.json, any JavaScript files written in ESM syntax (including distributables) will always have the .mjs extension. Note also that package.json may include the sideEffects key, which is almost always false for optimal tree shaking where appropriate.

License

See LICENSE.

Contributing and Support

New issues and pull requests are always welcome and greatly appreciated! 🤩 Just as well, you can star 🌟 this project to let me know you found it useful! ✊🏿 Or buy me a beer, I'd appreciate it. Thank you!

See CONTRIBUTING.md and SUPPORT.md for more information.

Contributors

See the table of contributors.