Big update!Introducing GitHub Bot Commands. Learn more
Log inBook a demo


Package Overview
File Explorer

Advanced tools


A tiny (737b), correct, general-purpose, and configurable "exports" resolver without file-system reliance


Version published
Weekly downloads
decreased by-10.65%

Weekly downloads





  • Add unsafe option (#12, #13): 0b69d12 Note: This option should not be used by most consumers, but is made available for edge cases. As the name suggests, this is "unsafe" and breaks away from the default Node resolve conditions.


  • Update module size: a98d57a

Full Changelog:



resolve.exports CI codecov

A tiny (737b), correct, general-purpose, and configurable "exports" resolver without file-system reliance


Hopefully, this module may serve as a reference point (and/or be used directly) so that the varying tools and bundlers within the ecosystem can share a common approach with one another as well as with the native Node.js implementation.

With the push for ESM, we must be very careful and avoid fragmentation. If we, as a community, begin propagating different dialects of "exports" resolution, then we're headed for deep trouble. It will make supporting (and using) "exports" nearly impossible, which may force its abandonment and along with it, its benefits.

Let's have nice things.



$ npm install resolve.exports


Please see /test/ for examples.

import { resolve, legacy } from 'resolve.exports'; const contents = { "name": "foobar", "module": "dist/module.mjs", "main": "dist/require.js", "exports": { ".": { "import": "./dist/module.mjs", "require": "./dist/require.js" }, "./lite": { "worker": { "browser": "./lite/worker.brower.js", "node": "./lite/worker.node.js" }, "import": "./lite/module.mjs", "require": "./lite/require.js" } } }; // Assumes `.` as default entry // Assumes `import` as default condition resolve(contents); //=> "./dist/module.mjs" // entry: nullish === "foobar" === "." resolve(contents, 'foobar'); //=> "./dist/module.mjs" resolve(contents, '.'); //=> "./dist/module.mjs" // entry: "foobar/lite" === "./lite" resolve(contents, 'foobar/lite'); //=> "./lite/module.mjs" resolve(contents, './lite'); //=> "./lite/module.mjs" // Assume `require` usage resolve(contents, 'foobar', { require: true }); //=> "./dist/require.js" resolve(contents, './lite', { require: true }); //=> "./lite/require.js" // Throws "Missing <entry> export in <name> package" Error resolve(contents, 'foobar/hello'); resolve(contents, './hello/world'); // Add custom condition(s) resolve(contents, 'foobar/lite', { conditions: ['worker'] }); // => "./lite/worker.node.js" // Toggle "browser" condition resolve(contents, 'foobar/lite', { conditions: ['worker'], browser: true }); // => "./lite/worker.browser.js" // --- // Legacy // --- // prefer "module" > "main" (default) legacy(contents); //=> "dist/module.mjs" // customize fields order legacy(contents, { fields: ['main', 'module'] }); //=> "dist/require.js"


resolve(pkg, entry?, options?)

Returns: string or undefined

Traverse the "exports" within the contents of a package.json file.
If the contents does not contain an "exports" map, then undefined will be returned.

Successful resolutions will always result in a string value. This will be the value of the resolved mapping itself – which means that the output is a relative file path.

This function may throw an Error if:

  • the requested entry cannot be resolved (aka, not defined in the "exports" map)
  • an entry was resolved but no known conditions were found (see options.conditions)

Type: object
Required: true

The package.json contents.


Type: string
Required: false
Default: . (aka, root)

The desired target entry, or the original import path.

When entry is not a relative path (aka, does not start with '.'), then entry is given the './' prefix.

When entry begins with the package name (determined via the value), then entry is truncated and made relative.

When entry is already relative, it is accepted as is.


Assume we have a module named "foobar" and whose pkg contains "name": "foobar".

entry valuetreated asreason
null / undefined'.'default
'.''.'value was relative
'foobar''.'value was
'foobar/lite''./lite'value had prefix
'./lite''./lite'value was relative
'lite''./lite'value was not relative & did not have prefix

Type: boolean
Default: false

When truthy, the "require" field is added to the list of allowed/known conditions.

When falsey, the "import" field is added to the list of allowed/known conditions instead.


Type: boolean
Default: false

When truthy, the "browser" field is added to the list of allowed/known conditions.


Type: string[]
Default: []

Provide a list of additional/custom conditions that should be accepted when seen.

Important: The order specified within options.conditions does not matter.
The matching order/priority is always determined by the "exports" map's key order.

For example, you may choose to accept a "production" condition in certain environments. Given the following pkg content:

const contents = { // ... "exports": { "worker": "./index.worker.js", "require": "./index.require.js", "production": "./", "import": "./index.import.mjs", } }; resolve(contents, '.'); //=> "./index.import.mjs" resolve(contents, '.', { conditions: ['production'] }); //=> "./" resolve(contents, '.', { conditions: ['production'], require: true, }); //=> "./index.require.js" resolve(contents, '.', { conditions: ['production', 'worker'], require: true, }); //=> "./index.worker.js" resolve(contents, '.', { conditions: ['production', 'worker'] }); //=> "./index.worker.js"

Type: boolean
Default: false

Important: You probably do not want this option!
It will break out of Node's default resolution conditions.

When enabled, this option will ignore all other options except options.conditions. This is because, when enabled, options.unsafe does not assume or provide any default conditions except the "default" condition.

resolve(contents); //=> Conditions: ["default", "import", "node"] resolve(contents, { unsafe: true }); //=> Conditions: ["default"] resolve(contents, { unsafe: true, require: true, browser: true }); //=> Conditions: ["default"]

In other words, this means that trying to use options.require or options.browser alongside options.unsafe will have no effect. In order to enable these conditions, you must provide them manually into the options.conditions list:

resolve(contents, { unsafe: true, conditions: ["require"] }); //=> Conditions: ["default", "require"] resolve(contents, { unsafe: true, conditions: ["browser", "require", "custom123"] }); //=> Conditions: ["default", "browser", "require", "custom123"]

legacy(pkg, options?)

Returns: string or undefined

Also included is a "legacy" method for resolving non-"exports" package fields. This may be used as a fallback method when for when no "exports" mapping is defined. In other words, it's completely optional (and tree-shakeable).

You may customize the field priority via options.fields.

When a field is found, its value is returned as written.
When no fields were found, undefined is returned. If you wish to mimic Node.js behavior, you can assume this means 'index.js' – but this module does not make that assumption for you.


Type: boolean or string
Default: false

When truthy, ensures that the 'browser' field is part of the acceptable fields list.

Important: If your custom options.fields value includes 'browser', then your order is respected.
Otherwise, when truthy, options.browser will move 'browser' to the front of the list, making it the top priority.

When true and "browser" is an object, then legacy() will return the the entire "browser" object.

You may also pass a string value, which will be treated as an import/file path. When this is the case and "browser" is an object, then legacy() may return:

  • false – if the package author decided a file should be ignored; or
  • your options.browser string value – but made relative, if not already

See the `"browser" field specification for more information.


Type: string[]
Default: ['module', 'main']

A list of fields to accept. The order of the array determines the priority/importance of each field, with the most important fields at the beginning of the list.

By default, the legacy() method will accept any "module" and/or "main" fields if they are defined. However, if both fields are defined, then "module" will be returned.

const contents = { "name": "...", "worker": "worker.js", "module": "module.mjs", "browser": "browser.js", "main": "main.js", } legacy(contents); // fields = [module, main] //=> "module.mjs" legacy(contents, { browser: true }); // fields = [browser, module, main] //=> "browser.mjs" legacy(contents, { fields: ['missing', 'worker', 'module', 'main'] }); // fields = [missing, worker, module, main] //=> "worker.js" legacy(contents, { fields: ['missing', 'worker', 'module', 'main'], browser: true, }); // fields = [browser, missing, worker, module, main] //=> "browser.js" legacy(contents, { fields: ['module', 'browser', 'main'], browser: true, }); // fields = [module, browser, main] //=> "module.mjs"


MIT © Luke Edwards



What is resolve.exports?

A tiny (737b), correct, general-purpose, and configurable "exports" resolver without file-system reliance

Is resolve.exports popular?

The npm package resolve.exports receives a total of 9,269,143 weekly downloads. As such, resolve.exports popularity was classified as popular.

Is resolve.exports well maintained?

We found that resolve.exports demonstrated a not healthy version release cadence and project activity because the last version was released a year ago.It has 1 open source maintainer collaborating on the project.

Last updated on 15 Oct 2021

Did you know?

Socket installs a Github app to automatically flag issues on every pull request and report the health of your dependencies. Find out what is inside your node modules and prevent malicious activity before you update the dependencies.

Install Socket


Subscribe to our newsletter

Get open source security insights delivered straight into your inbox. Be the first to learn about new features and product updates.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc