Engineering Journal
Table Formatter
Table Formatter

The AMD Loader Conflict That Silently Broke DOMPurify

2026-06-04

TLDR

Loading a UMD library (like DOMPurify) after a strict AMD module loader (like Monaco Editor's loader.min.js) causes the UMD preamble to detect window.define and attempt an anonymous module definition. Strict loaders reject second anonymous modules, silently preventing global registration. Loading UMD scripts before AMD loaders eliminates script order conflicts.
Script Load OrderAMD Loader BehaviorGlobal RegistrationSecurity Status
Monaco $\rightarrow$ DOMPurifyIntercepts anonymous define() callwindow.DOMPurify is undefinedSilently bypassed (Fallback to raw HTML)
DOMPurify $\rightarrow$ MonacoScript executes before define() existsGlobal registered successfully100% Sanitization Active

Monaco's loader intercepts UMD scripts and disables HTML sanitization

During development, we noticed that user-pasted HTML sanitization was silently bypassing DOMPurify checks. The console logged a cryptic AMD module error: Uncaught Error: Can only have one anonymous define call per script file.

The bug stemmed from script load order:

<!-- Broken script order -->
<script src="loader.min.js"></script>   <!-- Monaco AMD loader defines window.define -->
<script src="purify.min.js"></script>   <!-- DOMPurify UMD sees define and calls AMD path -->

Because Monaco's loader.min.js runs first, it defines window.define. When purify.min.js runs afterward, its UMD preamble detects define and tries to register as an anonymous AMD module instead of assigning itself to window.DOMPurify. Monaco's loader rejects the un-named module, leaving window.DOMPurify undefined.


Preloading UMD libraries before Monaco initializes restores global registration

To fix the conflict, we loaded all UMD libraries before initializing the AMD loader:

<!-- Corrected script order -->
<script src="purify.min.js"></script>   <!-- Registers window.DOMPurify as a global -->
<script src="loader.min.js"></script>   <!-- Installs Monaco AMD loader afterward -->

Running DOMPurify first allows it to attach directly to window. When Monaco's loader initializes later, DOMPurify is already registered and ignores the newly attached define function.

Broken Order (Monaco First):
Browser loads loader.min.js (AMD) -> window.define created
                                             |
Browser loads purify.min.js (UMD) -> Sees window.define? --Yes--> Calls define() -> Blocked/Rejected!
                                                                                (window.DOMPurify is undefined)

Correct Order (DOMPurify First): Browser loads purify.min.js (UMD) -> Sees window.define? --No--> Registers window.DOMPurify | Browser loads loader.min.js (AMD) -----------------------------> Creates window.define (DOMPurify works perfectly!)

Rule of thumb: Load UMD global script dependencies prior to initializing strict AMD module loaders like Monaco or RequireJS.
Read this post in the full Engineering Journal →