ui5-tooling-modules
Version:
UI5 CLI extensions to load and convert node modules as UI5 AMD-like modules
125 lines (112 loc) • 4.54 kB
JavaScript
/**
* Rollup plugin: replace server-only fetch packages with a Web Fetch shim.
*
* Intercepts `node-fetch`, `cross-fetch`, `isomorphic-fetch` (and any of
* their subpath imports, e.g. `cross-fetch/polyfill`) and substitutes a tiny
* virtual module that re-exports the browser's native Web Fetch API.
*
* Why: those packages are designed for Node and drag in `node:net`,
* `node:fs/promises`, `fetch-blob`, `formdata-polyfill`, and more — none of
* which polyfill cleanly into a browser bundle. Modern browsers have a
* global `fetch` for years, and in the UI5 runtime `globalThis.fetch` is
* always available. Intercepting at `resolveId` short-circuits resolution
* before commonjs / polyfill-node ever see the import, so the entire
* Node-only sub-graph drops out of the bundle.
*
* The shim re-exports `fetch`, `Headers`, `Request`, `Response`, `Blob`,
* `File`, `FormData`, `AbortController`, and `AbortSignal` from
* `globalThis`, plus inert `AbortError` / `FetchError` / `isRedirect` stubs
* for compatibility. Node-only helpers (`blobFrom`, `fileFrom`, ...) are
* stubbed to throw an explanatory error if invoked.
*
* @param {object} [options]
* @param {string[]} [options.packages] override the default list of
* intercepted packages (default: `node-fetch`, `cross-fetch`,
* `isomorphic-fetch`)
* @param {{ verbose?: (msg: string) => void }} [options.log]
* optional logger; `verbose` is called for every interception
* @returns {import('rollup').Plugin}
*/
const VIRTUAL_PREFIX = "\0fetch-shim:";
// The list of packages we intercept. Subpath imports
// (`<pkg>` or `<pkg>/...`) all funnel into the same shim — the shim only
// re-exports the Web Fetch API, so the subpath is irrelevant to the result.
const SHIMMED_PACKAGES = ["node-fetch", "cross-fetch", "isomorphic-fetch"];
const SHIM_SOURCE = `// node-fetch / cross-fetch / isomorphic-fetch shim emitted by ui5-tooling-modules.
// Re-exports the browser's native Web Fetch API. See rollup-plugin-fetch-shim.js
// in ui5-tooling-modules for rationale.
const _fetch = globalThis.fetch ? globalThis.fetch.bind(globalThis) : undefined;
const _Headers = globalThis.Headers;
const _Request = globalThis.Request;
const _Response = globalThis.Response;
const _Blob = globalThis.Blob;
const _File = globalThis.File;
const _FormData = globalThis.FormData;
const _AbortController = globalThis.AbortController;
const _AbortSignal = globalThis.AbortSignal;
class AbortError extends Error {
constructor(message) {
super(message);
this.name = "AbortError";
this.type = "aborted";
}
}
class FetchError extends Error {
constructor(message, type, systemError) {
super(message);
this.name = "FetchError";
this.type = type;
if (systemError) {
this.code = this.errno = systemError.code;
this.erroredSysCall = systemError.syscall;
}
}
}
const _isRedirect = (code) => [301, 302, 303, 307, 308].includes(code);
// Node-only helpers — kept as stubs so static imports do not throw at parse
// time. They explicitly fail if invoked so the call site is easy to find.
const _nodeOnly = (name) => () => {
throw new Error("[ui5-tooling-modules] " + name + " is not available in the browser; use the native Web Fetch API instead.");
};
export default _fetch;
export {
_fetch as fetch,
_Headers as Headers,
_Request as Request,
_Response as Response,
_Blob as Blob,
_File as File,
_FormData as FormData,
_AbortController as AbortController,
_AbortSignal as AbortSignal,
AbortError,
FetchError,
_isRedirect as isRedirect,
};
export const blobFrom = _nodeOnly("blobFrom");
export const blobFromSync = _nodeOnly("blobFromSync");
export const fileFrom = _nodeOnly("fileFrom");
export const fileFromSync = _nodeOnly("fileFromSync");
`;
module.exports = function fetchShim({ packages = SHIMMED_PACKAGES, log } = {}) {
const shimRegex = new RegExp(`^(${packages.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:/.*)?$`);
return {
name: "fetch-shim",
resolveId(importee) {
if (shimRegex.test(importee)) {
log?.verbose?.(`fetch-shim: intercepted import "${importee}" -> globalThis.fetch shim`);
// Use a single virtual id so rollup deduplicates the shim across
// every (pkg, subpath) variant in the graph.
return { id: `${VIRTUAL_PREFIX}index.js`, moduleSideEffects: false };
}
return null;
},
load(id) {
if (id.startsWith(VIRTUAL_PREFIX)) {
return SHIM_SOURCE;
}
return null;
},
};
};
module.exports.SHIMMED_PACKAGES = SHIMMED_PACKAGES;