A tiny, correct, general-purpose, and configurable "exports" and "imports" resolver without file-system reliance
npx @tessl/cli install tessl/npm-resolve-exports@2.0.0Resolve.exports is a tiny (952b), correct, general-purpose, and configurable "exports" and "imports" resolver for Node.js package.json fields without file-system reliance. It provides spec-compliant resolution of module paths based on conditional exports, supporting both CommonJS and ES modules.
npm install resolve.exportsimport { resolve, exports, imports, legacy } from "resolve.exports";For CommonJS:
const { resolve, exports, imports, legacy } = require("resolve.exports");import { resolve, exports } from "resolve.exports";
// Example package.json-like object
const pkg = {
name: "my-package",
exports: {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.js"
}
}
};
// Resolve main entry point for ES modules
const result = resolve(pkg, ".", { require: false });
console.log(result); // ["./dist/index.mjs"]
// Resolve specific export for CommonJS
const utilsResult = exports(pkg, "./utils", { require: true });
console.log(utilsResult); // ["./dist/utils.js"]The primary entry point that automatically routes to exports() or imports() based on the entry value.
/**
* Convenience helper that automatically routes to exports() or imports() based on input value
* @param pkg - Package.json-like object containing exports/imports
* @param input - Target entry identifier (defaults to ".")
* @param options - Resolution options
* @returns Array of resolved paths or void if no match
*/
function resolve<T = Package>(
pkg: T,
input?: string,
options?: Options
): Imports.Output | Exports.Output | void;Resolves package.json "exports" field mappings according to Node.js specification.
/**
* Resolves package.json "exports" field mappings
* @param pkg - Package.json-like object containing exports
* @param input - Export specifier to resolve (defaults to ".")
* @param options - Resolution options
* @returns Array of resolved paths or void if no match
* @throws Error if entry not found or no matching conditions
*/
function exports<T = Package>(
pkg: T,
input?: string,
options?: Options
): Exports.Output | void;Usage Example:
const pkg = {
name: "example",
exports: {
".": {
"node": "./dist/node.js",
"browser": "./dist/browser.js",
"default": "./dist/index.js"
},
"./feature": "./dist/feature.js"
}
};
// Resolve for browser environment
const browserPath = exports(pkg, ".", { browser: true });
console.log(browserPath); // ["./dist/browser.js"]
// Resolve specific feature
const featurePath = exports(pkg, "./feature");
console.log(featurePath); // ["./dist/feature.js"]Resolves package.json "imports" field mappings for internal package imports starting with "#".
/**
* Resolves package.json "imports" field mappings
* @param pkg - Package.json-like object containing imports
* @param input - Import specifier (must start with "#")
* @param options - Resolution options
* @returns Array of resolved paths or void if no match
* @throws Error if target not found or no matching conditions
*/
function imports<T = Package>(
pkg: T,
input: string,
options?: Options
): Imports.Output | void;Usage Example:
const pkg = {
name: "example",
imports: {
"#utils": {
"node": "./src/utils-node.js",
"browser": "./src/utils-browser.js"
},
"#config/*": "./config/*.json"
}
};
// Resolve internal import
const utilsPath = imports(pkg, "#utils", { browser: true });
console.log(utilsPath); // ["./src/utils-browser.js"]
// Resolve wildcard import
const configPath = imports(pkg, "#config/database");
console.log(configPath); // ["./config/database.json"]Resolves legacy package.json fields ("main", "module", "browser") as fallback when exports are not available.
/**
* Resolves legacy package.json fields (main, module, browser) as fallback
*/
function legacy<T = Package>(
pkg: T,
options: { browser: true; fields?: readonly string[] }
): Browser | void;
function legacy<T = Package>(
pkg: T,
options: { browser: string; fields?: readonly string[] }
): string | false | void;
function legacy<T = Package>(
pkg: T,
options: { browser: false; fields?: readonly string[] }
): string | void;
function legacy<T = Package>(
pkg: T,
options?: {
browser?: boolean | string;
fields?: readonly string[];
}
): Browser | string;Usage Example:
const pkg = {
name: "legacy-package",
main: "./lib/index.js",
module: "./lib/index.mjs",
browser: "./lib/browser.js"
};
// Get main entry for Node.js
const nodeEntry = legacy(pkg, { browser: false });
console.log(nodeEntry); // "./lib/index.js"
// Get browser entry
const browserEntry = legacy(pkg, { browser: true });
console.log(browserEntry); // "./lib/browser.js"
// Custom field priority
const customEntry = legacy(pkg, {
browser: false,
fields: ["module", "main"]
});
console.log(customEntry); // "./lib/index.mjs"Configuration options for resolution behavior.
interface Options {
/**
* When true, adds the "browser" conditions.
* Otherwise the "node" condition is enabled.
* @default false
*/
browser?: boolean;
/**
* Any custom conditions to match.
* @note Array order does not matter. Priority is determined by the key-order of conditions defined within a package's imports/exports mapping.
* @default []
*/
conditions?: readonly string[];
/**
* When true, adds the "require" condition.
* Otherwise the "import" condition is enabled.
* @default false
*/
require?: boolean;
/**
* Prevents "require", "import", "browser", and/or "node" conditions from being added automatically.
* When enabled, only `options.conditions` are added alongside the "default" condition.
* @important Enabling this deviates from Node.js default behavior.
* @default false
*/
unsafe?: boolean;
}Represents a package.json structure for resolution.
interface Package {
name: string;
version?: string;
module?: string;
main?: string;
imports?: Imports;
exports?: Exports;
browser?: Browser;
[key: string]: any;
}Export mapping type definitions.
type Exports = Path | {
[path: Exports.Entry]: Exports.Value;
[cond: Condition]: Exports.Value;
};
namespace Exports {
/** Allows "." and "./{name}" */
type Entry = `.${string}`;
/** strings must be internal paths */
type Value = Path | null | {
[c: Condition]: Value;
} | Value[];
type Output = Path[];
}Import mapping type definitions.
type Imports = {
[entry: Imports.Entry]: Imports.Value;
};
namespace Imports {
type Entry = `#${string}`;
/** External dependency name (not an internal path) */
type External = string;
/** strings are dependency names OR internal paths */
type Value = External | Path | null | {
[c: Condition]: Value;
} | Value[];
type Output = Array<External | Path>;
}Basic type definitions used throughout the API.
/**
* A resolve condition
* @example "node", "default", "production"
*/
type Condition = string;
/** An internal file path */
type Path = `./${string}`;
type Browser = string[] | string | {
[file: Path | string]: string | false;
};The package throws Error instances with specific message formats for resolution failures:
Missing "{entry}" specifier in "{name}" package - when the requested entry/target is not found in the package's exports/importsNo known conditions for "{entry}" specifier in "{name}" package - when no matching conditions are found for the entrytry {
const result = exports(pkg, "./nonexistent");
} catch (error) {
console.error(error.message); // Missing "./nonexistent" specifier in "my-package" package
}const pkg = {
name: "my-package",
exports: {
".": {
"development": "./src/index.js",
"production": "./dist/index.js",
"default": "./lib/index.js"
}
}
};
// Resolve with custom condition
const devPath = resolve(pkg, ".", {
conditions: ["development"],
unsafe: true // Only use custom conditions
});
console.log(devPath); // ["./src/index.js"]const pkg = {
name: "universal-package",
exports: {
".": {
"import": {
"browser": "./dist/browser.mjs",
"node": "./dist/node.mjs"
},
"require": {
"browser": "./dist/browser.js",
"node": "./dist/node.js"
}
}
}
};
// Browser + ES modules
const browserESM = resolve(pkg, ".", { browser: true, require: false });
console.log(browserESM); // ["./dist/browser.mjs"]
// Node.js + CommonJS
const nodeCJS = resolve(pkg, ".", { browser: false, require: true });
console.log(nodeCJS); // ["./dist/node.js"]