fix: make module importable in DOM-free runtimes (SSR)#49
Open
dmchaledev wants to merge 1 commit into
Open
Conversation
The README advertises `import { calculate }` as a "pure DOM-free
calculator", but importing the module in any non-DOM runtime (Node SSR,
Next.js server components, Nuxt, Astro) crashed at load time because
three operations ran at module top level and assumed a browser:
1. `class ... extends HTMLElement` — HTMLElement is undefined off-DOM
2. `document.createElement('template')` for the component markup
3. `customElements.define(...)`
Guard all three so evaluating the module never touches DOM globals
unless they exist:
- fall back to a stub base class when HTMLElement is absent
- keep the template markup as a string and build the <template>
lazily on first component construction (browser only)
- register the element only in a browser, and only once (also fixes a
double-registration throw if the module loads twice)
The pure `calculate()` export now works in a plain Node process with no
DOM shim. Adds test/ssr.test.mjs, which imports the module with no DOM
globals defined and exercises calculate() end to end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xv8XRbA9DRzE1t8RtJpNAa
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The README advertises the pure
calculate()export as a "pure DOM-free calculator":But importing the module in any non-DOM runtime — Node SSR, Next.js server components, Nuxt, Astro — crashes at load time. Three operations run at module top level and assume a browser:
class HailbytesVulnCalculator extends HTMLElement—HTMLElementisundefinedoff-DOM, soextends undefinedthrows aTypeErrordocument.createElement('template')— builds the component markup eagerlycustomElements.define(...)— registers the custom element eagerlyReproduction (before this change), in a plain Node process:
The existing tests never caught this because
smoke.test.mjsinstalls a full DOM shim before importing. The documented server-side use case was silently broken.Fix
Guard all three so evaluating the module never touches DOM globals unless they exist:
class {}whenHTMLElementis absent. The stub is never instantiated off-DOM because registration is guarded.TEMPLATE_HTML) and build the<template>lazily on first component construction (browser only).customElements.defineonly whencustomElementsexists and the tag isn't already registered. The!customElements.get(...)guard also fixes a double-registrationthrowif the module is loaded twice.No behavior change in the browser — the component builds and registers exactly as before, just lazily. The only observable difference is that
import { calculate }now works in a plain Node process, as documented.Tests
test/ssr.test.mjs(2 tests): imports the module in a process with nodocument/HTMLElement/customElementsdefined, and exercisescalculate()end to end.node:testruns each file in its own process, so no other test's DOM shim leaks in. This test fails against the old code and passes with the fix.🤖 Generated with Claude Code
Generated by Claude Code