Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

fix: use the native fetch API in browsers and a patched node-fetch otherwise #25576

Merged
merged 3 commits into from
May 26, 2022
Merged

fix: use the native fetch API in browsers and a patched node-fetch otherwise #25576

merged 3 commits into from
May 26, 2022

Conversation

steveluscher
Copy link
Contributor

@steveluscher steveluscher commented May 26, 2022

Problem

  1. Presently, web3.js does not work in a Service Worker. cross-fetch is incompatible in that environment.
  2. We're including a fetch ponyfill when in reality web3.js is already incompatible with browsers that don't support fetch natively.

Summary of Changes

  • Augment the build system so that you can fork modules
  • Fork the browser version of the fetch implementation to very simply be globalThis.fetch which works in browser and Service Worker environments.

Notes on the new fork system

The fork system (which should really have been in a separate PR) lets you override modules depending on the build environment. Given that you have a module called foo, the build system will check if there's a module at that directory level called __forks__/CONFIG/foo.ts. If there is, that will be built into the bundle.

src/
L __forks__/
  L browser/
    L foo.ts  <-- this will be used in browser builds
L foo.ts  <-- this will be used in non-browser builds
L utils/
  L __forks__/
    L node/
      L assert.ts  <-- this will be used in node builds
  L assert.ts  <-- this will be used in non-node builds

Test plan

Ran the test plan at https://github.com/wentokay/unfetch-test. Thanks @wentokay!

Noted that this saves 2.62K gzipped (tested in Solana Explorer build).


This diff was inspired by the phenomenal PR #25500.

@steveluscher steveluscher requested review from jstarry and jordaaash May 26, 2022 04:33
@@ -0,0 +1 @@
export default globalThis.fetch;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

globalThis will resolve to self in workers, and window in browsers. Both have fetch available on them.

@jstarry
Copy link
Contributor

jstarry commented May 26, 2022

Aside.. @steveluscher what version of yarn do you use? I'm on 1.22.18 and I think we keep thrashing the lock file and creating huge diffs. Can we try to get on the same version?

@codecov
Copy link

codecov bot commented May 26, 2022

Codecov Report

Merging #25576 (e552067) into master (f9f6b94) will decrease coverage by 6.6%.
The diff coverage is n/a.

❗ Current head e552067 differs from pull request most recent head e7ac7d3. Consider uploading reports for the commit e7ac7d3 to get more accurate results

@@             Coverage Diff             @@
##           master   #25576       +/-   ##
===========================================
- Coverage    82.0%    75.3%     -6.7%     
===========================================
  Files         655       40      -615     
  Lines      171822     2342   -169480     
  Branches      335      336        +1     
===========================================
- Hits       140972     1765   -139207     
+ Misses      30734      459    -30275     
- Partials      116      118        +2     

@steveluscher
Copy link
Contributor Author

I'm on 1.22.17. 🤔

@jstarry
Copy link
Contributor

jstarry commented May 26, 2022

Maybe it's dependabot then 🤷🏻

@steveluscher
Copy link
Contributor Author

That little point version made all the difference! I just upgraded to 1.22.19 and now the diff is many times smaller.

@steveluscher steveluscher changed the title fix: fork fetch for browsers fix: use the native fetch API in browsers and a patched node-fetch otherwise May 26, 2022
Copy link
Contributor

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm but have a few questions.. and I think the typing magic you used is cool but hard to understand and maintain. I'd prefer if things were simpler.

init?: nodeFetch.RequestInit,
): Promise<nodeFetch.Response> {
const processedInput =
typeof input === 'string' && input.slice(0, 2) === '//'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when would the input be missing a protocol and just start with //?

Copy link
Contributor Author

@steveluscher steveluscher May 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a standard patch for the broken implementation of node-fetch that doesn't support protocol-relative URLs like the fetch in browsers does. https://github.com/node-fetch/node-fetch/blob/main/docs/v2-LIMITS.md

Folks can and will write apps that make use of protocol-relative URLs, and we don't want them to break in Node when they do.

This replicates what cross-fetch was already doing.

https://github.com/lquixada/cross-fetch/blob/24dedb4c8a16a33cb9b4d4682fb731438a6a9e2d/src/node-ponyfill.js#L4-L11

fetchMiddleware?: FetchMiddleware,
disableRetryOnRateLimit?: boolean,
): RpcClient {
const fetch = customFetch ? customFetch : crossFetch;
const fetchImpl = (customFetch ? customFetch : fetch) as FetchFn;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: how about importing ./fetch-iml as fetchImpl and keeping this as fetch?

Comment on lines 2165 to 2167
) => any
? (...args: A) => TReturn
: never;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't really make sense of this syntax.. what is being evaluated by the ? operator?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I was hoping the names of the utility types would make the reader incurious as to how it's implemented. These types should really just be TypeScript builtins, but alas.

I'll kill this stuff and just write the types manually.

Comment on lines 2179 to 2182
export type FetchMiddleware = AlterReturnType<
AddParameters<FetchFn, [fetch: AlterReturnType<FetchFn, void>]>,
void
>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the benefit of expressing the type of FetchMiddleware like this over the original type signature which is more explicit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the original signature is just straight up wrong. The first parameter to fetch is RequestInfo not string. The second RequestInit not any. I'm just trying to make everything properly line up with the type of the fetch-impl shim.

@steveluscher steveluscher merged commit da28bad into solana-labs:master May 26, 2022
@steveluscher steveluscher deleted the fork-fetch-for-browsers branch May 26, 2022 21:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants