Skip to content

fix: make module importable in Node/SSR environments#42

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

fix: make module importable in Node/SSR environments#42
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-wk1efr

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Problem

The module touches browser-only globals at import time, so importing it anywhere without a DOM throws immediately:

ReferenceError: document is not defined
    at hailbytes-vuln-calculator.js:553   // const TMPL = document.createElement('template')

Three top-level statements run eagerly on import:

  • const TMPL = document.createElement('template') — needs document
  • class HailbytesVulnCalculator extends HTMLElement — needs HTMLElement at class-definition time
  • customElements.define(...) — needs customElements

This breaks two things the project actively advertises:

  1. The pure calculate() API. The README pitches import { calculate } as "the pure DOM-free calculator", but importing it in Node/SSR crashes before you can call it.
  2. Next.js support. The README lists Next.js as a supported host. Next.js server-renders by default, so import '@hailbytes/vulnerability-calculator' on the server crashes the render.

The existing test suite didn't catch this because both test files install a DOM shim (globalThis.document, globalThis.customElements, globalThis.HTMLElement) before importing the module — so they never exercise the real Node/SSR path.

Reproduction (before this PR)

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

Fix

Guard the browser-only work so the module parses and imports everywhere, while the custom element still registers in the browser:

  • Create the <template> only when document exists (null otherwise — it's only read when the element is instantiated, which never happens off-browser).
  • Subclass HTMLElement when present, else a plain base class, so the class definition parses in Node.
  • Call customElements.define(...) only when customElements exists.

No behavior change in the browser; the pure calculate() export now works in Node/SSR.

After this PR

$ node --input-type=module -e "import { calculate } from './hailbytes-vuln-calculator.js'; console.log(calculate({target_hosts:500,scan_intensity:'medium',scan_frequency:'weekly',scanning_tools:['openvas'],compliance_needs:[],scan_window:8}).vm_resources.cpu_cores)"
3

Tests

Added test/ssr.test.mjs, which imports the module in a fresh child process with no DOM shim and asserts calculate() works — a real regression guard for the SSR path that the shimmed tests can't provide.

Full suite: 66 passing (node --test test/*.test.mjs).

🤖 Generated with Claude Code

https://claude.ai/code/session_01V7jeAVU59dscFTawE34JgK


Generated by Claude Code

The module eagerly touched `document`, `HTMLElement`, and `customElements`
at import time, so `import { calculate }` — documented as the pure DOM-free
calculator and advertised as Next.js-compatible — threw
`ReferenceError: document is not defined` in any Node/SSR context. Only the
tests survived, because they install a DOM shim before importing.

Guard the browser-only work: create the <template> only when `document`
exists, fall back to a plain base class when `HTMLElement` is absent, and
register the custom element only when `customElements` exists. The pure
`calculate()` export now works off the browser while the component still
registers in it.

Add test/ssr.test.mjs, which imports the module in a fresh DOM-free child
process and asserts calculate() works.

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