Skip to content
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

Replace deprecated requests with node-fetch #191

Merged
merged 19 commits into from
Jul 23, 2020

Conversation

rmeritz
Copy link
Contributor

@rmeritz rmeritz commented Jun 18, 2020

@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 18, 2020

So it seems that node-fetch doesn't work with node 8 which is now in LTS. https://nodejs.org/en/blog/release/v8.9.0/

I've replicated all the falling tests on the node 8 build locally and it is a real problem with the library.

Looking at the commit history it seems like this library had only be supporting node 10+ but then a decision was explicitly made to support node 8+. 31fd0a0

@kasrak - Do we definitely want to support node 8 moving forward? I can close this in favor of another solution if so. Sorry I didn't catch this issue earlier, but at least we have CI!

@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 18, 2020

@kasrak - So it turns out the probably wasn't with node-fetch but with my own code changes because I was using finally which wasn't in node 8. So you can ignore what I said about node 8. This should be good to go now.

Copy link
Contributor

@kasrak kasrak left a comment

Choose a reason for hiding this comment

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

Hm, this causes the browser build (airtable.browser.js) to go from ~200KB to 2MB which seems fishy.

@kasrak
Copy link
Contributor

kasrak commented Jun 23, 2020

In order to test against the latest changes I regenerated thebrowserifyed js with node_modules/grunt/bin/grunt browserify and copied the built file to the test directly. I'm unclear what the process should be for when airtable.browser.js should be updated. I didn't check in the built version, because I assume that is only updated when I release is done. And explanation of how and when to regenerate the airtable.browser.js would be great to add to the README, but I'm not sure what the process is.

Yep, the workflow is to manually generate a new airtable.browser.js as part of a release (e.g. 31fd0a0). But in this case let's keep checking in the built bundle to verify the size as part of this code review. Once we're happy with it, we'll uncheck-in those changes and land this PR without any changes to airtable.browser.js

- Tested that `node-fetch` replacement actually works by running the
`test/test_files/index.html`
- In order to test againast the latest changes I regenerated the
browserifyed js with `node_modules/grunt/bin/grunt browserify`.
- I copied the built file to the test directly.
- I'm unclear what the process is for when `airtable.browser.js` should
be updated. I didn't check in the built version, because I assume that
is only updated when I release is done, but I'm not clear what the
process is. That would be something that would be great to clarify in
the README.
- Fix sending a signal so that it actually aborts
@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 23, 2020

@kasrak - I'm working on figuring out a solution to this. In order to move forward it would be helpful to know what browsers (and versions) this library should support. It would help me figure out what polyfills I need to add (or not) and it would help future users have a clear understanding of if the library will work for them or not.

I see we are already polyfilling promise even though it has wide support (except for IE). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility Should that be taken as an explicate decision to support at least IE?

I need to determine if I need to polyfill fetch and abortController as well. https://developer.mozilla.org/en-US/docs/Web/API/AbortController https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility

The deprecated requests module was also being used in
`base#makeRequests` even though it was no longer in the list of
installed packages in package.json browserify was pulling it it which
was the root cause of the package being so bloated (because I was
not longer replaceing requests with xhr).

This replaces `request` with `node-fetch` in `makeRequest`.
`makeRequest`s isn't not actually used anywhere. All API calls actually
are going through `runAction` instead and I think maybe `makeRequest`s
should just be removed but in order to do that we need to get more
clarity around what is public vs private.
- Regenerate the the airtable.browser.js via grunt and ensure that it
works via the python server
- Replace `node-fetch` and `abort-controller` packages with the build in
versions on the window for the browser build
- Maybe they should instead be replace by polyfills. There is a lack of
clarity of what browsers needs to be supported.
- Both fetch and abortController are widely supported by many browsers
so I think the polyfills can be skipped.

https://developer.mozilla.org/en-US/docs/Web/API/AbortController#Browser_compatibility
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility
@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 24, 2020

@kasrak - I figured out what was causing the bloat of the built package size. It turns out there was a second use of the request package in base#makeRequest that I was missing. And even though I had removed the request library from package.json browserify was pulling it back in. I've removed the use of request in base#makeRequest with fetch as well. And now the built library is back down to 220K.

