Skip to content

fix: make module import-safe in Node/SSR environments#35

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

fix: make module import-safe in Node/SSR environments#35
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-p7uh1x

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Problem

Importing the package in any non-browser context — Node, SSR, or build-time prerender — throws immediately at module load:

ReferenceError: document is not defined

(and HTMLElement is not defined once document is shimmed).

This directly contradicts what the package advertises:

  • README documents a "pure DOM-free calculator" (import { calculate } from '@hailbytes/vulnerability-calculator') and lists Next.js among supported frameworks.
  • index.d.ts documents calculate() as "Pure infrastructure-sizing calculation — no DOM required."

But the module evaluated three things unconditionally at the top level, all of which require a browser:

  1. class HailbytesVulnCalculator extends HTMLElementHTMLElement is undefined in Node/SSR
  2. const TMPL = document.createElement('template')document is undefined
  3. customElements.define(...)customElements is undefined

The existing test/smoke.test.mjs masked the bug because it installs global DOM shims before importing the module, which is not how Next.js SSR or a server-side calculate() call behaves.

Reproduction

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

Fix

Make the three browser-only operations defer/guard on environment availability. Browser behavior is unchanged; the element still registers and renders identically when a DOM is present.

  • Build the <template> lazily — only when a component is actually instantiated.
  • Fall back to a plain base class when HTMLElement is undefined, so the class declaration evaluates in Node/SSR.
  • Guard customElements.define() on browser availability, and skip it if the element is already defined (avoids double-registration errors).

Tests

Adds test/ssr-import.test.mjs, which imports the module in a plain Node process with no DOM globals (the Node test runner isolates each test file in its own process) and asserts both import-safety and that calculate() works server-side.

  • Verified the new test fails on the pre-fix code and passes after the fix.
  • Full suite: 69 passing (65 existing + 4 new), 0 failing, across Node 18/20/22 in CI.
$ node -e "import('./hailbytes-vuln-calculator.js').then(m => m.calculate({target_hosts:1000,scan_intensity:'medium',scan_frequency:'weekly',scan_window:8,scanning_tools:['hailbytes_asm'],compliance_needs:[]}))"
# import OK, calculate() OK, cpu_cores = 6

🤖 Generated with Claude Code


Generated by Claude Code

Importing the package in any non-browser context (Node, SSR, build-time
prerender) threw 'ReferenceError: document is not defined' because the
<template>, the HTMLElement base class, and customElements.define() were
all evaluated unconditionally at module load.

This contradicted the README/index.d.ts, which advertise a pure DOM-free
calculate() and list Next.js (SSR) as a supported environment. The existing
smoke test masked the bug by installing global DOM shims before importing.

- Build the <template> lazily (only when a component is instantiated)
- Fall back to a plain base class when HTMLElement is undefined
- Guard customElements.define() on browser availability + double-define

Browser behavior is unchanged. Adds test/ssr-import.test.mjs, which imports
the module with no DOM globals to lock in SSR/Node import safety.

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