Skip to content

Guard module-scope moveBefore feature-detect for DOM-less import#47

Open
codetalcott wants to merge 1 commit into
yippee-fun:mainfrom
codetalcott:node-ssr-import-safety
Open

Guard module-scope moveBefore feature-detect for DOM-less import#47
codetalcott wants to merge 1 commit into
yippee-fun:mainfrom
codetalcott:node-ssr-import-safety

Conversation

@codetalcott

Copy link
Copy Markdown

Problem

Importing morphlex in a DOM-less environment (bare Node / SSR) throws on import, before any morph function is called:

ReferenceError: Element is not defined

The cause is the module-scope feature-detect at the top of src/morphlex.ts:

const SUPPORTS_MOVE_BEFORE = "moveBefore" in Element.prototype

Element is dereferenced at module-evaluation time, so any environment without a DOM global (Node without a DOM shim, SSR/prerender, a bundler evaluating the module in Node) crashes on import. This is the only module-scope DOM access in the file — every other Element/Node/document reference is inside a function body or a type, so guarding this one line makes the whole module importable without a DOM.

Fix

const SUPPORTS_MOVE_BEFORE = typeof Element !== "undefined" && "moveBefore" in Element.prototype
  • In a browser: typeof Element === "function", so "moveBefore" in Element.prototype is evaluated exactly as before — behavior is identical.
  • Without a DOM: short-circuits to false, so moveBefore() falls back to insertBefore (moot in practice, since morphing needs a real DOM to run — this only makes the import safe).

This does not attempt to make morphing work headless; it only prevents the import-time crash so morphlex can be pulled into modules that are also evaluated in Node (the common SSR / bundler case).

Test

Added test/new/ssr-import.test.ts — stubs Element to undefined, resets the module registry, and asserts the module imports and still exposes morph / morphInner. It fails on main (ReferenceError: Element is not defined) and passes with this change, so it guards the regression. It also covers the new typeof Element !== "undefined" branch, which the happy-dom test env (where Element is always defined) otherwise wouldn't exercise.

Validation

# Fails before this change, passes after:
npx vitest run test/new/ssr-import.test.ts

# Existing node suite unchanged (DOM behavior identical):
#   test/morphlex*.test.ts — 148 passed

# End-to-end, against a built bundle with no DOM present:
node --input-type=module -e "await import('./dist/morphlex.min.js').then(m => console.log(typeof m.morph))"
#   before: ReferenceError: Element is not defined
#   after:  function

prettier --check clean on both files; tabs/no-semicolon style matches the surrounding code.

Context

Found while making a downstream library (which vendors morphlex for DOM morphing) importable in bare Node — we currently work around it with a guarded globalThis.Element shim ahead of the morphlex import. This one-liner removes the need for that shim for anyone importing morphlex in a mixed Node/browser codebase.

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.

1 participant