CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-jsdom

A JavaScript implementation of many web standards

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

advanced.mddocs/guides/

Advanced Features and Limitations

Advanced configurations, edge cases, and important limitations of jsdom.

Important Limitations

jsdom has some notable limitations compared to real browsers:

No Navigation

Cannot change the global object by clicking links or setting location.href. Use reconfigure() to change URL or window.top, or create new JSDOM instances.

const { JSDOM } = require("jsdom");

const dom = new JSDOM();
console.log(dom.window.location.href); // "about:blank"

// Change the URL (doesn't navigate, just updates)
dom.reconfigure({ url: "https://example.com/" });
console.log(dom.window.location.href); // "https://example.com/"

// The DOM content remains unchanged
// No navigation occurs, just URL update

No Layout

Cannot calculate visual positioning. Methods like getBoundingClientRect() and properties like offsetTop return zeros. Use Object.defineProperty() to mock these if needed.

const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body><div id="box"></div></body>`);
const { document } = dom.window;

const box = document.getElementById("box");

// These return zeros
console.log(box.getBoundingClientRect()); // { x: 0, y: 0, width: 0, height: 0 }
console.log(box.offsetTop); // 0
console.log(box.offsetLeft); // 0

// Mock if needed
Object.defineProperty(box, "offsetTop", { value: 100 });
console.log(box.offsetTop); // 100

No Rendering

Not a visual browser. Use pretendToBeVisual: true option to make jsdom pretend it's rendering (affects document.hidden, enables requestAnimationFrame).

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
  pretendToBeVisual: true
});

console.log(dom.window.document.hidden); // false
console.log(dom.window.document.visibilityState); // "visible"

// Enables requestAnimationFrame
dom.window.requestAnimationFrame(() => {
  console.log("Animation frame");
});

Additional Features

Canvas Support

jsdom supports the HTML5 Canvas API when the optional canvas npm package (v3.x) is installed as a peer dependency. Without it, <canvas> elements behave like <div> elements.

// With canvas package installed
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body><canvas id="myCanvas"></canvas></body>`);
const { document } = dom.window;

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 100, 100); // Works if canvas package is installed

Encoding Sniffing

When providing binary data (Buffer, ArrayBuffer, TypedArray) instead of strings, jsdom automatically sniffs the encoding following browser behavior:

  • Detects UTF-8 and UTF-16 BOMs
  • Scans for <meta charset> tags
  • Uses contentType option's charset parameter
  • Defaults to UTF-8
const { JSDOM } = require("jsdom");

// With binary data
const buffer = Buffer.from('<!DOCTYPE html><p>Hello</p>');
const dom = new JSDOM(buffer); // Auto-detects encoding

Closing a jsdom

Terminate all timers and remove event listeners:

const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`);

// Clean up when done
dom.window.close(); // Terminates timers, removes event listeners

Advanced Configuration

Using beforeParse Hook

The beforeParse hook is called before HTML parsing begins, useful for adding shims or modifying the environment:

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
  beforeParse(window) {
    // Add custom APIs before parsing
    window.myCustomAPI = () => console.log("Custom API called");
    
    // Inject libraries
    window.jQuery = require("jquery");
  }
});

// Custom API is available
dom.window.myCustomAPI(); // "Custom API called"

Node Location Tracking

Enable location tracking for debugging and source mapping:

const { JSDOM } = require("jsdom");

const dom = new JSDOM(
  `<p>Hello
    <img src="foo.jpg">
  </p>`,
  { includeNodeLocations: true }
);

const pEl = dom.window.document.querySelector("p");
const location = dom.nodeLocation(pEl);

console.log(location);
// {
//   startOffset: 0,
//   endOffset: 39,
//   startLine: 1,
//   startCol: 1,
//   endLine: 3,
//   endCol: 4,
//   startTag: {...},
//   endTag: {...}
// }

Note: Cannot be used with XML content type.

Storage Quota

Configure maximum size for localStorage and sessionStorage:

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`<!DOCTYPE html><html></html>`, {
  url: "https://example.org/",
  storageQuota: 10000000 // 10 million code units (default: 5 million)
});

const { window } = dom;
window.localStorage.setItem("key", "value");

Using VM Context

Access the internal VM context for advanced script execution:

const { Script } = require("vm");
const { JSDOM } = require("jsdom");

const dom = new JSDOM(``, { runScripts: "outside-only" });

// Pre-compile a script
const script = new Script(`
  if (!this.ran) {
    this.ran = 0;
  }
  ++this.ran;
`);

const vmContext = dom.getInternalVMContext();

// Run the pre-compiled script multiple times
script.runInContext(vmContext);
script.runInContext(vmContext);
script.runInContext(vmContext);

console.assert(dom.window.ran === 3);

Working with the Window

Best Practices

const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`);

// GOOD: Access window directly
const { window } = dom;
const div = window.document.createElement("div");

// AVOID: Don't merge with Node.js global
// global.window = dom.window; // Don't do this!

// GOOD: Run code inside jsdom context
dom.window.eval('console.log(document.title);');

// GOOD: Use window constructors for type checks
console.log(div instanceof window.HTMLDivElement); // true
console.log(div instanceof HTMLDivElement); // false (different global)

Edge Cases

Fragment Limitations

DocumentFragments created with JSDOM.fragment() have limitations:

const { JSDOM } = require("jsdom");

const frag = JSDOM.fragment(`<p>Hello</p>`);

// Note: The fragment's ownerDocument has no browsing context
// - ownerDocument.defaultView is null
// - Resources won't load
// - No associated URL

fromURL() Behavior

Important notes about fromURL():

const { JSDOM } = require("jsdom");

JSDOM.fromURL("https://example.com/", {
  referrer: "https://google.com/"
}).then(dom => {
  // Follows redirects automatically
  // Cannot specify `url` or `contentType` options (determined from response)
  // `referrer` option becomes the HTTP Referer request header
  // Cookies from Set-Cookie headers are stored in the cookie jar
  // Cookies in the jar are sent as Cookie request headers
});

fromFile() Defaults

fromFile() has special default behaviors:

const { JSDOM } = require("jsdom");

// `url` defaults to a file URL (e.g., `file:///path/to/test.html`)
// `contentType` defaults to `"application/xhtml+xml"` for `.xht`, `.xhtml`, or `.xml` extensions
// `contentType` defaults to `"text/html"` for all other extensions
// Binary encoding is sniffed from file contents (BOMs, meta charset tags)

await JSDOM.fromFile("./page.html"); // Parsed as text/html
await JSDOM.fromFile("./page.xhtml"); // Parsed as application/xhtml+xml

Performance Considerations

Resource Loading

Resource loading is asynchronous. Scripts and stylesheets may not be loaded immediately:

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>
    </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

Memory Management

Remember to close jsdom instances when done:

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`<!DOCTYPE html><body></body>`);

// Use the dom...

// Clean up
dom.window.close(); // Important for long-running processes

docs

index.md

tile.json