Skip to content

Commit 30ff1b6

Browse files
committed
feat: add silent option
1 parent ead837b commit 30ff1b6

File tree

4 files changed

+63
-1
lines changed

4 files changed

+63
-1
lines changed

.changeset/cyan-ideas-admire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"resolve-sync": minor
3+
---
4+
5+
Add "silent" config option to gracefully handle missing resolved paths.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ const result = resolveSync("fs", {
8686
// result === "fs"
8787
```
8888

89+
### `silent?: boolean`
90+
91+
If `silent` is set to `true`, the resolver will suppress errors and return `undefined` when a module cannot be resolved, instead of throwing an exception. This is useful for optional dependencies or when you want to handle missing modules gracefully.
92+
8993
### `exts?: string[]`
9094

9195
An optional array of file extensions to try when resolving files without explicit extensions. Defaults to:

src/__tests__/index.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,17 @@ describe("resolve - external option", () => {
850850
});
851851
});
852852

853+
describe("resolve - silent option", () => {
854+
it("returns undefined instead of throwing when module is missing and silent is true", () => {
855+
const result = resolveSync("./missing.js", {
856+
from: "/project/src/index.js",
857+
fs: vfs(["/project/src/file.js"]),
858+
silent: true,
859+
});
860+
assert.equal(result, undefined);
861+
});
862+
});
863+
853864
function vfs(files: (string | [string, string])[]): ResolveOptions["fs"] {
854865
const fileMap: Record<string, string> = {};
855866
for (const entry of files) {

src/index.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,49 @@ import { exports, imports } from "resolve.exports";
22

33
import { external as defaultExternal, fs as defaultFS } from "#defaults";
44
export interface ResolveOptions {
5+
/**
6+
* The absolute path to the file from which to resolve the module.
7+
*/
58
from: string;
9+
/**
10+
* The root directory for resolution. Defaults to "/".
11+
*/
612
root?: string;
13+
/**
14+
* File extensions to consider. Defaults to [".js", ".json"].
15+
*/
716
exts?: string[];
17+
/**
18+
* Package.json fields to check for entry points. Defaults to ["module", "main"] or ["browser", "module", "main"] if browser option is true.
19+
*/
820
fields?: string[];
21+
/**
22+
* If true, suppresses errors and returns undefined when a module cannot be resolved. Defaults to false.
23+
*/
24+
silent?: boolean;
25+
/**
26+
* If true, resolves using CommonJS (require) semantics.
27+
*/
928
require?: boolean;
29+
/**
30+
* If true, resolves using browser field mappings.
31+
*/
1032
browser?: boolean;
33+
/**
34+
* Additional conditions for conditional exports/imports.
35+
*/
1136
conditions?: string[];
37+
/**
38+
* Function to determine if a module id should be treated as external.
39+
*/
1240
external?: (id: string) => boolean;
41+
/**
42+
* If true, preserves symbolic links instead of resolving to real paths.
43+
*/
1344
preserveSymlinks?: boolean;
45+
/**
46+
* Custom file system interface.
47+
*/
1448
fs?: {
1549
isFile(file: string): boolean;
1650
readPkg(file: string): unknown;
@@ -24,6 +58,7 @@ interface ResolveContext {
2458
fromDir: string;
2559
exts: string[];
2660
fields: string[];
61+
silent: boolean;
2762
external: (id: string) => boolean;
2863
isFile(file: string): boolean;
2964
readPkg(file: string): unknown;
@@ -40,7 +75,10 @@ const defaultFields = ["module", "main"];
4075
const defaultBrowserFields = ["browser", "module", "main"];
4176
const identity = (file: string) => file;
4277

43-
export function resolveSync(id: string, opts: ResolveOptions): string | false {
78+
export function resolveSync(
79+
id: string,
80+
opts: ResolveOptions,
81+
): string | false | undefined {
4482
const ctx = toContext(opts);
4583
const resolved = resolveId(ctx, id);
4684

@@ -49,6 +87,7 @@ export function resolveSync(id: string, opts: ResolveOptions): string | false {
4987
}
5088

5189
if (!resolved) {
90+
if (ctx.silent) return;
5291
throw new Error(`Cannot find module '${id}' from '${ctx.from}'`);
5392
}
5493

@@ -67,6 +106,7 @@ function toContext(opts: ResolveOptions): ResolveContext {
67106
root,
68107
from,
69108
fromDir,
109+
silent: !!opts.silent,
70110
exts: opts.exts || defaultExts,
71111
fields: opts.fields || (browser ? defaultBrowserFields : defaultFields),
72112
realpath,
@@ -178,6 +218,7 @@ function resolvePkgPart(ctx: ResolveContext, pkgDir: string, part: string) {
178218
const pkg = ctx.readPkg(pkgFile);
179219

180220
if (!pkg || typeof pkg !== "object") {
221+
if (ctx.silent) return;
181222
throw new Error(`Invalid package '${pkgFile}' loaded by '${ctx.from}'.`);
182223
}
183224

@@ -187,6 +228,7 @@ function resolvePkgPart(ctx: ResolveContext, pkgDir: string, part: string) {
187228
: resolvePkgField(ctx, pkg, pkgDir, part);
188229

189230
if (resolved === undefined) {
231+
if (ctx.silent) return;
190232
throw new Error(
191233
`Could not resolve entry for package '${pkgFile}' loaded by '${ctx.from}'.`,
192234
);

0 commit comments

Comments
 (0)