This is now working but it would still be great to address the question I asked about what browser versions we want to support. I didn't end up pulling in pollyfills for fetch or abortController because they are both widely supported and I didn't want to add bloat. It would be very helpful to me and all future users and maintainers to clearly lay out in the README what versions are supported.

Can you clarify what versions we support so I can determine if we need to add polyfills or not?

Maybe that would also allow us to pull out the Promise polyfill library as one user already asked for. #179 I would be happy to make that change as well.

Also, one reason I missed the use of request in base#makeRequest is that makeRequest is never actually used anywhere. It is technically possible to access it but 100% of API calls are filtered through runAction which does practically the same thing as makeRequest. Can I just remove makeRequest or deprecate it? Do we consider it a public API? I cannot imagine a user wanting to use it and it isn't part of the documentation.

Speaking of the public API, when working making changes to this library and writing tests it came up over and over again that there isn't a clear distinction documented anywhere of what is a public and what is a private interface. I think it would very helpful to the long term maintainability of the project to provide clarity around this. I would be more than happy to add jdocs to make this clear (using the public API docs as my guide) if you agree that that would be a useful path forward. Do you think it makes sense for me to go ahead and do this? (Esp ahead of the Typescript changes).

@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 24, 2020

Also, I cannot add linked issues, but this should close #153 when merged.

@rmeritz rmeritz requested a review from kasrak June 24, 2020 20:19
@kasrak
Copy link
Contributor

kasrak commented Jun 26, 2020

This is now working but it would still be great to address the question I asked about what browser versions we want to support. I didn't end up pulling in pollyfills for fetch or abortController because they are both widely supported and I didn't want to add bloat. It would be very helpful to me and all future users and maintainers to clearly lay out in the README what versions are supported.

Let's match the supported browser list for Airtable (https://support.airtable.com/hc/en-us/articles/217990018), which is currently:

  • Chrome 67 or higher
  • Firefox 52 or higher
  • Safari 10 or higher
  • Edge 25 or higher

So we can drop support for IE, drop the Promise polyfill (maybe in a separate PR?), and do a major version bump when we release the next version of Airtable.js

Can I just remove makeRequest or deprecate it? Do we consider it a public API?

Let's leave it in for now, it's handy for making more custom requests to the API.

there isn't a clear distinction documented anywhere of what is a public and what is a private interface.

By convention, any identifiers beginning with _ are private. Let's chat separately about ways to make that more clear.

Does any of the above require changes to this PR? Let me know if not and I'll review the code changes. Thanks!

@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 26, 2020

Does any of the above require changes to this PR? Let me know if not and I'll review the code changes. Thanks!

Yes. I have to make one more set of changes. Thanks so much for providing clear requirements on browser support. It does mean that I will need to add 2 polyfills though: one for fetch just for Safari 10.0 and one for AbortController for Safari 10 - 11 and Firefox 52 - 56.

drop the Promise polyfill (maybe in a separate PR?), and do a major version bump when we release the next version of Airtable.js

I'm drop the promise polyfill in a follow-up PR.

By convention, any identifiers beginning with _ are private. Let's chat separately about ways to make that more clear.

I think that is quite clear. But the API documented in the developer docs for airtable.js (the ones generated per base) only shows the use of a tiny tiny percentage of what according to the convention is technically public. It would be helpful to the long-term maintainability of the library to only commit to a small externally public API -- meaning that there would be a distinction between methods that a are private to a file (using underscore) and methods that are private to external use. Anyway, this definitely doesn't have to be answered in this PR at all. I just thought I would mention it while it was top of mind.

- Add AbortController polyfill for Safari 10 - 11 and Firefox 52 - 56
- Was going to add fetch polyfill but it is only missing from Safari 10.0. It is in Safari 10.1 and all
other browsers that need to be supported. All Safari before 10.1 is only
0.26% of all browsers usage. It is not possible to test Safari 10.0 on
Browserstack and the complexity and size cost of adding the polyfill
for this one minor version is large, so it was skipped it.

Including the one polyfill brings the size of the built package to 232K up
from 220K.
@rmeritz
Copy link
Contributor Author

rmeritz commented Jun 26, 2020

@kasrak - I added the polyfill needed and tested it works with Browserstack in the older browsers, so you can review it again.

Copy link
Contributor

@minicat minicat left a comment

Choose a reason for hiding this comment

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

looks mostly good too me, had a few questions!

