stdlib-js / stdlib-js/stdlib

[RFC]: Bringing `resolve` Dependency In-House

Open
#11,143 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
6k
Forks
1.3k
Avg merge
1d 3h
Merged PRs (30d)
611

Description

# Replace `resolve` Dependency with In-House `@stdlib/utils/resolve`

Replace the external [`resolve`](https://www.npmjs.com/package/resolve) npm dependency with a new stdlib-native package. This eliminates `resolve` and its **3 transitive dependencies** (`is-core-module`, `path-parse`, `supports-preserve-symlinks-flag`) from the supply chain — removing 4 third-party packages total.

## Background

The `resolve` package implements Node.js's `require.resolve()` algorithm with configurable `basedir` and `paths` options.

Only these features are used:

| Feature | Used? |
|---|---|
| `resolve(id, {basedir}, cb)` (async) | ✅ 5 files |
| `resolve.sync(id, {basedir})` | ✅ 7 files |
| `resolve.sync(id, {basedir, paths})` | ✅ 1 file |
| All other options (`packageFilter`, `pathFilter`, `extensions`, `moduleDirectory`, etc.) | ❌ |

## Proposed Changes

### New Package: `@stdlib/utils/resolve`

#### Folder Structure

```
lib/node_modules/@stdlib/utils/resolve/
├── README.md
├── package.json

├── lib/
│ ├── index.js # Entry point: exports async + .sync
│ ├── main.js # Async resolve( id, opts, clbk )
│ ├── sync.js # Sync resolve( id, opts ) → string
│ ├── validate.js # Options validation
│ ├── defaults.js # Default options (basedir, extensions)
│ ├── resolve_path.js # Core sync resolution algorithm
│ ├── async_resolve_path.js # Core async resolution algorithm
│ └── is_core_module.js # Built-in module check via module.builtinModules

├── docs/
│ ├── repl.txt
│ └── types/
│ ├── index.d.ts
│ └── test.ts

├── benchmark/
│ └── benchmark.js # Performance benchmarks (sync vs async)

├── examples/
│ └── index.js # Usage examples

└── test/
├── test.js # Main export tests
├── test.sync.js # Sync API tests
├── test.async.js # Async API tests
├── test.validate.js # Options validation tests
├── test.is_core_module.js # Core module detection tests
└── fixtures/
├── node_modules/
│ └── mock-pkg/
│ ├── package.json # { "main": "./lib/index.js" }
│ └── lib/
│ └── index.js
├── index.js # Mock directory index
├── foo.js # Mock module
└── foo.json # Mock JSON module
```

#### Key Implementation Details

**`lib/index.js`** — Entry point matching the `resolve` package's API shape:
```js
var resolve = require( './main.js' ); // async
var sync = require( './sync.js' ); // sync
resolve.sync = sync;
module.exports = resolve;
```

**`lib/resolve_path.js`**— Core Node.js resolution algorithm:
1. If `id` is a core module → return `id`
2. If `id` starts with `./` or `../` or `/` → resolve as file, then as directory
3. Otherwise → walk `node_modules` directories upward from `basedir`; if `paths` option given, also search those
4. File resolution: try exact path → `.js` → `.json` → `.node`
5. Directory resolution: read `package.json` `main` field → fallback to `index.js` → `index.json` → `index.node`

**`lib/is_core_module.js`** — Zero-dependency replacement for `is-core-module`:
```js
var builtinModules = require( 'module' ).builtinModules;
function isCoreModule( id ) {
return builtinModules.indexOf( id ) !== -1;
}
```

**Internal dependencies only** — no third-party packages:
- `path` (Node.js built-in)
- `fs` (Node.js built-in)
- `module` (Node.js built-in)
- `@stdlib/assert/is-string` (input validation)
- `@stdlib/assert/is-plain-object` (options validation)
- `@stdlib/assert/has-own-property` (options checking)
- `@stdlib/error/tools/fmtprodmsg` (error formatting)

---

### Consumer Migration (13 files)

Each migration is a 1-line `require` swap. The API shape is identical.

**Sync consumers** (8 files) — `require( 'resolve' ).sync` → `require( '@stdlib/utils/resolve' ).sync`:

| # | File |
|---|---|
| 1 | eslint/require-file-extensions/lib/main.js|
| 2 | modules/pkg-deps/lib/walk_file.sync.js |
| 3 | pkgs/browser-compatible/lib/resolve.sync.js |
| 4 | pkgs/entry-points/lib/resolve.sync.js|
| 5 | pkgs/browser-entry-points/lib/resolve.sync.js |
| 6 | utils/library-manifest/lib/main.js |
| 7 | repl/help/scripts/build.js |

**Async consumers** (6 files) — `require( 'resolve' )` → `require( '@stdlib/utils/resolve' )`:

| # | File |
|---|---|
| 8 | modules/pkg-deps/lib/walk_file.js |
| 9 | modules/pkg-deps/test/test.walk_file.js *(also update `proxyquire` mock key)* |
| 10 | pkgs/browser-compatible/lib/resolve.js|
| 11 | pkgs/entry-points/lib/resolve.js|
| 12 | pkgs/browser-entry-points/lib/resolve.js|
| 13 | lint/namespace-aliases/lib/resolve.js|

---

### Dependency Removal
```
npm uninstall resolve
```
---

## Verification Plan

### Automated Tests

stdlib uses `make test`:

* Run new package tests using stdlib's test infrastructure:

```bash
make EXAMPLES_FILTER=".*/utils/resolve/.*"" examples

make BENCHMARKS_FILTER=".*/utils/resolve/.*"" benchmark

make TESTS_FILTER=". */utils/resolve/.*"" test
```
* Run consumer regression tests (pkg-deps has the most extensive test suite):

```
make TESTS_FILTER=". */_tools/modules/pkg-deps/.*"" test
```

### Manual Verification
{TODO}

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.