The ResourceLoader class handles loading of external resources including scripts, stylesheets, images, and iframes. It can be customized via subclassing for advanced use cases like proxies, custom headers, or response modification.
By default, jsdom does not load any external resources. Enable resource loading with the resources option.
Usage Examples:
const { JSDOM } = require("jsdom");
// Default: No resources loaded
const dom1 = new JSDOM(`
<script src="https://example.com/script.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">
`);
// script.js and style.css will NOT be loaded
// Enable usable resources
const dom2 = new JSDOM(`
<script src="https://example.com/script.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">
`, {
resources: "usable",
runScripts: "dangerously" // Required for external scripts
});
// script.js and style.css WILL be loadedWhen resources: "usable" is set, jsdom loads the following resource types:
<frame> and <iframe> elements<link rel="stylesheet"> elements<script src=""> elements (requires runScripts: "dangerously")<img> elements (requires optional canvas npm package)Important: Set a valid url option to resolve relative URLs correctly. The default URL is "about:blank", which causes relative URLs to fail.
Usage Example:
const { JSDOM } = require("jsdom");
// BAD: Relative URLs will fail with default "about:blank"
const dom1 = new JSDOM(`
<script src="/script.js"></script>
`, {
resources: "usable",
runScripts: "dangerously"
});
// Fails: Cannot parse "/script.js" against "about:blank"
// GOOD: Provide base URL for relative URLs
const dom2 = new JSDOM(`
<script src="/script.js"></script>
`, {
url: "https://example.org/",
resources: "usable",
runScripts: "dangerously"
});
// Success: Loads "https://example.org/script.js"
// Or use fromURL() which sets URL automatically
JSDOM.fromURL("https://example.org/page.html", {
resources: "usable",
runScripts: "dangerously"
}).then(dom => {
// All resources are loaded with correct base URL
});Creates a custom resource loader with configuration for proxies, SSL, and user agent.
/**
* Creates a resource loader
* @param options - Configuration options
*/
constructor(options?: ResourceLoaderOptions);
interface ResourceLoaderOptions {
/**
* HTTP proxy address
* @example "http://127.0.0.1:9001"
*/
proxy?: string;
/**
* Whether to require valid SSL certificates
* @default true
*/
strictSSL?: boolean;
/**
* User-Agent header for requests
* Also sets navigator.userAgent
* @default "Mozilla/5.0 (${process.platform}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${version}"
*/
userAgent?: string;
}Usage Example:
const { JSDOM, ResourceLoader } = require("jsdom");
// Basic configuration
const resourceLoader = new ResourceLoader({
proxy: "http://127.0.0.1:9001",
strictSSL: false,
userAgent: "MyApp/1.0"
});
const dom = new JSDOM(`
<html>
<head>
<script src="https://example.com/script.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">
</head>
</html>
`, {
url: "https://example.org/",
resources: resourceLoader,
runScripts: "dangerously"
});
// All requests will:
// - Use the proxy
// - Accept invalid SSL certificates
// - Send "User-Agent: MyApp/1.0" header
console.log(dom.window.navigator.userAgent); // "MyApp/1.0"Subclass ResourceLoader and override the fetch() method for complete control over resource loading.
/**
* Fetch a resource
* Override this method in subclasses for custom behavior
* @param url - URL to fetch
* @param options - Request options
* @returns Promise resolving to Buffer of content, or null to skip loading.
* For HTTP/HTTPS requests, the Promise has additional properties:
* - response: HTTP response object (available after 'response' event)
* - href: Final URL after redirects (available after request completes)
* - abort(): Method to cancel the request
* - getHeader(name): Method to retrieve request header values
*/
fetch(url: string, options: FetchOptions): FetchPromise;
interface FetchOptions {
/**
* DOM element requesting the resource (if applicable)
*/
element?: Element;
/**
* Accept header value
*/
accept?: string;
/**
* Cookie jar for sending cookies
*/
cookieJar?: CookieJar;
/**
* Referrer URL
*/
referrer?: string;
}
interface FetchPromise extends Promise<Buffer | null> {
/**
* HTTP response object (only for HTTP/HTTPS requests)
* Set during the 'response' event
*/
response?: {
statusCode: number;
headers: Record<string, string>;
};
/**
* Final URL after following redirects (only for HTTP/HTTPS requests)
* Available after request completes
*/
href?: string;
/**
* Abort the fetch request (all request types)
*/
abort(): void;
/**
* Get a request header value (only for HTTP/HTTPS requests)
* @param name - Header name
* @returns Header value
*/
getHeader?(name: string): string | undefined;
}Usage Example:
const { JSDOM, ResourceLoader } = require("jsdom");
// Override specific URL
class CustomResourceLoader extends ResourceLoader {
fetch(url, options) {
// Override a specific script's contents
if (url === "https://example.com/config.js") {
return Promise.resolve(Buffer.from(`
window.CONFIG = { apiUrl: "https://test-api.example.com" };
`));
}
// Block certain resources
if (url.includes("analytics")) {
return Promise.resolve(null); // Return null to skip loading
}
// Use default behavior for everything else
return super.fetch(url, options);
}
}
const dom = new JSDOM(`
<html>
<head>
<script src="https://example.com/config.js"></script>
<script src="https://analytics.example.com/tracker.js"></script>
<script src="https://example.com/app.js"></script>
</head>
</html>
`, {
url: "https://example.org/",
resources: new CustomResourceLoader(),
runScripts: "dangerously"
});
// config.js loads with custom content
// tracker.js is blocked
// app.js loads normallyUse the element option to identify which element is requesting a resource.
Usage Example:
const { JSDOM, ResourceLoader } = require("jsdom");
class LoggingResourceLoader extends ResourceLoader {
fetch(url, options) {
if (options.element) {
console.log(`Element ${options.element.localName} is requesting ${url}`);
console.log(`Element attributes:`, {
id: options.element.id,
className: options.element.className,
src: options.element.src,
href: options.element.href
});
} else {
console.log(`Requesting ${url} (no element)`);
}
return super.fetch(url, options);
}
}
const dom = new JSDOM(`
<html>
<head>
<link rel="stylesheet" href="https://example.com/style.css">
<script src="https://example.com/script.js"></script>
</head>
<body>
<img id="logo" src="https://example.com/logo.png">
<iframe src="https://example.com/widget.html"></iframe>
</body>
</html>
`, {
url: "https://example.org/",
resources: new LoggingResourceLoader(),
runScripts: "dangerously"
});
// Output:
// Element link is requesting https://example.com/style.css
// Element script is requesting https://example.com/script.js
// Element img is requesting https://example.com/logo.png
// Element iframe is requesting https://example.com/widget.htmlIntercept and modify resource contents before they're used.
Usage Example:
const { JSDOM, ResourceLoader } = require("jsdom");
class ModifyingResourceLoader extends ResourceLoader {
async fetch(url, options) {
const buffer = await super.fetch(url, options);
if (!buffer) return null;
// Modify JavaScript files
if (url.endsWith(".js")) {
let content = buffer.toString("utf-8");
// Add instrumentation
content = `console.log("Loading ${url}");\n` + content;
// Replace API endpoints
content = content.replace(
/https:\/\/api\.example\.com/g,
"https://test-api.example.com"
);
return Buffer.from(content, "utf-8");
}
// Modify CSS files
if (url.endsWith(".css")) {
let content = buffer.toString("utf-8");
// Add custom CSS
content = `/* Custom styles */\n` + content;
return Buffer.from(content, "utf-8");
}
return buffer;
}
}
const dom = new JSDOM(html, {
url: "https://example.org/",
resources: new ModifyingResourceLoader(),
runScripts: "dangerously"
});Implement a cache to avoid re-fetching resources.
Usage Example:
const { JSDOM, ResourceLoader } = require("jsdom");
class CachingResourceLoader extends ResourceLoader {
constructor(options) {
super(options);
this.cache = new Map();
}
async fetch(url, options) {
// Check cache
if (this.cache.has(url)) {
console.log(`Cache hit: ${url}`);
return this.cache.get(url);
}
console.log(`Cache miss: ${url}`);
// Fetch and cache
const buffer = await super.fetch(url, options);
if (buffer) {
this.cache.set(url, buffer);
}
return buffer;
}
clearCache() {
this.cache.clear();
}
}
const loader = new CachingResourceLoader();
// First page - fetches resources
const dom1 = await JSDOM.fromURL("https://example.com/", {
resources: loader,
runScripts: "dangerously"
});
// Second page - uses cached resources (if URLs match)
const dom2 = await JSDOM.fromURL("https://example.com/page2", {
resources: loader,
runScripts: "dangerously"
});
// Clear cache when done
loader.clearCache();Note that resource loading is asynchronous. Scripts and stylesheets may not be loaded immediately.
Usage Example:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`
<html>
<head>
<script src="https://example.com/slow-loading.js"></script>
</head>
<body>
<div id="content"></div>
<script>
// This inline script runs immediately
// But slow-loading.js may not have loaded yet
console.log("Inline script executed");
</script>
</body>
</html>
`, {
url: "https://example.org/",
resources: "usable",
runScripts: "dangerously"
});
// The page structure is available immediately
console.log(dom.window.document.getElementById("content")); // <div>
// But external scripts may still be loading
// There's no built-in way to know when all resources finish loading
// Workaround: Use a callback in the page
const domWithCallback = new JSDOM(`
<html>
<head>
<script src="https://example.com/lib.js"></script>
</head>
<body>
<script>
// Signal when ready
if (typeof window.onLibraryLoaded === 'function') {
window.onLibraryLoaded();
}
</script>
</body>
</html>
`, {
url: "https://example.org/",
resources: "usable",
runScripts: "dangerously"
});
// Set up callback before or during creation
domWithCallback.window.onLibraryLoaded = () => {
console.log("Library is loaded!");
};Resource loading errors are reported via the virtual console's jsdomError event.
Usage Example:
const { JSDOM, VirtualConsole } = require("jsdom");
const virtualConsole = new VirtualConsole();
virtualConsole.on("jsdomError", (error) => {
if (error.type === "resource-loading") {
console.error(`Failed to load: ${error.url}`);
console.error(`Error: ${error.cause?.message}`);
}
});
const dom = new JSDOM(`
<html>
<head>
<script src="https://nonexistent.example.com/script.js"></script>
<link rel="stylesheet" href="https://example.com/missing.css">
</head>
</html>
`, {
url: "https://example.org/",
resources: "usable",
runScripts: "dangerously",
virtualConsole
});
// Will emit jsdomError events for failed resourcesFor resources you need synchronously, provide them via custom ResourceLoader or beforeParse hook.
Usage Example:
const { JSDOM, ResourceLoader } = require("jsdom");
const fs = require("fs");
class LocalResourceLoader extends ResourceLoader {
fetch(url, options) {
// Serve local files for specific URLs
if (url === "https://example.com/jquery.js") {
const content = fs.readFileSync("./node_modules/jquery/dist/jquery.js");
return Promise.resolve(content);
}
return super.fetch(url, options);
}
}
const dom = new JSDOM(`
<html>
<head>
<script src="https://example.com/jquery.js"></script>
</head>
</html>
`, {
url: "https://example.org/",
resources: new LocalResourceLoader(),
runScripts: "dangerously"
});
// Or inject directly in beforeParse
const dom2 = new JSDOM(`
<html>
<body>
<script>
// jQuery is already available
$('body').append('<p>Hello</p>');
</script>
</body>
</html>
`, {
runScripts: "dangerously",
beforeParse(window) {
// Inject library before parsing
const jqueryCode = fs.readFileSync("./node_modules/jquery/dist/jquery.js", "utf-8");
window.eval(jqueryCode);
}
});Cookies from the cookie jar are automatically sent with resource requests.
Usage Example:
const { JSDOM, CookieJar } = require("jsdom");
const cookieJar = new CookieJar();
cookieJar.setCookieSync("auth=token123", "https://example.com/");
const dom = new JSDOM(`
<html>
<head>
<script src="https://example.com/authenticated-api.js"></script>
</head>
</html>
`, {
url: "https://example.com/",
resources: "usable",
runScripts: "dangerously",
cookieJar
});
// The request for authenticated-api.js will include:
// Cookie: auth=token123