README.md Outdated
@@ -18,6 +18,7 @@ To install airtable.js in a node project:

npm install airtable

Airtable.js should work with Node 10 and above.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe is compatible with instead of should work? So the wording is a bit more confidence inspiring 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call.

README.md Outdated
@@ -33,6 +34,8 @@ Edit `test/test_files/index.html` - put your `BASE_ID` and `API_KEY` (Be careful

Then open http://localhost:8000/ in your browser.

Airtable.js should work with browsers supported by Airtable App.
Copy link
Contributor

Choose a reason for hiding this comment

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

is compatible with and Airtable web app

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed as well.

@@ -1,4 +1,17 @@
require=(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
Copy link
Contributor

Choose a reason for hiding this comment

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

remember to revert these changes before landing!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've reverted it.

@@ -346,32 +355,26 @@ describe('Base', function() {
});

it('retries 429s until success', function() {
const realSetTimeout = setTimeout;

jest.useFakeTimers();
Copy link
Contributor

Choose a reason for hiding this comment

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

why was this changed? & number of requests changed from 3 to 2

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was a few weeks ago, so I'm not 100% sure. But I think I just changed it from 2 to 3 requests to make the tests faster.

As work that procceded, I wrote a bunch of new timeout tests (to get 100% coverage) that use the actual timeouts and I think I just made this test more consistent with the other.

I think I did this work because I ran into an issue with how I passed the timeout signal with the new node fetch library which caused me to dig in with these tests more.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh i see - sounds reasonable, thanks!

if ('body' in options && _canRequestMethodIncludeBody(method)) {
requestOptions.body = options.body;
requestOptions.body = JSON.stringify(options.body);
Copy link
Contributor

Choose a reason for hiding this comment

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

im assuming the JSON.stringify was previously handled by the request library?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was.

@@ -143,6 +143,7 @@ function getMockEnvironmentAsync(options) {
airtable: new Airtable({
apiKey: 'key123',
endpointUrl: 'http://localhost:' + testServerPort,
requestTimeout: 1000,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe add a comment mentioning this is required for the timeout tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@rmeritz
Copy link
Contributor Author

rmeritz commented Jul 14, 2020

@minicat - Thanks for taking a look. I think I addressed all your comments and answered your questions now.

Also, if this is ready to land. I'm 100% sure that I don't have permission do it and someone will need to do it internally.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@@ -346,32 +355,26 @@ describe('Base', function() {
});

it('retries 429s until success', function() {
const realSetTimeout = setTimeout;

jest.useFakeTimers();
Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh i see - sounds reasonable, thanks!

Copy link
Contributor

@minicat minicat left a comment

Choose a reason for hiding this comment

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

thanks! A few spelling nits but otherwise looks good to go

rmeritz and others added 2 commits July 16, 2020 10:07
Co-authored-by: Emma Yeap <6092008+minicat@users.noreply.github.com>
Co-authored-by: Emma Yeap <6092008+minicat@users.noreply.github.com>
@rmeritz
Copy link
Contributor Author

rmeritz commented Jul 16, 2020

@minicat - I committed spelling/grammar changes you caught. Thanks!

- Bodys sent with HEAD/GET requests should not cause TypeErrors instead
just add a console.warning.
`runAction` exposes a `Response` object in its callback.

The old `request` library had a slightly different interface than the
one for the `Response` object for `node-fetch`.

Specifically, both have `body` defined by `request` had `body` as the
parsed json body. Add the parsed json body to the `Response` object
returned by runAction. `body` has no `setter` and only a `getter` so a
new object needs to be created instead.
@minicat minicat merged commit 5d3948d into Airtable:master Jul 23, 2020
rmeritz added a commit to bocoup/airtable.js that referenced this pull request Jul 23, 2020
The Promise polyfill is not needed with the outlined techinal
requirements for node or browser support.

Closes Airtable#179

A follow up to: Airtable#191
@rmeritz rmeritz mentioned this pull request Jul 23, 2020
PeterPan627 added a commit to PeterPan627/airtable.js that referenced this pull request May 28, 2023
The Promise polyfill is not needed with the outlined techinal
requirements for node or browser support.

Closes Airtable/airtable.js#179

A follow up to: Airtable/airtable.js#191
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

Successfully merging this pull request may close these issues.

3 participants