FrontendAtlas
Interview Questions
Home>JavaScript interview questions> JavaScript coding challenges >Resolve package.json Exports (Import vs Require)

Resolve package.json Exports (Import vs Require)

hard
By FrontendAtlas Editorial · Updated Feb 5, 2026
Implement resolvePackageEntry(pkg, options) to choose the correct entry file for a package based on package.json fields like exports, module, and main. Support conditional exports (browser/import/require/default) and explain how bundlers resolve ESM vs CJS builds.

Arguments

  • pkg: object — A simplified package.json object with exports/module/main fields.
  • options: { kind?: 'import' | 'require', conditions?: string[] } (optional) — Resolution kind and extra conditions (e.g., ['browser', 'development']).

Returns

string — Resolved entry path (e.g., './dist/index.js').
Examples
resolvePackageEntry({ exports: './dist/index.js' }) // => './dist/index.js'

resolvePackageEntry({ exports: { '.': { browser: { import: './dist/browser.mjs' }, default: './dist/index.js' } } }, { kind: 'import', conditions: ['browser'] })
// => './dist/browser.mjs'

Solution

Overview

Modern packages often publish multiple entry points: ESM vs CJS, browser vs node, dev vs prod. The package.json exports field can be conditional. Your resolver should pick the best match for the requested kind and conditions, with sensible fallbacks.

1

Approach: Resolve conditional exports with a candidate order

Algorithm (simplified, interview-friendly):

1) Build a list of candidate conditions: [...options.conditions, kind, 'default'] (deduped).

2) If pkg.exports is a string, return it.

3) If pkg.exports is an object:

- If it has '.', treat exports['.'] as the root target.

- Otherwise treat the object as a conditional target.

- Walk the conditional object: pick the first matching condition key; if its value is another object, recurse.

4) If exports doesn't resolve, fall back to:

- kind='import' => pkg.module (ESM build)

- otherwise => pkg.main

5) If nothing resolves, throw.

Complexity: O(d * c) where d is nesting depth and c is number of candidate conditions.

export default function resolvePackageEntry(pkg, options = {}) {
  if (!pkg || typeof pkg !== 'object') throw new TypeError('pkg must be an object');

  const kind = options && options.kind === 'require' ? 'require' : 'import';
  const extra = Array.isArray(options && options.conditions)
    ? options.conditions.filter((c) => typeof c === 'string' && c.length)
    : [];

  const candidates = Array.from(new Set([...extra, kind, 'default']));

  const resolveTarget = (target) => {
    if (typeof target === 'string') return target;
    if (!target || typeof target !== 'object') return null;

    for (const cond of candidates) {
      if (Object.prototype.hasOwnProperty.call(target, cond)) {
        const out = resolveTarget(target[cond]);
        if (typeof out === 'string' && out.length) return out;
      }
    }
    return null;
  };

  let resolved = null;

  if (typeof pkg.exports === 'string') {
    resolved = pkg.exports;
  } else if (pkg.exports && typeof pkg.exports === 'object') {
    const root = Object.prototype.hasOwnProperty.call(pkg.exports, '.')
      ? pkg.exports['.']
      : pkg.exports;
    resolved = resolveTarget(root);
  }

  if (resolved) return resolved;

  if (kind === 'import' && typeof pkg.module === 'string' && pkg.module) return pkg.module;
  if (typeof pkg.main === 'string' && pkg.main) return pkg.main;

  throw new Error('Unable to resolve package entry');
}
export type ExportTarget = string | { [condition: string]: ExportTarget };

export type PackageJsonLike = {
  name?: string;
  main?: string;
  module?: string;
  exports?: ExportTarget | { [subpath: string]: ExportTarget };
};

export type ResolveOptions = {
  kind?: 'import' | 'require';
  conditions?: string[];
};

function isObject(x: unknown): x is Record<string, any> {
  return x !== null && typeof x === 'object';
}

export default function resolvePackageEntry(pkg: PackageJsonLike, options: ResolveOptions = {}): string {
  if (!isObject(pkg)) throw new TypeError('pkg must be an object');

  const kind: 'import' | 'require' = options.kind === 'require' ? 'require' : 'import';
  const extra = Array.isArray(options.conditions)
    ? options.conditions.filter((c): c is string => typeof c === 'string' && c.length > 0)
    : [];

  const candidates = Array.from(new Set([...extra, kind, 'default']));

  const resolveTarget = (target: any): string | null => {
    if (typeof target === 'string') return target;
    if (!isObject(target)) return null;

    for (const cond of candidates) {
      if (Object.prototype.hasOwnProperty.call(target, cond)) {
        const out = resolveTarget((target as any)[cond]);
        if (typeof out === 'string' && out.length) return out;
      }
    }
    return null;
  };

  let resolved: string | null = null;

  if (typeof (pkg as any).exports === 'string') {
    resolved = (pkg as any).exports;
  } else if (isObject((pkg as any).exports)) {
    const exp = (pkg as any).exports as any;
    const root = Object.prototype.hasOwnProperty.call(exp, '.') ? exp['.'] : exp;
    resolved = resolveTarget(root);
  }

  if (resolved) return resolved;

  if (kind === 'import' && typeof pkg.module === 'string' && pkg.module) return pkg.module;
  if (typeof pkg.main === 'string' && pkg.main) return pkg.main;

  throw new Error('Unable to resolve package entry');
}

Notes & Pitfalls

Pitfalls
  • Assuming exports is always a string. Many packages use conditional exports for browser/import/require.
  • Choosing module/main blindly can break bundlers when exports exists (exports should generally win).
  • Condition order matters: for a browser build you typically prefer 'browser' over 'default'.
Edge cases
  • exports can be nested objects; recurse until you reach a string target.
  • If exports doesn't match any condition, fall back to default, then module/main.
  • Throw when nothing resolves to avoid silently bundling the wrong file.
Techniques
  • Candidate condition list: [..conditions, kind, 'default'].
  • Recursive conditional resolution.
  • Deterministic fallbacks (module/main).

Resources

  • Node.js – Package entry points (exports)

Similar questions

Create a Spy Function (Test Double)intermediateRun With a Performance Budget (Sync or Async)intermediateCleanup Bag (Dispose Subscriptions)easy

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse JavaScript interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen JavaScript mastery study planOpen Company PrepOpen JavaScript Framework Prep Guide
↗Incidents hub
82 / 88Next →→