The AMD Loader Conflict That Silently Broke DOMPurify
TLDR
Loading a UMD library (like DOMPurify) after a strict AMD module loader (like Monaco Editor'sloader.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 Order | AMD Loader Behavior | Global Registration | Security Status |
|---|---|---|---|
| Monaco $\rightarrow$ DOMPurify | Intercepts anonymous define() call | window.DOMPurify is undefined | Silently bypassed (Fallback to raw HTML) |
| DOMPurify $\rightarrow$ Monaco | Script executes before define() exists | Global registered successfully | 100% 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.