Skip to content

Commit 138f648

Browse files
authored
feat: make the lib isomorphic (#280)
1 parent 135bf4f commit 138f648

File tree

9 files changed

+246
-85
lines changed

9 files changed

+246
-85
lines changed

.eslintrc.yml

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ extends: "@jsdevtools"
77
env:
88
node: true
99
browser: true
10+
rules:
11+
"@typescript-eslint/no-explicit-any": ["off"]

.github/workflows/CI-CD.yaml

+12-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ name: CI-CD
88

99
on:
1010
push:
11+
branches:
12+
- main
1113
pull_request:
1214
schedule:
1315
- cron: "0 0 1 * *"
@@ -25,16 +27,16 @@ jobs:
2527
- macos-latest
2628
- windows-latest
2729
node:
28-
- 10
29-
- 12
3030
- 14
31+
- lts/*
32+
- current
3133

3234
steps:
3335
- name: Checkout source
34-
uses: actions/checkout@v2
36+
uses: actions/checkout@v3
3537

3638
- name: Install Node ${{ matrix.node }}
37-
uses: actions/setup-node@v1
39+
uses: actions/setup-node@v3
3840
with:
3941
node-version: ${{ matrix.node }}
4042

@@ -69,12 +71,12 @@ jobs:
6971

7072
steps:
7173
- name: Checkout source
72-
uses: actions/checkout@v2
74+
uses: actions/checkout@v3
7375

7476
- name: Install Node
75-
uses: actions/setup-node@v1
77+
uses: actions/setup-node@v3
7678
with:
77-
node-version: 12
79+
node-version: lts/*
7880

7981
- name: Install dependencies
8082
run: npm ci
@@ -120,10 +122,10 @@ jobs:
120122
- node_tests
121123
- browser_tests
122124
steps:
123-
- uses: actions/checkout@v2
124-
- uses: actions/setup-node@v1
125+
- uses: actions/checkout@v3
126+
- uses: actions/setup-node@v3
125127
with:
126-
node-version: 12
128+
node-version: lts/*
127129

128130
- name: Install dependencies
129131
run: npm ci

.mocharc.yml

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ spec: test/specs/**/*.spec.js
77
bail: true
88
recursive: true
99
async-only: true
10+
require: ./test/fixtures/polyfill.js

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ When using a transpiler such as [Babel](https://babeljs.io/) or [TypeScript](htt
111111
import $RefParser from "@apidevtools/json-schema-ref-parser";
112112
```
113113

114+
If you are using Node.js < 18, you'll need a polyfill for `fetch`, like [node-fetch](https://github.com/node-fetch/node-fetch):
115+
```javascript
116+
import fetch from "node-fetch";
117+
118+
globalThis.fetch = fetch;
119+
```
120+
114121

115122

116123
Browser support

lib/resolvers/http.js

+45-68
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"use strict";
22

3-
const http = require("http");
4-
const https = require("https");
53
const { ono } = require("@jsdevtools/ono");
64
const url = require("../util/url");
75
const { ResolverError } = require("../util/errors");
@@ -70,12 +68,12 @@ module.exports = {
7068
* @param {object} file - An object containing information about the referenced file
7169
* @param {string} file.url - The full URL of the referenced file
7270
* @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
73-
* @returns {Promise<Buffer>}
71+
* @returns {Promise<string>}
7472
*/
7573
read (file) {
7674
let u = url.parse(file.url);
7775

78-
if (process.browser && !u.protocol) {
76+
if (typeof window !== "undefined" && !u.protocol) {
7977
// Use the protocol of the current page
8078
u.protocol = url.parse(location.href).protocol;
8179
}
@@ -91,42 +89,40 @@ module.exports = {
9189
* @param {object} httpOptions - The `options.resolve.http` object
9290
* @param {number} [redirects] - The redirect URLs that have already been followed
9391
*
94-
* @returns {Promise<Buffer>}
92+
* @returns {Promise<string>}
9593
* The promise resolves with the raw downloaded data, or rejects if there is an HTTP error.
9694
*/
9795
function download (u, httpOptions, redirects) {
98-
return new Promise(((resolve, reject) => {
99-
u = url.parse(u);
100-
redirects = redirects || [];
101-
redirects.push(u.href);
102-
103-
get(u, httpOptions)
104-
.then((res) => {
105-
if (res.statusCode >= 400) {
106-
throw ono({ status: res.statusCode }, `HTTP ERROR ${res.statusCode}`);
96+
u = url.parse(u);
97+
redirects = redirects || [];
98+
redirects.push(u.href);
99+
100+
return get(u, httpOptions)
101+
.then((res) => {
102+
if (res.statusCode >= 400) {
103+
throw ono({ status: res.statusCode }, `HTTP ERROR ${res.statusCode}`);
104+
}
105+
else if (res.statusCode >= 300) {
106+
if (redirects.length > httpOptions.redirects) {
107+
throw new ResolverError(ono({ status: res.statusCode },
108+
`Error downloading ${redirects[0]}. \nToo many redirects: \n ${redirects.join(" \n ")}`));
107109
}
108-
else if (res.statusCode >= 300) {
109-
if (redirects.length > httpOptions.redirects) {
110-
reject(new ResolverError(ono({ status: res.statusCode },
111-
`Error downloading ${redirects[0]}. \nToo many redirects: \n ${redirects.join(" \n ")}`)));
112-
}
113-
else if (!res.headers.location) {
114-
throw ono({ status: res.statusCode }, `HTTP ${res.statusCode} redirect with no location header`);
115-
}
116-
else {
117-
// console.log('HTTP %d redirect %s -> %s', res.statusCode, u.href, res.headers.location);
118-
let redirectTo = url.resolve(u, res.headers.location);
119-
download(redirectTo, httpOptions, redirects).then(resolve, reject);
120-
}
110+
else if (!res.headers.location) {
111+
throw ono({ status: res.statusCode }, `HTTP ${res.statusCode} redirect with no location header`);
121112
}
122113
else {
123-
resolve(res.body || Buffer.alloc(0));
114+
// console.log('HTTP %d redirect %s -> %s', res.statusCode, u.href, res.headers.location);
115+
let redirectTo = url.resolve(u, res.headers.location);
116+
return download(redirectTo, httpOptions, redirects);
124117
}
125-
})
126-
.catch((err) => {
127-
reject(new ResolverError(ono(err, `Error downloading ${u.href}`), u.href));
128-
});
129-
}));
118+
}
119+
else {
120+
return res.text();
121+
}
122+
})
123+
.catch((err) => {
124+
throw new ResolverError(ono(err, `Error downloading ${u.href}`), u.href);
125+
});
130126
}
131127

132128
/**
@@ -139,42 +135,23 @@ function download (u, httpOptions, redirects) {
139135
* The promise resolves with the HTTP Response object.
140136
*/
141137
function get (u, httpOptions) {
142-
return new Promise(((resolve, reject) => {
143-
// console.log('GET', u.href);
144-
145-
let protocol = u.protocol === "https:" ? https : http;
146-
let req = protocol.get({
147-
hostname: u.hostname,
148-
port: u.port,
149-
path: u.path,
150-
auth: u.auth,
151-
protocol: u.protocol,
152-
headers: httpOptions.headers || {},
153-
withCredentials: httpOptions.withCredentials
154-
});
138+
let controller;
139+
let timeoutId;
140+
if (httpOptions.timeout) {
141+
controller = new AbortController();
142+
timeoutId = setTimeout(() => controller.abort(), httpOptions.timeout);
143+
}
155144

156-
if (typeof req.setTimeout === "function") {
157-
req.setTimeout(httpOptions.timeout);
145+
return fetch(u, {
146+
method: "GET",
147+
headers: httpOptions.headers || {},
148+
credentials: httpOptions.withCredentials ? "include" : "same-origin",
149+
signal: controller ? controller.signal : null,
150+
}).then(response => {
151+
if (timeoutId) {
152+
clearTimeout(timeoutId);
158153
}
159154

160-
req.on("timeout", () => {
161-
req.abort();
162-
});
163-
164-
req.on("error", reject);
165-
166-
req.once("response", (res) => {
167-
res.body = Buffer.alloc(0);
168-
169-
res.on("data", (data) => {
170-
res.body = Buffer.concat([res.body, Buffer.from(data)]);
171-
});
172-
173-
res.on("error", reject);
174-
175-
res.on("end", () => {
176-
resolve(res);
177-
});
178-
});
179-
}));
155+
return response;
156+
});
180157
}

lib/util/url.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22

3-
let isWindows = /^win/.test(process.platform),
3+
let isWindows = /^win/.test(globalThis.process?.platform),
44
forwardSlashPattern = /\//g,
55
protocolPattern = /^(\w{2,}):\/\//i,
66
url = module.exports,
@@ -22,7 +22,7 @@ let urlDecodePatterns = [
2222
/\%40/g, "@"
2323
];
2424

25-
exports.parse = require("url").parse;
25+
exports.parse = (u) => new URL(u);
2626

2727
/**
2828
* Returns resolved target URL relative to a base URL in a manner similar to that of a Web browser resolving an anchor tag HREF.
@@ -45,7 +45,7 @@ exports.resolve = function resolve (from, to) {
4545
* @returns {string}
4646
*/
4747
exports.cwd = function cwd () {
48-
if (process.browser) {
48+
if (typeof window !== "undefined") {
4949
return location.href;
5050
}
5151

@@ -144,7 +144,7 @@ exports.isHttp = function isHttp (path) {
144144
}
145145
else if (protocol === undefined) {
146146
// There is no protocol. If we're running in a browser, then assume it's HTTP.
147-
return process.browser;
147+
return typeof window !== "undefined";
148148
}
149149
else {
150150
// It's some other protocol, such as "ftp://", "mongodb://", etc.

0 commit comments

Comments
 (0)