From c744c71455f2c5b7904a3a0de3c5d8d7bd4fe578 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 17 Nov 2021 18:13:24 +0000 Subject: [PATCH] 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+ --- .aegir.js | 27 +++++++++++++++++++++++++++ README.md | 18 +++++++++++++----- package.json | 11 +++++------ scripts/undici.js | 2 ++ src/index.js | 11 ++++++----- test/index.spec.js | 12 ++++++------ 6 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 .aegir.js create mode 100644 scripts/undici.js diff --git a/.aegir.js b/.aegir.js new file mode 100644 index 0000000..d24b219 --- /dev/null +++ b/.aegir.js @@ -0,0 +1,27 @@ +/** @type {import('aegir').Options["build"]["config"]} */ +const esbuild = { + plugins: [ + { + name: 'node built ins', + setup (build) { + build.onResolve({ filter: /^undici$/ }, () => { + return { path: require.resolve('./scripts/undici') } + }) + } + } + ] +} + +/** @type {import('aegir').PartialOptions} */ +module.exports = { + build: { + config: esbuild + }, + test: { + browser: { + config: { + buildConfig: esbuild + } + } + } +} diff --git a/README.md b/README.md index 1553fe5..a92f470 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # native-fetch -> Returns native fetch/Request/Headers if available or the node-fetch module if not +> Returns native fetch/Request/Headers if available or the `undici` module if not -An (almost) drop-in replacement for the `node-fetch` module that returns the native fetch if available or the polyfill if not. +An (almost) drop-in replacement for the `undici` module that returns the native fetch if available or the polyfill if not. ### Why? @@ -10,18 +10,26 @@ Some environments such as the Electron Renderer process straddle the node/browse Instead we can check for the availability of a given API and return it, rather than the node-polyfill for that API. +### Why Undici and not node-fetch? + +[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. + +[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. + +If you are trying to write polymorphic code with strong typing this is a big deal. + ## Install -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. +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. ```console -$ npm install --save native-fetch node-fetch +$ npm install --save native-fetch undici ``` ## Usage ```javascript -const { default: fetch } = require('native-fetch') +const { fetch } = require('native-fetch') fetch('https://github.com/') .then(res => res.text()) diff --git a/package.json b/package.json index 1fd7d49..2bf8175 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "native-fetch", "version": "3.0.0", - "description": "Returns native fetch if available or the node-fetch module if not", + "description": "Returns native fetch if available or the undici module if not", "main": "src/index.js", "types": "dist/src/index.d.ts", "files": [ @@ -11,7 +11,7 @@ "scripts": { "test": "aegir test -t node -t browser -t webworker -t electron-main -t electron-renderer", "lint": "aegir lint && aegir ts -p check", - "prepare": "aegir build --no-bundle", + "build": "aegir build --no-bundle", "release": "aegir release --docs false", "release-minor": "aegir release --type minor --docs false", "release-major": "aegir release --type major --docs false" @@ -23,12 +23,11 @@ "url": "git+https://github.com/achingbrain/native-fetch.git" }, "peerDependencies": { - "node-fetch": "*" + "undici": "*" }, "devDependencies": { - "@types/node-fetch": "^2.5.8", - "aegir": "^30.3.0", - "node-fetch": "^2.6.0" + "aegir": "^36.0.0", + "undici": "^4.10.0" }, "contributors": [ "achingbrain " diff --git a/scripts/undici.js b/scripts/undici.js new file mode 100644 index 0000000..5704bb7 --- /dev/null +++ b/scripts/undici.js @@ -0,0 +1,2 @@ + +module.exports = {} diff --git a/src/index.js b/src/index.js index 7c60c46..7c44d3d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,18 @@ 'use strict' +// @ts-expect-error globalThis.fetch is defined according to the env types but it's not in node if (globalThis.fetch && globalThis.Headers && globalThis.Request && globalThis.Response) { module.exports = { - default: globalThis.fetch, + fetch: globalThis.fetch, Headers: globalThis.Headers, Request: globalThis.Request, Response: globalThis.Response } } else { module.exports = { - default: require('node-fetch').default, - Headers: require('node-fetch').Headers, - Request: require('node-fetch').Request, - Response: require('node-fetch').Response + fetch: require('undici').fetch, + Headers: require('undici').Headers, + Request: require('undici').Request, + Response: require('undici').Response } } diff --git a/test/index.spec.js b/test/index.spec.js index d41d2ab..d0fc84c 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -3,35 +3,35 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') const NativeFetch = require('../src') -const NodeFetch = require('node-fetch') +const Undici = require('undici') describe('env', function () { it('fetch should be correct in each env', function () { switch (process.env.AEGIR_RUNNER) { case 'electron-main': - expect(NativeFetch.default).to.equal(NodeFetch.default) + expect(NativeFetch.fetch).to.equal(Undici.fetch) expect(globalThis.fetch).to.be.undefined() break case 'electron-renderer': - expect(NativeFetch.default).to.equal(globalThis.fetch) + expect(NativeFetch.fetch).to.equal(globalThis.fetch) expect(NativeFetch.Headers).to.equal(globalThis.Headers) expect(NativeFetch.Request).to.equal(globalThis.Request) expect(NativeFetch.Response).to.equal(globalThis.Response) expect(globalThis.fetch).to.be.ok() break case 'node': - expect(NativeFetch.default).to.equal(NodeFetch.default) + expect(NativeFetch.fetch).to.equal(Undici.fetch) expect(globalThis.fetch).to.be.undefined() break case 'browser': - expect(NativeFetch.default).to.equal(globalThis.fetch) + expect(NativeFetch.fetch).to.equal(globalThis.fetch) expect(NativeFetch.Headers).to.equal(globalThis.Headers) expect(NativeFetch.Request).to.equal(globalThis.Request) expect(NativeFetch.Response).to.equal(globalThis.Response) expect(globalThis.fetch).to.be.ok() break case 'webworker': - expect(NativeFetch.default).to.equal(globalThis.fetch) + expect(NativeFetch.fetch).to.equal(globalThis.fetch) expect(NativeFetch.Headers).to.equal(globalThis.Headers) expect(NativeFetch.Request).to.equal(globalThis.Request) expect(NativeFetch.Response).to.equal(globalThis.Response)