Skip to content

How to properly polyfill fetch/XMLHttpRequest in node #586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
flimzy opened this issue Feb 13, 2017 · 19 comments
Open

How to properly polyfill fetch/XMLHttpRequest in node #586

flimzy opened this issue Feb 13, 2017 · 19 comments

Comments

@flimzy
Copy link
Member

flimzy commented Feb 13, 2017

The compatibility table mentions partial support for 'net/http', saying:

client only, emulated via Fetch/XMLHttpRequest APIs; node.js requires polyfill

But I'm not having any luck figuring out how to get such a polyfill to work. The API detection happens very early in the Go program initialization, such that, as far as I can tell, it is impossible to load a polyfill in time for it to be detected.

Is the only option to prepend GopherJS's output with something like var fetch = require('fetch-node'); before executing it?

@flimzy flimzy changed the title How to properly polifyl fetch/XMLHttpRequest in node How to properly polyfill fetch/XMLHttpRequest in node Feb 13, 2017
@dmitshur
Copy link
Member

@flimzy I believe this issue is at least related to #518, so I want to ask if you've already seen that?

@MartinSahlen
Copy link

I would recommend to see my Comment on #518. However it should be mentioned that polyfilling in this case meant including the polyfill in the build with a name .inc.js, not doing anything with the output. The polyfill must be present at transpile time, not at node.js runtime.

@flimzy
Copy link
Member Author

flimzy commented Feb 14, 2017

However it should be mentioned that polyfilling in this case meant including the polyfill in the build with a name .inc.js, not doing anything with the output.

@MartinSahlen I tried with an .inc.js file, too, to no avail. Could you point me to a working example?

The polyfill must be present at transpile time, not at node.js runtime.

