Skip to content

Commit c744c71

Browse files
authored
feat: swap node-fetch for undici (#4)
node-fetch does not use browser streams which makes using it in typed environments very hard since the methods on node streams are different from those on browser streams. undici is a node implementation of fetch that supports browser streams and is probably the closest thing we'll ever see to fetch in node core, so swap node-fetch for undici. BREAKING CHANGE: only browser streams are returned, requires node 16+
1 parent 2fc76e7 commit c744c71

File tree

6 files changed

+59
-22
lines changed

6 files changed

+59
-22
lines changed

.aegir.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** @type {import('aegir').Options["build"]["config"]} */
2+
const esbuild = {
3+
plugins: [
4+
{
5+
name: 'node built ins',
6+
setup (build) {
7+
build.onResolve({ filter: /^undici$/ }, () => {
8+
return { path: require.resolve('./scripts/undici') }
9+
})
10+
}
11+
}
12+
]
13+
}
14+
15+
/** @type {import('aegir').PartialOptions} */
16+
module.exports = {
17+
build: {
18+
config: esbuild
19+
},
20+
test: {
21+
browser: {
22+
config: {
23+
buildConfig: esbuild
24+
}
25+
}
26+
}
27+
}

README.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
# native-fetch
22

3-
> Returns native fetch/Request/Headers if available or the node-fetch module if not
3+
> Returns native fetch/Request/Headers if available or the `undici` module if not
44
5-
An (almost) drop-in replacement for the `node-fetch` module that returns the native fetch if available or the polyfill if not.
5+
An (almost) drop-in replacement for the `undici` module that returns the native fetch if available or the polyfill if not.
66

77
### Why?
88

99
Some environments such as the Electron Renderer process straddle the node/browser divide with features from both APIs available. In these cases the webpack approach of always using the `browser` field in your `package.json` to override requires is too heavy-handed as sometimes you want to use the node version of an API.
1010

1111
Instead we can check for the availability of a given API and return it, rather than the node-polyfill for that API.
1212

13+
### Why Undici and not node-fetch?
14+
15+
[node-fetch](https://www.npmjs.com/package/node-fetch) is the OG fetch implementation for node, but it uses [Node.js streams](https://nodejs.org/api/stream.html) instead of [WHATWG streams](https://streams.spec.whatwg.org/). This means the APIs are not the same which leads to all sorts of weird shenanigans with types.
16+
17+
[undici](https://www.npmjs.com/package/undici) is the new kid on the block and uses WHATWG streams so all of the APIs now live in glorious harmony.
18+
19+
If you are trying to write polymorphic code with strong typing this is a big deal.
20+
1321
## Install
1422

15-
You must install a version of `node-fetch` [alongside this module](https://docs.npmjs.com/files/package.json#peerdependencies) to be used if a native implementation is not available.
23+
You must install a version of `undici` [alongside this module](https://docs.npmjs.com/files/package.json#peerdependencies) to be used if a native implementation is not available.
1624

1725
```console
18-
$ npm install --save native-fetch node-fetch
26+
$ npm install --save native-fetch undici
1927
```
2028

2129
## Usage
2230

2331
```javascript
24-
const { default: fetch } = require('native-fetch')
32+
const { fetch } = require('native-fetch')
2533

2634
fetch('https://github.com/')
2735
.then(res => res.text())

package.json

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "native-fetch",
33
"version": "3.0.0",
4-
"description": "Returns native fetch if available or the node-fetch module if not",
4+
"description": "Returns native fetch if available or the undici module if not",
55
"main": "src/index.js",
66
"types": "dist/src/index.d.ts",
77
"files": [
@@ -11,7 +11,7 @@
1111
"scripts": {
1212
"test": "aegir test -t node -t browser -t webworker -t electron-main -t electron-renderer",
1313
"lint": "aegir lint && aegir ts -p check",
14-
"prepare": "aegir build --no-bundle",
14+
"build": "aegir build --no-bundle",
1515
"release": "aegir release --docs false",
1616
"release-minor": "aegir release --type minor --docs false",
1717
"release-major": "aegir release --type major --docs false"
@@ -23,12 +23,11 @@
2323
"url": "git+https://github.com/achingbrain/native-fetch.git"
2424
},
2525
"peerDependencies": {
26-
"node-fetch": "*"
26+
"undici": "*"
2727
},
2828
"devDependencies": {
29-
"@types/node-fetch": "^2.5.8",
30-
"aegir": "^30.3.0",
31-
"node-fetch": "^2.6.0"
29+
"aegir": "^36.0.0",
30+
"undici": "^4.10.0"
3231
},
3332
"contributors": [
3433
"achingbrain <[email protected]>"

scripts/undici.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
module.exports = {}

src/index.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
'use strict'
22

3+
// @ts-expect-error globalThis.fetch is defined according to the env types but it's not in node
34
if (globalThis.fetch && globalThis.Headers && globalThis.Request && globalThis.Response) {
45
module.exports = {
5-
default: globalThis.fetch,
6+
fetch: globalThis.fetch,
67
Headers: globalThis.Headers,
78
Request: globalThis.Request,
89
Response: globalThis.Response
910
}
1011
} else {
1112
module.exports = {
12-
default: require('node-fetch').default,
13-
Headers: require('node-fetch').Headers,
14-
Request: require('node-fetch').Request,
15-
Response: require('node-fetch').Response
13+
fetch: require('undici').fetch,
14+
Headers: require('undici').Headers,
15+
Request: require('undici').Request,
16+
Response: require('undici').Response
1617
}
1718
}

test/index.spec.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,35 @@
33
/* eslint-env mocha */
44
const { expect } = require('aegir/utils/chai')
55
const NativeFetch = require('../src')
6-
const NodeFetch = require('node-fetch')
6+
const Undici = require('undici')
77

88
describe('env', function () {
99
it('fetch should be correct in each env', function () {
1010
switch (process.env.AEGIR_RUNNER) {
1111
case 'electron-main':
12-
expect(NativeFetch.default).to.equal(NodeFetch.default)
12+
expect(NativeFetch.fetch).to.equal(Undici.fetch)
1313
expect(globalThis.fetch).to.be.undefined()
1414
break
1515
case 'electron-renderer':
16-
expect(NativeFetch.default).to.equal(globalThis.fetch)
16+
expect(NativeFetch.fetch).to.equal(globalThis.fetch)
1717
expect(NativeFetch.Headers).to.equal(globalThis.Headers)
1818
expect(NativeFetch.Request).to.equal(globalThis.Request)
1919
expect(NativeFetch.Response).to.equal(globalThis.Response)
2020
expect(globalThis.fetch).to.be.ok()
2121
break
2222
case 'node':
23-
expect(NativeFetch.default).to.equal(NodeFetch.default)
23+
expect(NativeFetch.fetch).to.equal(Undici.fetch)
2424
expect(globalThis.fetch).to.be.undefined()
2525
break
2626
case 'browser':
27-
expect(NativeFetch.default).to.equal(globalThis.fetch)
27+
expect(NativeFetch.fetch).to.equal(globalThis.fetch)
2828
expect(NativeFetch.Headers).to.equal(globalThis.Headers)
2929
expect(NativeFetch.Request).to.equal(globalThis.Request)
3030
expect(NativeFetch.Response).to.equal(globalThis.Response)
3131
expect(globalThis.fetch).to.be.ok()
3232
break
3333
case 'webworker':
34-
expect(NativeFetch.default).to.equal(globalThis.fetch)
34+
expect(NativeFetch.fetch).to.equal(globalThis.fetch)
3535
expect(NativeFetch.Headers).to.equal(globalThis.Headers)
3636
expect(NativeFetch.Request).to.equal(globalThis.Request)
3737
expect(NativeFetch.Response).to.equal(globalThis.Response)

0 commit comments

Comments
 (0)