At a glance
defer
Script fetches in parallel with HTML parsing and executes after the document is fully
parsed, in source order. The CDN auto-init fires on DOMContentLoaded, which fires immediately
after the deferred script runs — so the menu is always ready when the page becomes interactive.
- Zero parser blocking — fastest possible FCP/LCP
- DOM is ready when the script runs — no timing race
- Script order preserved when multiple deferred scripts exist
- Not available for inline
<script>blocks
async
Script fetches in parallel with HTML parsing and executes as soon as it downloads,
potentially before the DOM is complete. The CDN bundle handles this correctly — it defers its own auto-init
to DOMContentLoaded internally, so you get the same result as defer at the cost of
unpredictable execution order.
- Fastest possible download start — no parse dependency
- Safe with AMegMen CDN build (internal DOMContentLoaded guard)
- Execution order not guaranteed when multiple async scripts exist
- Slightly more complex mental model
No attribute (blocking)
Script is fetched and executed synchronously, blocking HTML parsing. Appropriate only when the script must
run before any subsequent HTML is parsed — rare for UI libraries. Placing the tag at the bottom of
<body> mitigates the blocking, but defer is strictly better.
- Guaranteed execution before anything that follows in the HTML
- Blocks HTML parser — harms FCP and LCP
- Prevents browser speculative pre-loading of later resources
Using defer (recommended)
Place the <script> tag anywhere — <head> or end of
<body> — with the defer attribute. The browser fetches the script in parallel
with HTML parsing. Execution happens after parsing completes, just before DOMContentLoaded fires.
amegmen.cdn.js) listens for
DOMContentLoaded to run auto-init. When the script is deferred, HTML parsing finishes before the
script runs, so DOMContentLoaded fires synchronously after the script executes. The menu is always
ready when the page becomes interactive — no race condition.
<!-- In <head> — preferred for earliest fetch -->
<link rel="stylesheet" href="dist/styles/amegmen.css" />
<script src="dist/scripts/amegmen.cdn.js" defer></script>
<!-- HTML nav markup (parsed before script executes) -->
<nav data-amegmen aria-label="Primary navigation">
<ul>
<li>
<a href="/products">Products</a>
<div class="amegmen-panel">
<ul class="amegmen-panel-group">
<li><a href="/software">Software</a></li>
<li><a href="/services">Services</a></li>
</ul>
</div>
</li>
<li><a href="/pricing">Pricing</a></li>
</ul>
</nav>
When to use defer: This is the default choice for any production deployment. It
maximises parse performance, ensures the DOM is ready before the script runs, and preserves script execution
order when multiple deferred scripts are present.
See the Lighthouse demo for a complete example combining
defer with critical CSS inlining and stylesheet preloading for a perfect performance score.
Using async
The async attribute also fetches the script in parallel with HTML parsing, but execution happens
as soon as the download finishes — the parser is paused briefly at that point. Because the
download may complete before the document is fully parsed, a naive script that reads the DOM immediately would
fail. The AMegMen CDN build handles this transparently by checking document.readyState and
deferring to DOMContentLoaded when necessary.
amegmen.cdn.js: The CDN auto-init code reads
document.readyState at execution time. If the DOM is already complete (readyState
'interactive' or 'complete'), init runs immediately. Otherwise it registers a one-time
DOMContentLoaded listener. This makes the bundle safe for both async and
defer.
<!-- async: script executes as soon as it downloads -->
<link rel="stylesheet" href="dist/styles/amegmen.css" />
<script src="dist/scripts/amegmen.cdn.js" async></script>
<!-- AMegMen's internal DOMContentLoaded guard makes this safe -->
<nav data-amegmen aria-label="Primary navigation">
<ul>
<li><a href="/products">Products</a></li>
</ul>
</nav>
When to use async: Consider async when the mega menu is not above the
fold on initial load, or when you're aggregating many independent scripts and execution order between them does
not matter. For most single-nav pages, defer is simpler and equally fast.
Caveats: If you also load other scripts that depend on AMegMen (for example, a script that
calls new AMegMen(…) programmatically), those scripts must not rely on AMegMen being
available in global scope unless they also guard against load order. Use defer in that case —
deferred scripts execute in source order, making dependency sequencing predictable.
No attribute — blocking script
Omitting both defer and async causes the browser to pause HTML parsing, fetch the
script, execute it, then resume parsing. This is rarely the right choice for a UI library.
<!-- End-of-body placement reduces (but does not eliminate) blocking -->
<!-- Prefer defer instead. -->
<script src="dist/scripts/amegmen.cdn.js"></script>
</body>
The one valid case: If your HTML is assembled server-side with all navigation markup inlined
and you have strict control over when every byte of the page is delivered, a blocking script at the very end of
<body> works. But a deferred script in <head> starts downloading earlier
and imposes less latency overall — it is always the better option.
Strategy comparison
| Strategy | Blocks HTML parser? | Execution timing | Order guaranteed? | Safe with AMegMen CDN? |
|---|---|---|---|---|
defer ✓ |
No | After DOM parsed, before DOMContentLoaded | Yes — source order | Yes |
async |
Briefly on execute | As soon as download completes | No | Yes — internal guard |
| None (blocking) | Yes — fully | Immediately during parse | Yes — source order | Yes (with DOM-ready guard) |
| None (end of body) | Effectively no | After all preceding HTML | Yes — source order | Yes |
Module bundlers (webpack, Vite, Rollup, Parcel)
When importing AMegMen through a bundler, the defer/async distinction is handled by
the bundler's output configuration. The ESM entry point has no auto-init side effect — you control
initialization explicitly.
// Bundler import — no CDN, no global, tree-shakeable
import { AMegMen, autoInit } from 'amegmen';
import 'amegmen/dist/styles/amegmen.css';
// Manual init: called after your framework mounts the DOM
autoInit('[data-amegmen]');
// Or per-element:
const menu = new AMegMen(document.querySelector('#main-nav'), {
openOnMouseover: true,
});
The package declares "sideEffects": false, so tree-shaking removes any unused exports from the
final bundle.
Related demos
- Main demo — all five markup patterns
-
Lighthouse demo —
defer+ critical CSS + perfect scores - Print demo — hard-copy layout