Is that really true? The check appears to be done at runtime. Is there an additional check at compile time? (and how would that even be done, since the transpiler doesn't know which JavaScript engine/browser is being targeted?)

@MartinSahlen
Copy link

MartinSahlen commented Feb 14, 2017

@flimzy TBH, I will not write my claims in stone. They are just based on lengthy trial and error. I tried for instance, just stubbing out fetch and ReadableStream as global objects and then do a require on the transpiled output from gopherjs. It did not seem to work, getting the same message about
"net/http: neither of Fetch nor XMLHttpRequest APIs is available".

However including it in the build step made it "work", as in it: The code tried to do a request but got strange errors (even after properly polyfilling fetch and ReadableStream). Using a version of XMLHttpRequest, (https://www.npmjs.com/package/xhr2) , did the trick for me and then using webpack to bundle it all into a .inc.js file.

I would 1) recommend you read my comment here #518 (comment) to read a bit more about what seems to be needed to do a polyfill, at least from empirical observations and 2) check out https://github.com/MartinSahlen/go-cloud-fn for a working example

The docs are regrettingly pretty scarce on this one so as I said these are just based on my experiments. Would be happy to be proven wrong and devised a simpler solution 😅

@flimzy
Copy link
Member Author

flimzy commented Feb 14, 2017

Thanks for the additional information, and the link to go-cloud-fn.

However, I don't see a .inc.js file in use here. It appears that the polyfill is bundled with the GopherJS output with webpack, which is essentially the same as the option I asked about in the original question:

Is the only option to prepend GopherJS's output with something like var fetch = require('fetch-node'); before executing it?

Am I missing something?

@MartinSahlen
Copy link

I'm sorry, I'm just assuming we are all in the same state of mind here.

So, what I found is that doing a require for the polyfill did not work. The reason I can only guess but I decided to use webpack to just bundle everything needed for the polyfill in a big fat file in the same manner as you would with a frontend app (generating a bundle.js file) so that it would be agnostic of any module loader system or something, just plain javascript with a global object (node equivalent of window) that contains the polyfill(s).

In my case (https://github.com/MartinSahlen/go-cloud-fn/blob/master/node-compat/webpack.config.js) I set the output to polyfill.inc.js as you see in the webpack config. So the polyfill.inc.js is, as you can see in the .gitignore not included since it is just the generated output for the polyfill file (https://github.com/MartinSahlen/go-cloud-fn/blob/master/node-compat/polyfill.js).

If you look at the https://github.com/MartinSahlen/go-cloud-fn/blob/master/package.json file you can see specifically how this is done.

Does this make sense to you @flimzy ?

@flimzy
Copy link
Member Author

flimzy commented Feb 14, 2017

Ahh, I did miss that your .inc.js was generated. Thanks for the clarification. I'll look more closely at this when I have a chance. I think this will be quite helpful.
👍

@flimzy
Copy link
Member Author

flimzy commented Feb 26, 2017

Today I'm finally getting back to this issue, and today I have no problems at all. I'm not sure what I was doing differently before, but today everything works exactly as expected when I simply do the same as you with:

global.XMLHttpRequest = require('xhr2');

@flimzy
Copy link
Member Author

flimzy commented Feb 26, 2017

I think I was previously trying various fetch polyfills, which all failed for me. Both xhr and xhr2 satisfy GopherJS, but fail at runtime for me. So I'm still investigating. I'll probably write up a mini howto for the wiki when I find something that works.

@MartinSahlen
Copy link

What fails? Have you remembered to wrap http calls in goroutines to avoid blocking?

@MartinSahlen
Copy link

And are you using webpack to bundle dependencies? Just curious to know if your setup deviates from mine, as I have this confirmed working.

@flimzy
Copy link
Member Author

flimzy commented Feb 26, 2017

With xhr2, my failure is:

throw new Error("Unsupported send() data " + data);
      ^
Error: Unsupported send() data 
at XMLHttpRequestUpload._setData (/home/jonhall/go/src/github.com/flimzy/kivik/node_modules/xhr2/lib/xhr2.js:844:15)
at XMLHttpRequest._sendHttp (/home/jonhall/go/src/github.com/flimzy/kivik/node_modules/xhr2/lib/xhr2.js:375:19)
at XMLHttpRequest.send (/home/jonhall/go/src/github.com/flimzy/kivik/node_modules/xhr2/lib/xhr2.js:203:16)
at $packages.net/http.XHRTransport.ptr.RoundTrip (/home/jonhall/go/src/github.com/flimzy/kivik/http.go:99:3)
at send (/net/http/client.go:249:3)
at $packages.net/http.Client.ptr.send (/net/http/client.go:173:3)
at $packages.net/http.Client.ptr.Do (/net/http/client.go:595:7)
at $packages.github.com/flimzy/kivik/driver/couchdb.client.ptr.makeRequest (/github.com/flimzy/kivik/driver/couchdb/http.go:37:3)
<snip>

With xhr the failure is:

(node:9035) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: callback argument missing

(with no stack trace).

I'm attempting to debug the xhr2 failure at the moment.

When using the various fetch polyfills, I can't even get GopherJS to acknowledge that the polyfill is loaded. So I'm probably still doing something wrong there.

@flimzy
Copy link
Member Author

flimzy commented Feb 26, 2017

No, I'm not using webpack. I just added the following code to an *.inc.js, and am running gopherjs test ...:

try {
    global.XMLHttpRequest = require('xhr');
} catch(e) {}

@flimzy
Copy link
Member Author

flimzy commented Feb 26, 2017

I have expanded on my investigation of the problem with xhr2 in #597

@dmitshur
Copy link
Member

dmitshur commented Mar 1, 2017

When I was looking over http://redux.js.org/docs/advanced/AsyncActions.html#async-action-creators, I spotted a mention of a polyfill for Fetch API for node:

Internally, it uses whatwg-fetch polyfill on the client, and node-fetch on the server, ...

Given that GopherJS implements an http.RoundTripper based on Fetch API in its implementation of net/http, perhaps that's a good thing to try?

I don't see it mentioned here, but in the previous related issue #518, @MartinSahlen said he tried it:

I tried

@MartinSahlen, what was your experience with node-fetch, if you recall?

@broady
Copy link

broady commented Mar 1, 2017

I suppose I should try to find my polyfill for the net package. It's on my filesystem somewhere.

It implements net.Dialer, and then you can just use the net/http package directly.

@dmitshur
Copy link
Member

dmitshur commented Mar 1, 2017

On the topic of incorporating some node module for this (/cc @flimzy), I wonder if we can reuse the approach used for installing the syscall module? E.g., https://github.com/gopherjs/gopherjs/blob/master/doc/syscalls.md.

@MartinSahlen
Copy link

MartinSahlen commented Mar 1, 2017

My experience with node-fetch was that it did not work at all (it might be that I did not manage to properly polyfill the ReadableStream though.

@pjebs
Copy link
Contributor

pjebs commented Jul 8, 2018

I used the global.XMLHttpRequest = require('xhr'); approach.
Has anyone worked out a way to get xhr2 to respect context cancellation? The xhr2 documentation states it supports abort(). Other than that, it works well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants