feat: Convert nock to TypeScript + ESM#2968
Conversation
|
@markscamilleri Following #2861, I'd love to hear your thoughts on this. |
There was a problem hiding this comment.
Pull request overview
Modernizes the nock codebase by moving the runtime sources to TypeScript (run directly in Node via type-stripping) and converting the package from CommonJS to ESM, along with updating tests/tooling to match.
Changes:
- Converted runtime entrypoint and
lib/sources from CJS.jsto ESM.ts, and updated package metadata (type,exports,main). - Replaced hand-maintained typings + dtslint with generated declarations (
tsc --emitDeclarationOnly) and added TS project configs. - Updated Mocha/Jest tests and examples to ESM imports, updated lint/coverage tooling and CI workflow.
Reviewed changes
Copilot reviewed 95 out of 99 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| types/tslint.json | Removed dtslint config (switching away from dtslint). |
| types/tsconfig.json | Removed old DefinitelyTyped-style tsconfig for types tests. |
| types/tests.ts | Removed dtslint-based type test suite. |
| types/index.d.ts | Removed hand-written declaration file (now generated). |
| tsconfig.json | Added base TS config for source type-checking. |
| tsconfig.build.json | Added TS config for generating .d.ts into types/. |
| package.json | Switched to ESM + .ts entrypoint, updated engines, scripts, deps, exports, and types generation. |
| eslint.config.mjs | Added TypeScript ESLint config and updated patterns/ignores for ESM+TS. |
| .gitignore | Ignored generated types/ output. |
| .mocharc.json | Replaced JS mocha config with JSON config for ESM project. |
| .mocharc.js | Removed old CommonJS mocha config file. |
| .github/workflows/continuous-delivery.yaml | Added a CI step to generate types before release. |
| CHANGELOG.md | Removed obsolete TODO notes about dtslint/types generation. |
| index.ts | New ESM TypeScript entrypoint wiring up exports and types. |
| index.js | Removed old CommonJS entrypoint. |
| lib/back.ts | Converted nock-back implementation to TS/ESM and added type surface. |
| lib/common.ts | Converted shared utilities to TS/ESM and added typed signatures. |
| lib/debug.ts | Converted debuglog helpers to TS/ESM. |
| lib/debug.js | Removed old CommonJS debug module. |
| lib/global_emitter.ts | Added typed global emitter (TS/ESM). |
| lib/global_emitter.js | Removed old CommonJS global emitter. |
| lib/handle-request.ts | Converted request handling to TS/ESM; NetConnectNotAllowedError now a class. |
| lib/intercept.ts | Converted interceptor registry to TS/ESM and updated undici lazy-load path. |
| lib/interceptor.ts | Converted Interceptor implementation to TS/ESM and exported key types. |
| lib/interceptors/builtin.ts | Converted builtin interceptor wiring to TS/ESM. |
| lib/interceptors/undici.ts | Converted undici integration to TS/ESM and updated dispatcher classes. |
| lib/match_body.ts | Converted request-body matching util to TS/ESM. |
| lib/playback_interceptor.ts | Converted playback logic to TS/ESM and tightened types. |
| lib/recorder.ts | Converted recorder to TS/ESM, introduced TS types for recorder options/output. |
| lib/scope.ts | Converted Scope to TS/ESM and introduced TS types for options/definitions. |
| lib/stringify.ts | Converted safe stringify helper to TS/ESM and exported default. |
| lib/utils/node/index.ts | Added TS/ESM utilities for GET-with-body support. |
| lib/utils/node/index.js | Removed old CommonJS node utils module. |
| lib/create_response.js | Removed old CommonJS response creation helper. |
| tests/setup.js | Converted test setup to ESM imports. |
| tests/servers/index.js | Converted test server helpers to ESM exports and updated path resolution. |
| tests/test_abort.js | Converted to ESM imports and updated nock import target. |
| tests/test_abort_signal.js | Converted to ESM imports and updated nock import target. |
| tests/test_back.js | Converted to ESM and updated fixture path resolution. |
| tests/test_client_request.js | Converted to ESM imports and updated server helper import. |
| tests/test_common.js | Converted to ESM imports and updated internal module imports. |
| tests/test_destroy.js | Converted to ESM imports and updated nock import target. |
| tests/test_fetch.js | Converted to ESM imports and updated fixture path resolution. |
| tests/test_gzip_request.js | Converted to ESM imports and updated nock import target. |
| tests/test_ipv6.js | Converted to ESM imports and updated nock import target. |
| tests/test_reply_with_error.js | Converted to ESM imports and updated nock import target. |
| tests/test_socket.js | Converted to ESM imports and updated nock import target. |
| tests/test_stringify.js | Updated test to import stringify from TS source. |
| tests/test_undici.js | Converted to ESM imports and updated nock import target. |
| tests/test_unix_socket.js | Converted to ESM imports and adjusted Windows skip logic. |
| tests/got/got_client.js | Converted got client helper to ESM default export. |
| tests/got/fixtures/logging.mjs | Updated to import nock from TS entrypoint. |
| tests/got/test_allow_unmocked.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_allow_unmocked_https.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_back_filters.js | Converted to ESM imports and updated fixtures path resolution. |
| tests/got/test_basic_auth.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_body_match.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_content_encoding.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_default_reply_headers.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_define.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_delay.js | Converted to ESM imports and updated assets path resolution. |
| tests/got/test_dynamic_mock.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_events.js | Converted to ESM imports and updated assets path resolution. |
| tests/got/test_fake_timer.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_header_matching.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_intercept.js | Converted to ESM imports and updated internal util import. |
| tests/got/test_intercept_parallel.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_logging.js | Converted to ESM imports and updated fixtures path resolution. |
| tests/got/test_net_connect.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_nock_lifecycle.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_nock_off.js | Converted to ESM imports and updated reload logic to dynamic import. |
| tests/got/test_passthrough.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_persist_optionally.js | Converted to ESM imports and updated assets path resolution. |
| tests/got/test_query.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_query_complex.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_recorder.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_redirects.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_remove_interceptor.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_reply_body.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_reply_function_async.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_reply_function_sync.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_reply_headers.js | Converted to ESM imports and updated assets path resolution. |
| tests/got/test_reply_with_file.js | Converted to ESM imports and updated assets path resolution. |
| tests/got/test_repeating.js | Converted to ESM imports and updated nock import target. |
| tests/got/test_request_overrider.js | Converted to ESM imports and updated server/nock imports. |
| tests/got/test_scope.js | Converted to ESM imports and updated fixture path resolution. |
| tests/got/test_stream.js | Converted to ESM imports and updated assets path resolution. |
| tests/got/test_url_encoding.js | Converted to ESM imports and updated got helper import. |
| tests_jest/memory_leak.spec.js | Converted Jest test to ESM import and TS entrypoint reference. |
| examples/_log.js | Converted example logger helper to ESM default export. |
| examples/binary-reply.js | Converted example to ESM imports and updated path resolution. |
| examples/delay-response.js | Converted example to ESM imports and updated nock import target. |
| examples/net-connect-default-no-mock.js | Converted example to ESM imports. |
| examples/net-connect-default-other-mock.js | Converted example to ESM imports and updated nock import target. |
| examples/net-connect-disabled-different-host.js | Converted example to ESM imports and updated nock import target. |
| examples/net-connect-mock-same-host-different-path.js | Converted example to ESM imports and updated nock import target. |
| examples/socket-delay-abort.js | Converted example to ESM imports and updated nock import target. |
| examples/socket-delay-no-abort.js | Converted example to ESM imports and updated nock import target. |
Comments suppressed due to low confidence (3)
lib/intercept.ts:214
activate()lazy-loads the undici interceptor viacreateRequire(...)._require('./interceptors/undici.ts'). Since./interceptors/undici.tsis an ES module (usesimport/export) and has a.tsextension, thisrequire()call is expected to throw (e.g.,ERR_REQUIRE_ESM/ unknown extension), and that error code is not handled by the current catch block. Consider switching this to a dynamicimport()approach (and restructuringactivateaccordingly), or providing a loadable CommonJS entry specifically for this sync require path.
lib/recorder.ts:173recordis typed asrecord(recOptions: boolean | RecorderOptions), but the public API supports callingrec()with no arguments (and the implementation toleratesundefined). Making this parameter optional (recOptions?: ...) preserves backward-compatible typings for TS consumers.
lib/scope.ts:328getScopeFromDefinitionusesURL.parse(...), butURLhere is the global WHATWGURLclass (notnode:url’s legacyurlmodule) and does not have a.parse()method. This will throw at runtime whennockDef.portis present. Import and use the legacy parser (e.g.,import url from 'node:url'+url.parse(...)), or switch tonew URL(nockDef.scope)and read.portfrom that.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "engines": { | ||
| "node": ">=18.20.0 <20 || >=20.12.1" | ||
| "node": ">=22.6.0" | ||
| }, | ||
| "main": "./index.ts", | ||
| "type": "module", |
There was a problem hiding this comment.
PR description states Node’s built-in TS type-stripping is “stable since v23.6”, but engines.node is set to >=22.6.0 while main/exports point at .ts sources. If runtime execution truly depends on stable type-stripping, consider aligning engines.node with that minimum to avoid installs that cannot execute the entrypoint.
I always preferred to have JS sources for libraries that are used by others. With Node 23.6+, we would publish the TS code directly, without a build step? |
|
@gr2m I knew I forgot something 😅 (Node.js does not allow this anyway) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 100 out of 104 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
lib/scope.ts:392
getScopeFromDefinitioncallsURL.parse(...), butURLhere is the global WHATWG URL class and does not have aparsemethod. This will throw at runtime when a definition includes aport. Import the legacy parser fromnode:url(e.g.import url from 'node:url'and useurl.parse(...)), or rewrite this logic to usenew URL(...)and read.port.
lib/intercept.ts:249activate()usescreateRequire(...)._require('./interceptors/undici.ts')to load an ESM module. On Node versions withoutrequire(esm)support (and/or when loading TS sources), this throwsERR_REQUIRE_ESMand undici mocking gets silently disabled even ifundiciis installed. Prefer a sync-safe approach: import the local undici wrapper normally and have itrequire('undici')internally, or bump the supported Node engine(s) to versions whererequire(esm)is guaranteed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| node-version: |
There was a problem hiding this comment.
The workflow comment says it verifies against engines.node, but the matrix no longer tests Node 20 even though package.json declares >=20.12.1. Either add Node 20 back to the matrix or raise the engines.node minimum to match what CI actually validates.
| node-version: | |
| node-version: | |
| - 20 |
| ## ESM | ||
|
|
||
| Nock is now a pure ES module. If your project uses CommonJS, you can still use `require()` with the Node.js's built-in `require(esm)` support (stable since v22.12): | ||
|
|
||
| ```js | ||
| // ESM | ||
| import nock from 'nock' | ||
|
|
||
| // CommonJS (Node.js >= 22.12) | ||
| const { default: nock } = require('nock') | ||
| ``` |
There was a problem hiding this comment.
This section documents CommonJS usage via require(esm) (Node >=22.12), but package.json currently declares support for Node >=20.12.1. Please clarify the minimum Node version required for CommonJS consumers here (or update engines.node accordingly) so users on Node 20/21 don’t assume require('nock') will work.
|
@gr2m I'd love your feedback on this one. |
|
@gr2m I'm merging it. Please share your feedback anytime. thanks! |
|
🎉 This PR is included in version 15.0.0-beta.11 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
Looking great! |
What
This PR modernizes nock's codebase in two steps:
TypeScript — All source files converted from
.jsto.ts. Node.js runs.tsfiles directly via built-in type stripping (stable since v23.6). No transpile step, no build step for development.ESM — Converted from CommonJS (
require/module.exports) to ES Modules (import/export). Package now declares"type": "module".This PR does not include a CommonJS version, but we can add it later if needed using a dual package build.
Why
TypeScript without a build step. Node.js v23.6+ strips type annotations natively at startup. This means:
.tsfiles and run them directly — no compilation, no source maps, no watchingerasableSyntaxOnlyensures we only use type syntax that Node.js can erase (no enums, no constructor parameter properties, no namespaces)ESM gives us:
Auto-generated declarations. The hand-written index.d.ts was a maintenance burden — it duplicated types from the source and was drifting. Now
npm run build:typesgenerates all.d.tsfiles from source.Changes
Source files (lib/, index.ts):
.js→.ts@param/@returns/@typedefwith inline TypeScriptrequire()/module.exports→import/exportBackContext,InterceptorSurface)NetConnectNotAllowedErrorconverted to a proper classConfiguration:
typescript-eslintparser, updated file patternsTooling:
**/*.{js,ts}What didn't change
import nock from 'nock'works,nock.Scope,nock.Interceptoretc. all resolve.typescript-eslintis dev-only.