Skip to content

fix: make module importable without a DOM (SSR / pure calculate())#37

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-7ts1sb
Open

fix: make module importable without a DOM (SSR / pure calculate())#37
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-7ts1sb

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Problem

The README advertises the pure calculator:

// Or import the pure DOM-free calculator:
import { calculate } from '@hailbytes/vulnerability-calculator';
console.log(calculate({ /* inputs */ }).vm_resources);

and index.d.ts documents it as "Pure infrastructure-sizing calculation — no DOM required."

But the module crashes on import in any non-DOM environment — Node, SSR, Next.js / Vite server components, or a plain unit test — because of three top-level DOM accesses that run at module-evaluation time:

const TMPL = document.createElement('template');   // ReferenceError: document is not defined
class HailbytesVulnCalculator extends HTMLElement   // ReferenceError: HTMLElement is not defined
customElements.define('hailbytes-vuln-calculator', )

Reproduction (following the README verbatim):

$ node --input-type=module -e "import { calculate } from './hailbytes-vuln-calculator.js'"
ReferenceError: document is not defined
    at hailbytes-vuln-calculator.js:553:14

The existing test suite never caught this because every test file injects a global DOM shim before importing the module — something real consumers don't do.

Fix

Detect DOM support once (HAS_DOM) and guard all three top-level accesses:

  • Build the <template> lazily; store the HTML string separately so it costs nothing without a DOM.
  • Fall back to a plain base class when HTMLElement is absent (the element is only ever instantiated in a browser; only its class binding needs to exist for the exports).
  • Register the custom element only in the browser — and make it idempotent (customElements.get(...) check), which also prevents "already defined" errors under HMR / double import.

The pure calculate() export now works in Node with no shim, and the component still registers normally in the browser.

Tests

  • Added test/no-dom.test.mjs, which imports the module with no DOM shim and verifies calculate(), the default export, and the named class are all usable. This locks in the fix, since every other test file installs a shim and could not catch a regression.
  • Full suite: 68 tests passing (65 existing + 3 new).

🤖 Generated with Claude Code

https://claude.ai/code/session_01C5r4KaikJi9KNTp7PLvuAa


Generated by Claude Code

The README advertises `import { calculate }` as "the pure DOM-free
calculator" and index.d.ts documents it as "no DOM required", but the
module crashed on import in any non-DOM environment (Node, SSR, Next.js /
Vite server components, plain unit tests) because of three top-level DOM
accesses:

  - `document.createElement('template')`
  - `class ... extends HTMLElement`
  - `customElements.define(...)`

Existing tests only passed because they inject a global DOM shim before
importing, so real consumers following the README hit an immediate
ReferenceError.

Guard all three behind a HAS_DOM check: build the template lazily, fall
back to a plain base class when HTMLElement is absent, and register the
custom element only in the browser (now idempotent, avoiding
"already defined" errors under HMR / double import). The pure
`calculate()` export now works in Node with no shim.

Add test/no-dom.test.mjs, which imports the module with no DOM shim to
lock in the fix (every other test file installs a shim and could not
catch a regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5r4KaikJi9KNTp7PLvuAa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants