Package detail

bidirectional-resolve

Xunnamius162MIT2.0.1

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

inverse, reverse, bidirectional, enhanced

readme

Resolve a package entry point to a file path or a file path to a package entry point!


![Black Lives Matter!][x-badge-blm-image] ![Last commit timestamp][x-badge-lastcommit-image] ![Codecov][x-badge-codecov-image] [![Source license][x-badge-license-image]][x-badge-license-link] [![Uses Semantic Release!][x-badge-semanticrelease-image]][x-badge-semanticrelease-link] ![NPM version][x-badge-npm-image] ![Monthly Downloads][x-badge-downloads-image]


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][4] 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][6]

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][7]

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][8]

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][9]

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][10]

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][x-pkg-cjs-mojito] 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.

<summary>Expand details</summary> 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][x-pkg-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][x-pkg-exports-conditions]. These entries may or may not include: an [exports[ENTRY].types][x-pkg-exports-types-key] condition pointing to a type declaration file for TypeScript and IDEs, a [exports[ENTRY].module][x-pkg-exports-module-key] 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][x-pkg-exports-conditions] not enumerated here. Check the package.json file to see which export conditions are supported. Note that, regardless of the [{ "type": "..." }][x-pkg-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][x-pkg-side-effects-key] key, which is almost always false for optimal tree shaking where appropriate.

License

See LICENSE.

Contributing and Support

[New issues][x-repo-choose-new-issue] 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.

[x-badge-blm-image]: https://xunn.at/badge-blm 'Join the movement!'

[x-badge-codecov-image]: https://img.shields.io/codecov/c/github/Xunnamius/project-utils/main?style=flat-square&token=HWRIOBAAPW&flag=package.main_bidirectional-resolve 'Is this package well-tested?'

[x-badge-downloads-image]: https://img.shields.io/npm/dm/bidirectional-resolve?style=flat-square 'Number of times this package has been downloaded per month'

[x-badge-lastcommit-image]: https://img.shields.io/github/last-commit/Xunnamius/project-utils?style=flat-square 'Latest commit timestamp' [x-badge-license-image]: https://img.shields.io/npm/l/bidirectional-resolve?style=flat-square "This package's source license" [x-badge-license-link]: https://github.com/Xunnamius/project-utils/blob/main/LICENSE [x-badge-npm-image]: https://xunn.at/npm-pkg-version/bidirectional-resolve 'Install this package using npm or yarn!'

[x-badge-semanticrelease-image]: https://xunn.at/badge-semantic-release 'This repo practices continuous integration and deployment!' [x-badge-semanticrelease-link]: https://github.com/semantic-release/semantic-release [x-pkg-cjs-mojito]: https://dev.to/jakobjingleheimer/configuring-commonjs-es-modules-for-nodejs-12ed#publish-only-a-cjs-distribution-with-property-exports [x-pkg-dual-package-hazard]: https://nodejs.org/api/packages.html#dual-package-hazard [x-pkg-exports-conditions]: https://webpack.js.org/guides/package-exports#reference-syntax [x-pkg-exports-module-key]: https://webpack.js.org/guides/package-exports#providing-commonjs-and-esm-version-stateless [x-pkg-exports-types-key]: https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta#packagejson-exports-imports-and-self-referencing [x-pkg-side-effects-key]: https://webpack.js.org/guides/tree-shaking#mark-the-file-as-side-effect-free

[x-pkg-type]: https://github.com/nodejs/node/blob/8d8e06a345043bec787e904edc9a2f5c5e9c275f/doc/api/packages.md#type [x-repo-choose-new-issue]: https://github.com/Xunnamius/project-utils/issues/new/choose

[4]: https://github.com/Xunnamius/symbiote/blob/c3fc1264932eb8224289ef973366fc0cb5435f59/babel.config.cjs#L344-L435

[6]: https://github.com/Xunnamius/project-utils/blob/main/packages/bidirectional-resolve/docs/functions/flattenPackageJsonSubpathMap.md [7]: https://github.com/Xunnamius/project-utils/blob/main/packages/bidirectional-resolve/docs/functions/resolveEntryPointsFromExportsTarget.md [8]: https://github.com/Xunnamius/project-utils/blob/main/packages/bidirectional-resolve/docs/functions/resolveExportsTargetsFromEntryPoint.md [9]: https://github.com/Xunnamius/project-utils/blob/main/packages/bidirectional-resolve/docs/functions/resolveEntryPointsFromImportsTarget.md [10]: https://github.com/Xunnamius/project-utils/blob/main/packages/bidirectional-resolve/docs/functions/resolveImportsTargetsFromEntryPoint.md

changelog

Changelog

All notable changes to this project will be documented in this auto-generated file. The format is based on Conventional Commits; this project adheres to Semantic Versioning.


@-xun/project@2.0.0 (2025-05-30)

💥 BREAKING CHANGES 💥

  • Minimum supported node version is now 20.18.0

⚙️ Build System

  • deps: bump @-xun/project-fs from 1.2.0 to 2.0.0 (44d7782)
  • deps: bump @-xun/project-types from 1.0.4 to 2.0.0 (e188fa0)
  • deps: bump core-js from 3.41.0 to 3.42.0 (af8fbc3)
  • deps: bump internal monorepo interdependencies to latest versions (268098a)
  • package: drop support for node\@18 (19084da)


🏗️ Patch @-xun/project@2.0.2 (2025-06-01)

⚙️ Build System

  • deps: bump internal monorepo interdependencies to latest versions (e7b2173)


🏗️ Patch @-xun/project@2.0.1 (2025-06-01)

⚙️ Build System

  • deps: bump internal monorepo interdependencies to latest versions (3f26464)


@-xun/project@1.0.0 (2025-02-03)

⚙️ Build System

  • Integrate externalized @-xun/memoize and bpma packages (e672064)
  • release: factor @-xun/project multirepo out from symbiote (880d8ce)


🏗️ Patch @-xun/project@1.0.5 (2025-03-19)

⚙️ Build System

  • deps: bump @-xun/project-fs from 1.0.5 to 1.1.0 (3890403)
  • deps: bump @-xun/project-fs from 1.1.0 to 1.2.0 (366cf7a)
  • deps: bump core-js from 3.40.0 to 3.41.0 (f9ccc15)


🏗️ Patch @-xun/project@1.0.4 (2025-03-13)

⚙️ Build System

  • deps: bump @-xun/project-fs from 1.0.4 to 1.0.5 (14698d1)
  • deps: bump @-xun/project-graph from 1.0.3 to 1.0.4 (8ab5e22)
  • deps: bump @-xun/project-types from 1.0.3 to 1.0.4 (d113f0b)


🏗️ Patch @-xun/project@1.0.3 (2025-02-24)

⚙️ Build System

  • deps: bump @-xun/project-fs from 1.0.2 to 1.0.3 (33c2322)
  • packages/graph: add missing "@types/semver" package (6a643c7)


🏗️ Patch @-xun/project@1.0.2 (2025-02-17)

⚙️ Build System

  • Update @-xun/run to 1.0.1 (ff90125)


🏗️ Patch @-xun/project@1.0.1 (2025-02-06)

⚙️ Build System

  • husky: skip slow tests (80a5ed7)
  • post-npm-install: add common-dummies post-install to npm-post-install (2747383)