Skip to content

Commit 72e0056

Browse files
committed
feat: add puppeteer adapter
1 parent ff658b4 commit 72e0056

File tree

10 files changed

+964
-42
lines changed

10 files changed

+964
-42
lines changed

.changeset/four-vans-shake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@fetch-impl/puppeteer": major
3+
---
4+
5+
Puppeteer adapter, supports fetch in real browser.

packages/fetcher/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Can be used as a global fetch implementation or as a local fetch implementation
66

77
- [@fetch-impl/cross-fetch](https://www.npmjs.com/package/@fetch-impl/cross-fetch) - Use [cross-fetch](https://www.npmjs.com/package/cross-fetch) as the fetch implementation.
88
- [@fetch-impl/playwright](https://www.npmjs.com/package/@fetch-impl/playwright) - Use [playwright](https://www.npmjs.com/package/playwright) browser as the fetch implementation.
9+
- [@fetch-impl/puppeteer](https://www.npmjs.com/package/@fetch-impl/puppeteer) - Use [puppeteer](https://www.npmjs.com/package/puppeteer) browser as the fetch implementation.
910

1011
## Usage
1112

packages/puppeteer/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# @fetch-impl/puppeteer
2+
3+
User Configurable Fetch Implementation. Puppeteer adapter, supports fetch in real browser.
4+
5+
## Usage
6+
7+
```ts
8+
import puppeteer from "puppeteer";
9+
import { usePuppeteer } from "@fetch-impl/puppeteer";
10+
import fetch from "@fetch-impl/fetcher";
11+
12+
// configure fetch to use puppeteer
13+
usePuppeteer(puppeteer.launch());
14+
15+
// use fetch as usual
16+
fetch("https://example.com").then(async (res) => {
17+
console.log(res.status, await res.text());
18+
});
19+
```
20+
21+
Note: The browser will not be closed automatically. You should close it manually when you are done with it.

packages/puppeteer/package.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"name": "@fetch-impl/puppeteer",
3+
"description": "User Configurable Fetch Implementation. Puppeteer adapter, supports fetch in real browser.",
4+
"version": "0.0.0",
5+
"author": "JacobLinCool <jacoblincool@gmail.com> (https://jacoblin.cool)",
6+
"license": "MIT",
7+
"keywords": [
8+
"fetch",
9+
"puppeteer"
10+
],
11+
"main": "dist/index.js",
12+
"types": "dist/index.d.ts",
13+
"exports": {
14+
"import": "./dist/index.mjs",
15+
"require": "./dist/index.js",
16+
"types": "./dist/index.d.ts",
17+
"default": "./dist/index.mjs"
18+
},
19+
"files": [
20+
"dist"
21+
],
22+
"scripts": {
23+
"build": "tsup",
24+
"build:docs": "typedoc --out docs src"
25+
},
26+
"dependencies": {
27+
"debug": "^4.3.4"
28+
},
29+
"peerDependencies": {
30+
"@fetch-impl/fetcher": "workspace:^",
31+
"puppeteer": "*",
32+
"puppeteer-core": "*"
33+
},
34+
"peerDependenciesMeta": {
35+
"puppeteer": {
36+
"optional": true
37+
},
38+
"puppeteer-core": {
39+
"optional": true
40+
}
41+
},
42+
"devDependencies": {
43+
"@fetch-impl/fetcher": "workspace:^",
44+
"@types/debug": "^4.1.12",
45+
"@types/node": "^20.11.25",
46+
"tsup": "^8.0.2",
47+
"typedoc": "^0.25.12",
48+
"typescript": "^5.4.2"
49+
},
50+
"repository": {
51+
"type": "git",
52+
"url": "https://github.com/JacobLinCool/fetch-impl.git"
53+
},
54+
"bugs": {
55+
"url": "https://github.com/JacobLinCool/fetch-impl/issues"
56+
},
57+
"homepage": "https://jacoblincool.github.io/fetch-impl",
58+
"publishConfig": {
59+
"provenance": true
60+
}
61+
}

packages/puppeteer/src/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { fetch as f, fetcher } from "@fetch-impl/fetcher";
2+
import debug from "debug";
3+
import type { Browser } from "puppeteer";
4+
5+
export const log = debug("fetch:puppeteer");
6+
7+
/**
8+
* Configures the `fetch` implementation to use Puppeteer for making HTTP requests.
9+
*
10+
* @param _browser - The Puppeteer browser instance or a promise that resolves to a browser instance.
11+
* @param instance - The fetcher instance to configure.
12+
*/
13+
export function usePuppeteer(_browser: Browser | Promise<Browser>, instance = fetcher): void {
14+
let idx = 0;
15+
16+
instance.set(async (...args) => {
17+
const i = idx++;
18+
const url = args[0].toString();
19+
log("Fetching", i, url, args[1]);
20+
21+
const browser = await _browser;
22+
const page = await browser.newPage();
23+
await page.goto(url);
24+
25+
const res = await page.evaluate(async (args) => {
26+
/* c8 ignore next 11 */
27+
// v8 coverage is not working for code running in browser
28+
const res = await fetch(...args);
29+
const body = Array.from(await res.arrayBuffer().then((buf) => new Uint32Array(buf)));
30+
31+
return {
32+
body: body,
33+
status: res.status,
34+
statusText: res.statusText,
35+
// @ts-ignore headers is iterable in browser
36+
headers: Object.fromEntries(res.headers),
37+
};
38+
}, args);
39+
log("Fetched", i, res.status, res.statusText, res.headers, res.body.length, "bytes");
40+
const body = new Uint32Array(res.body).buffer;
41+
42+
page.close().catch(() => log("Failed to close page", i));
43+
return new Response(body, res);
44+
});
45+
}
46+
47+
export * from "@fetch-impl/fetcher";
48+
export default f;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import fetch, { Fetcher } from "@fetch-impl/fetcher";
2+
import puppeteer from "puppeteer";
3+
import { describe, expect, test } from "vitest";
4+
import { usePuppeteer } from "../src";
5+
6+
const TARGET_URL = "https://example.com/";
7+
const TIMEOUT = 30_000;
8+
9+
describe("puppeteer", () => {
10+
test("global fetcher", { timeout: TIMEOUT }, async () => {
11+
usePuppeteer(puppeteer.launch());
12+
const our = await fetch(TARGET_URL).then((res) => res.text());
13+
const glb = await globalThis.fetch(TARGET_URL).then((res) => res.text());
14+
expect(our).toBe(glb);
15+
});
16+
17+
test("local fetcher", { timeout: TIMEOUT }, async () => {
18+
const fetcher = new Fetcher();
19+
usePuppeteer(puppeteer.launch(), fetcher);
20+
const our = await fetcher.fetch(TARGET_URL).then((res) => res.text());
21+
const glb = await globalThis.fetch(TARGET_URL).then((res) => res.text());
22+
expect(our).toBe(glb);
23+
});
24+
});

packages/puppeteer/tsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"paths": {
5+
"@/*": ["./src/*"]
6+
}
7+
}
8+
}

packages/puppeteer/tsup.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig(() => ({
4+
entry: ["src/index.ts"],
5+
outDir: "dist",
6+
target: "node18",
7+
format: ["esm", "cjs"],
8+
shims: true,
9+
clean: true,
10+
splitting: false,
11+
dts: true,
12+
}));

packages/puppeteer/typedoc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"includeVersion": true,
3+
"entryPoints": ["src/index.ts"]
4+
}

0 commit comments

Comments
 (0)