Generate import maps with content-hashed URLs. No bundler required.
ES modules load sequentially. Browser fetches main.js, parses it, discovers imports, fetches those, parses them, discovers more imports... This creates a waterfall that gets worse with deeper dependency trees.
Cache invalidation is also tricky. Change one file and users might get stale cached versions of files that import it.
This crate:
- Scans your JS/MJS/CSS files and hashes their contents
- Generates an import map that maps clean URLs to hashed URLs
- Generates
<link rel="modulepreload">tags for all modules - Updates your HTML in place
The import map lets you write clean imports (./utils.js) while the browser fetches hashed URLs (./utils.a1b2c3d4.js). Modulepreload tells the browser to fetch everything in parallel, eliminating the waterfall.
Add markers to your HTML:
<head>
<!-- IMPORTMAP -->
<!-- /IMPORTMAP -->
</head>Run the CLI:
importmap path/to/siteOr use as a library:
use importmap::ImportMap;
let map = ImportMap::scan(dir, "")?;
let html = fs::read_to_string("index.html")?;
if let Some(updated) = map.update_html(&html) {
fs::write("index.html", updated)?;
}The markers get replaced with:
<head>
<!-- IMPORTMAP -->
<link rel="modulepreload" href="/scripts/main.a1b2c3d4.js">
<link rel="modulepreload" href="/scripts/utils.e5f6g7h8.js">
<script type="importmap">
{
"imports": {
"/scripts/main.js": "/scripts/main.a1b2c3d4.js",
"/scripts/utils.js": "/scripts/utils.e5f6g7h8.js"
}
}
</script>
<!-- /IMPORTMAP -->
</head>Your source files stay unchanged. The browser:
- Sees modulepreload → fetches all modules in parallel
- Sees
import "./utils.js"→ checks import map → requests./utils.a1b2c3d4.js - Gets a cache hit (already preloaded)
Your server must handle hashed URLs by stripping the hash and serving the original file:
Request: /scripts/main.a1b2c3d4.js
Serve: /scripts/main.js
The hash is always 8 hex characters before the extension. Set Cache-Control: immutable, max-age=31536000 for hashed URLs—the content will never change for that hash.
- Hash: 8 hex chars from rapidhash of file contents
- Extensions:
.js,.mjs,.css - Skips: root-level
.jsfiles (service workers), files with.development.or.dev.in the name - Symlinks: followed (useful for
node_moduleslinks) - Indentation: preserved from the opening marker
MIT