Skip to content

[safe-html-react-parser] Replace isomorphic-dompurify with custom implementation supporting flexible DOM libraries#203

Merged
yujeong-jeon merged 6 commits intomainfrom
feature/202
Nov 20, 2025
Merged

[safe-html-react-parser] Replace isomorphic-dompurify with custom implementation supporting flexible DOM libraries#203
yujeong-jeon merged 6 commits intomainfrom
feature/202

Conversation

@yujeong-jeon
Copy link
Contributor

Related Issue

#202

Summary

Replace isomorphic-dompurify dependency with a custom DOMPurify implementation that supports user-chosen DOM libraries (jsdom, happy-dom, or linkedom) for better flexibility and performance optimization.

Motivation

  • isomorphic-dompurify has limited flexibility and no built-in memory management
  • Memory leaks can occur with heavy JSDOM usage in server-side rendering

Changes

  • New file: src/utils/dompurify.ts with OptimizedDOMPurify class
    • Implements LRU cache (default 100 entries) for performance
    • Periodic DOM instance recreation (default every 1000 calls) to prevent memory leaks
    • Configurable cache size and recreation interval
  • Flexible DOM support: Users can choose between:
    • jsdom - most complete, heavier (for maximum compatibility)
    • happy-dom - fast, lighter, recommended for balanced performance
    • linkedom - fastest, minimal footprint (for performance-critical apps)
  • Package dependencies:
    • Moved jsdom to optional peerDependencies
    • Added happy-dom and linkedom as optional peerDependencies
    • Kept dompurify and html-react-parser in main dependencies
  • API improvements:
    • Client-side: Uses browser's native window (no configuration needed)
    • Server-side: Requires explicit configuration via configureDOMPurify() or per-call options
    • Added domPurifyOptions to SafeParseOptions for per-call configuration
    • Global configuration via configureDOMPurify() function
  • Documentation: Updated README with configuration examples and performance comparison table

Migration Guide

For server-side rendering, install one of the DOM implementations:

# Recommended: happy-dom (best balance)
npm install happy-dom

# Or jsdom (maximum compatibility)
npm install jsdom

# Or linkedom (maximum performance)
npm install linkedom

Then configure at app initialization:

import { configureDOMPurify } from '@naverpay/safe-html-react-parser'
import { Window } from 'happy-dom'

configureDOMPurify({
  domWindowFactory: () => new Window()
})

Or use per-call options:

safeParse(html, {
  domPurifyOptions: {
    domWindowFactory: () => new Window()
  }
})

Performance Benefits

  • Caching: Reduces redundant sanitization operations
  • Memory management: Prevents memory leaks from long-running servers
  • User choice: Select DOM implementation based on project requirements
  • Smaller bundles: Only include the DOM library you actually use

… wrapper

Replace isomorphic-dompurify dependency with a custom implementation
using dompurify + jsdom directly for better control and optimization.

Changes:
- Add src/utils/dompurify.ts with OptimizedDOMPurify class
- Implement LRU cache (default 100 entries) for sanitized HTML
- Add periodic JSDOM instance recreation (default every 1000 calls)
- Update dependencies: isomorphic-dompurify → dompurify + jsdom

Benefits:
- Better memory management with configurable cache and recreation interval
- Improved performance through result caching
- More control over server-side sanitization behavior
@npayfebot
Copy link
Collaborator

npayfebot commented Nov 18, 2025

✅ Changeset detected

Latest commit: 3a166d5

@naverpay/safe-html-react-parser package have detected changes.

If no version change is needed, please add skip-detect-change to the label.

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@naverpay/safe-html-react-parser ✨ Minor
powered by: naverpay changeset detect-add actions

@npayfebot
Copy link
Collaborator

npayfebot commented Nov 18, 2025

NPM Packages

📦 @naverpay/safe-html-react-parser

Total Sizes: 4.57 kB

Total Changes: +2.99 kB (+190%) 🔍 (Size Increased)

File Status Previous Size Updated Size Changed
/dist/esm/index.mjs 🛠️ 667 B 712 B +45 B (+7%)
/dist/cjs/index.js 🛠️ 909 B 947 B +38 B (+4%)
/dist/esm/utils/lru-cache.mjs - 303 B +303 B
/dist/esm/utils/dompurify.mjs - 1.09 kB +1.09 kB
/dist/cjs/utils/lru-cache.js - 366 B +366 B
/dist/cjs/utils/dompurify.js - 1.15 kB +1.15 kB

🧩 Dependency Changes

Package Status Previous Version Updated Version Bundle Size (Max) Changed
dompurify@^3.3.0 - ^3.3.0 830 kB 830 kB
isomorphic-dompurify@^2.30.1 ^2.30.1 - -5.71 kB -5.71 kB

powered by: naverpay size-action

Replace isomorphic-dompurify with a custom implementation that supports flexible DOM libraries.
@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@npayfebot
Copy link
Collaborator

No changed files exist under the . path, no packages have been deployed.

@yujeong-jeon
Copy link
Contributor Author

?

@yceffort-naver
Copy link
Contributor

?

I’m sorry, this is my fault. Please take a look at this: #204 😓

@yceffort-naver
Copy link
Contributor

/canary-publish

@npayfebot
Copy link
Collaborator

Published Canary Packages

@naverpay/safe-html-react-parser@1.1.0-canary.251118-3eea5d1

@yceffort-naver
Copy link
Contributor

image

@yujeong-jeon
Copy link
Contributor Author

Oh, I saw this issue late. Thank you!!

@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@npayfebot
Copy link
Collaborator

Published Canary Packages

@naverpay/safe-html-react-parser@1.1.0-canary.251119-26c9e51

@yujeong-jeon yujeong-jeon self-assigned this Nov 19, 2025
@yujeong-jeon yujeong-jeon marked this pull request as ready for review November 19, 2025 07:23
@yujeong-jeon yujeong-jeon requested a review from a team as a code owner November 19, 2025 07:23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about serializing the Node to use it as a key? It seems like if there are different objects with the same content, they would be referenced by different keys. If that’s what you intended, please disregard this message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 76d76a3

Comment on lines +160 to +162
if (global.gc && typeof global.gc === 'function') {
global.gc()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re only planning to use it when the –expose-gc option is available, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. The use of global.gc() is only intended for server-side environments where the --expose-gc option is available. This is mainly to support scenarios where the package is used on the server, especially since it's open source and may be integrated in various environments.

Comment on lines +217 to +222
function getSanitizer(options?: SanitizerOptions) {
if (!instance) {
instance = new OptimizedDOMPurify(options)
}
return instance
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a singleton object, it seems you won’t be able to use different settings within a single application.

Copy link
Contributor Author

@yujeong-jeon yujeong-jeon Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. The current implementation uses a singleton, so only one global configuration is supported per process.

This design was chosen to minimize memory usage and complexity (as written in the description), especially for server-side scenarios. If there’s a need to support multiple configurations at the same time, I'm open to considering multi-instance support in the future.

Copy link
Contributor

@yceffort-naver yceffort-naver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GOGOGOGO

@yujeong-jeon
Copy link
Contributor Author

Thanks for the great review! Merging 🚀

@yujeong-jeon yujeong-jeon merged commit bbc554a into main Nov 20, 2025
7 checks passed
@yujeong-jeon yujeong-jeon deleted the feature/202 branch November 20, 2025 06:13
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.

3 participants

Comments