Skip to content

fix: make module importable without a DOM (SSR/serverless/CLI)#33

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

fix: make module importable without a DOM (SSR/serverless/CLI)#33
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-rp7dfd

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

The package advertises a pure, DOM-free calculate() — but importing it in any non-browser environment throws before calculate() is ever reachable.

import { calculate } from '@hailbytes/vulnerability-calculator';
// ReferenceError: document is not defined

The module runs DOM-dependent code at import time:

  • const TMPL = document.createElement('template') (top level)
  • class HailbytesVulnCalculator extends HTMLElement (the extends clause is evaluated at module load, so an undefined HTMLElement throws immediately)
  • customElements.define('hailbytes-vuln-calculator', …) (top level)

This contradicts the project's own documentation:

  • README.md:32-34"Or import the pure DOM-free calculator: import { calculate } from '@hailbytes/vulnerability-calculator'"
  • index.d.ts:94"Pure infrastructure-sizing calculation — no DOM required."

The symptom is already visible in the repo: test/smoke.test.mjs and test/calculate.test.mjs both inject a hand-written fake DOM (globalThis.document, HTMLElement, customElements, …) just to import the module. Without that shim, the import fails — so anyone using the package for SSR, a serverless function, a CLI, or build tooling hits the error on the first line.

Reproduction (before this PR)

$ node -e "import('./hailbytes-vuln-calculator.js').then(m=>m.calculate)"
FAIL: ReferenceError document is not defined

Fix

Guard the browser-only code so the module imports cleanly with no DOM, while keeping custom-element behavior in real browsers byte-for-byte the same:

  • Build the <template> only when document exists.
  • Fall back to a plain base class (class {}) when HTMLElement is undefined, so the class declaration doesn't throw.
  • Register the custom element only when both customElements and document exist.
const TMPL = typeof document !== 'undefined' ? document.createElement('template') : null;
if (TMPL) TMPL.innerHTML = `…`;

const BaseHTMLElement = typeof HTMLElement !== 'undefined' ? HTMLElement : class {};
class HailbytesVulnCalculator extends BaseHTMLElement {  }

if (typeof customElements !== 'undefined' && typeof document !== 'undefined') {
  customElements.define('hailbytes-vuln-calculator', HailbytesVulnCalculator);
}

After

$ node -e "import('./hailbytes-vuln-calculator.js').then(m=>{const r=m.calculate({target_hosts:1000,scan_intensity:'medium',scan_frequency:'weekly',scan_window:8,scanning_tools:['hailbytes_asm'],compliance_needs:[]});console.log('OK',r.vm_resources.cpu_cores)})"
OK 6

Tests

Added test/pure-import.test.mjs, which imports the module with no DOM shim and asserts calculate() works. node --test runs each test file in its own process, so this file genuinely exercises the non-browser path (it asserts globalThis.document / HTMLElement are undefined before importing).

Full suite is green — 67 passing (65 existing + 2 new):

# tests 67
# pass 67
# fail 0

The existing tests keep their DOM shims and continue to exercise the browser path, so both paths are covered.

Impact

  • Makes the documented "pure DOM-free calculate()" import actually work — unblocks SSR / serverless / CLI / build-tool consumers.
  • No behavior change in the browser; the custom element registers exactly as before.
  • Scope: one source file, plus a CHANGELOG entry and a new regression test. No dependencies added (still zero-dependency).

🤖 Generated with Claude Code


Generated by Claude Code

The module ran DOM-dependent code at import time (document.createElement
for the template, customElements.define, and `extends HTMLElement`), so
`import { calculate } from '@hailbytes/vulnerability-calculator'` threw
`ReferenceError: document is not defined` in plain Node — despite the
README documenting a "pure DOM-free calculate()" import and index.d.ts
describing calculate() as "no DOM required". The test suite had to inject
a fake DOM just to import the module.

Guard the browser-only code so the module imports cleanly without a DOM,
while keeping custom-element registration in real browsers unchanged:
- build the <template> only when `document` exists
- fall back to a plain base class when `HTMLElement` is undefined
- register the element only when `customElements` and `document` exist

Add test/pure-import.test.mjs, which imports the module with no DOM shim
(node --test isolates each file in its own process) to lock in the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv4GpGxQq4AYEtbZVtJpch
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