Skip to content

Commit 2264583

Browse files
committed
Use polyfills only if they don't exist in the runtime
1 parent 43fc759 commit 2264583

4 files changed

Lines changed: 98 additions & 83 deletions

File tree

deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"compilerOptions": {
2727
"lib": [
2828
"dom",
29+
"dom.iterable",
2930
"deno.ns"
3031
]
3132
}

src/serpapi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export async function getJson<
9191
timeout,
9292
);
9393
const json = await response.json() as BaseResponse<E>;
94-
const nextParametersFromResponse = extractNextParameters<E>(json);
94+
const nextParametersFromResponse = await extractNextParameters<E>(json);
9595
if (
9696
// https://github.com/serpapi/public-roadmap/issues/562
9797
// https://github.com/serpapi/public-roadmap/issues/563

src/utils.ts

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import type { EngineName, EngineParameters } from "./types.ts";
22
import { version } from "../version.ts";
3-
import fetch from "npm:cross-fetch@3.1.4";
4-
import core from "npm:core-js-pure@3.28.0";
5-
const { globalThis, URL, URLSearchParams } = core;
63

74
type UrlParameters = Record<
85
string,
@@ -11,26 +8,48 @@ type UrlParameters = Record<
118

129
/**
1310
* This `_internals` object is needed to support stubbing/spying of
14-
* fetch, execute and getBaseUrl.
11+
* certain functions in this file.
1512
* https://deno.land/manual@v1.28.3/basics/testing/mocking
1613
*
17-
* If fetch is stubbed via `globalThis`, the test phase of the npm build fails.
18-
* ```ts
19-
* const fetchStub = stub(globalThis, "fetch", resolvesNext([new Response("data")]));
20-
* ```
21-
*
22-
* [`dnt`](https://github.com/denoland/dnt) shims `fetch` by relying on the
23-
* `undici` package. It replaces all references to `fetch` with `dntShim.fetch`.
24-
* As a side effect, stubbing `globalThis.fetch` becomes incorrect; we want to
25-
* stub `dntShim.fetch` instead.
26-
*
27-
* As a workaround, the `_internals` object serves as an indirection and we
28-
* stub the `fetch` key of this object instead.
14+
* It's also useful to encapsulate functions that are polyfilled.
2915
*/
3016
export const _internals = {
31-
fetch: fetch,
17+
get fetch() {
18+
return (async () => {
19+
// Use runtime's `fetch` if it exists, otherwise fallback to `cross-fetch`.
20+
return typeof fetch === "function"
21+
? Promise.resolve(fetch)
22+
: (await import("npm:cross-fetch@3.1.4")).default;
23+
})();
24+
},
3225
execute: execute,
3326
getBaseUrl: getBaseUrl,
27+
get globalThis() {
28+
return (async () => {
29+
// Use runtime's `globalThis` if it exists, otherwise fallback to `core-js`'s implementation.
30+
// dnt-shim-ignore
31+
const gt = typeof globalThis !== "undefined" ? globalThis : undefined;
32+
return gt !== undefined
33+
? Promise.resolve(gt)
34+
: (await import("npm:core-js-pure@3.28.0")).default.globalThis;
35+
})();
36+
},
37+
get URL(): Promise<typeof URL> {
38+
return (async () => {
39+
// Use runtime's `URL` if it exists, otherwise fallback to `core-js`'s implementation.
40+
return typeof URL !== "undefined"
41+
? Promise.resolve(URL)
42+
: (await import("npm:core-js-pure@3.28.0")).default.URL;
43+
})();
44+
},
45+
get URLSearchParams(): Promise<typeof URLSearchParams> {
46+
return (async () => {
47+
// Use runtime's `URLSearchParams` if it exists, otherwise fallback to `core-js`'s implementation.
48+
return typeof URLSearchParams !== "undefined"
49+
? Promise.resolve(URLSearchParams)
50+
: (await import("npm:core-js-pure@3.28.0")).default.URLSearchParams;
51+
})();
52+
},
3453
};
3554

3655
/** Facilitates stubbing in tests, e.g. localhost as the base url */
@@ -46,14 +65,17 @@ type NextParameters<E extends EngineName = EngineName> = {
4665
>
4766
]: string;
4867
};
49-
export function extractNextParameters<E extends EngineName = EngineName>(json: {
50-
serpapi_pagination?: { next: string };
51-
pagination?: { next: string };
52-
}) {
68+
export async function extractNextParameters<E extends EngineName = EngineName>(
69+
json: {
70+
serpapi_pagination?: { next: string };
71+
pagination?: { next: string };
72+
},
73+
) {
5374
const nextUrlString = json["serpapi_pagination"]?.["next"] ||
5475
json["pagination"]?.["next"];
5576

5677
if (nextUrlString) {
78+
const URL = await _internals.URL;
5779
const nextUrl = new URL(nextUrlString);
5880
const nextParameters: Record<string, string> = {};
5981
for (const [k, v] of nextUrl.searchParams.entries()) {
@@ -78,22 +100,20 @@ export function haveParametersChanged(
78100
);
79101
}
80102

81-
function getSource() {
103+
export async function getSource() {
82104
const moduleSource = `serpapi@${version}`;
83105
try {
106+
const gt = await _internals.globalThis;
107+
84108
// Check if running in Node.js
85-
// dnt-shim-ignore
86-
// deno-lint-ignore no-explicit-any
87-
const nodeVersion = (globalThis as any).process?.versions?.node;
109+
const nodeVersion = gt.process?.versions?.node;
88110
if (nodeVersion) {
89111
return `nodejs@${nodeVersion},${moduleSource}`;
90112
}
91113

92114
// Assumes running in Deno instead. https://deno.land/api?s=Deno.version
93115
// Deno.version is not shimmed since it's not used when ran in a Node env.
94-
// dnt-shim-ignore
95-
// deno-lint-ignore no-explicit-any
96-
const denoVersion = (globalThis as any).Deno?.version?.deno;
116+
const denoVersion = gt.Deno?.version?.deno;
97117
if (denoVersion) {
98118
return `deno@${denoVersion},${moduleSource}`;
99119
}
@@ -105,37 +125,40 @@ function getSource() {
105125
}
106126
}
107127

108-
export function buildUrl<P extends UrlParameters>(
128+
export async function buildUrl<P extends UrlParameters>(
109129
path: string,
110130
parameters: P,
111-
): string {
131+
): Promise<string> {
112132
const nonUndefinedParams: [string, string][] = Object.entries(parameters)
113133
.filter(([_, value]) => value !== undefined)
114134
.map(([key, value]) => [key, `${value}`]);
135+
const URLSearchParams = await _internals.URLSearchParams;
115136
const searchParams = new URLSearchParams(nonUndefinedParams);
116137
return `${_internals.getBaseUrl()}${path}?${searchParams}`;
117138
}
118139

119-
export function execute<P extends UrlParameters>(
140+
export async function execute<P extends UrlParameters>(
120141
path: string,
121142
parameters: P,
122143
timeout: number,
123144
): Promise<Response> {
124-
const url = buildUrl(path, {
145+
const url = await buildUrl(path, {
125146
...parameters,
126-
source: getSource(),
147+
source: await getSource(),
127148
});
128149
// https://github.com/github/fetch/issues/175#issuecomment-216791333
129150
return new Promise((resolve, reject) => {
130151
const timer = setTimeout(() => reject(new Error("Timeout")), timeout);
131-
_internals.fetch(url)
132-
.then((res) => {
133-
clearTimeout(timer);
134-
resolve(res);
135-
})
136-
.catch((err) => {
137-
clearTimeout(timer);
138-
reject(err);
139-
});
152+
_internals.fetch.then((fetch) =>
153+
fetch(url)
154+
.then((res) => {
155+
clearTimeout(timer);
156+
resolve(res);
157+
})
158+
.catch((err) => {
159+
clearTimeout(timer);
160+
reject(err);
161+
})
162+
);
140163
});
141164
}

tests/utils_test.ts

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import {
55
describe,
66
it,
77
} from "https://deno.land/std@0.170.0/testing/bdd.ts";
8-
import {
9-
assertSpyCalls,
10-
resolvesNext,
11-
Stub,
12-
stub,
13-
} from "https://deno.land/std@0.170.0/testing/mock.ts";
8+
import { Stub, stub } from "https://deno.land/std@0.170.0/testing/mock.ts";
149
import {
1510
assertEquals,
1611
assertMatch,
@@ -21,19 +16,19 @@ import {
2116
buildUrl,
2217
execute,
2318
extractNextParameters,
19+
getSource,
2420
haveParametersChanged,
2521
} from "../src/utils.ts";
26-
import { Response } from "npm:cross-fetch@3.1.4";
2722

2823
loadSync({ export: true });
2924
const BASE_URL = Deno.env.get("ENV_TYPE") === "local"
3025
? "http://localhost:3000"
3126
: "https://serpapi.com";
3227

3328
describe("extractNextParameters", () => {
34-
it("with serpapi_pagination property", () => {
29+
it("with serpapi_pagination property", async () => {
3530
assertEquals(
36-
extractNextParameters<"google">({
31+
await extractNextParameters<"google">({
3732
serpapi_pagination: {
3833
next:
3934
"https://serpapi.com/search.json?device=desktop&engine=google&gl=us&google_domain=google.com&hl=en&location=Austin%2C+Texas%2C+United+States&q=coffee&start=10",
@@ -51,9 +46,9 @@ describe("extractNextParameters", () => {
5146
);
5247
});
5348

54-
it("with pagination property", () => {
49+
it("with pagination property", async () => {
5550
assertEquals(
56-
extractNextParameters<"google_scholar_profiles">(
51+
await extractNextParameters<"google_scholar_profiles">(
5752
{
5853
pagination: {
5954
next:
@@ -170,6 +165,15 @@ describe("haveParametersChanged", () => {
170165
});
171166
});
172167

168+
describe("getSource", () => {
169+
it("use runtime version", async () => {
170+
assertMatch(
171+
await getSource(),
172+
/(nodejs|deno)@\d+\.\d+\.\d+,serpapi@\d+\.\d+\.\d+$/,
173+
);
174+
});
175+
});
176+
173177
describe("buildUrl", () => {
174178
let urlStub: Stub;
175179

@@ -181,24 +185,32 @@ describe("buildUrl", () => {
181185
urlStub.restore();
182186
});
183187

184-
it("with blank path and empty parameters", () => {
185-
assertEquals(buildUrl("", {}), `${BASE_URL}?`);
188+
it("with blank path and empty parameters", async () => {
189+
assertEquals(await buildUrl("", {}), `${BASE_URL}?`);
186190
});
187191

188-
it("with path and empty parameters", () => {
189-
assertEquals(buildUrl("/", {}), `${BASE_URL}/?`);
192+
it("with path and empty parameters", async () => {
193+
assertEquals(await buildUrl("/", {}), `${BASE_URL}/?`);
190194
});
191195

192-
it("with path and parameters", () => {
196+
it("with path and parameters", async () => {
193197
assertEquals(
194-
buildUrl("/search", { q: "coffee", gl: "us" }),
198+
await buildUrl("/search", { q: "coffee", gl: "us" }),
195199
`${BASE_URL}/search?q=coffee&gl=us`,
196200
);
197201
});
198202

199-
it("with undefined parameters", () => {
203+
it("with source", async () => {
204+
const url = await buildUrl("/search", { source: await getSource() });
205+
assertMatch(
206+
url,
207+
/source=(nodejs|deno)%40\d+\.\d+\.\d+%2Cserpapi%40\d+\.\d+\.\d+$/,
208+
);
209+
});
210+
211+
it("with undefined parameters", async () => {
200212
assertEquals(
201-
buildUrl("/search", { q: "coffee", gl: undefined, hl: null }),
213+
await buildUrl("/search", { q: "coffee", gl: undefined, hl: null }),
202214
`${BASE_URL}/search?q=coffee&hl=null`,
203215
);
204216
});
@@ -218,27 +230,6 @@ describe("execute", {
218230
urlStub.restore();
219231
});
220232

221-
it("with path and parameters calls fetch with source appended", async () => {
222-
const fetchStub = stub(
223-
_internals,
224-
"fetch",
225-
resolvesNext([new Response("data")]),
226-
);
227-
try {
228-
await execute("/search", { q: "coffee", gl: "us" }, 4000);
229-
} finally {
230-
fetchStub.restore();
231-
}
232-
233-
assertSpyCalls(fetchStub, 1);
234-
const url = fetchStub.calls[0].args[0] as string;
235-
// e.g. deno@1.28.2,serpapi@1.0.0
236-
assertMatch(
237-
url,
238-
/source=(nodejs|deno)%40\d+\.\d+\.\d+%2Cserpapi%40\d+\.\d+\.\d+$/,
239-
);
240-
});
241-
242233
it("with short timeout", () => {
243234
assertRejects(async () =>
244235
await execute("/search", { q: "coffee", gl: "us" }, 1)

0 commit comments

Comments
 (0)