feat: self-host HTMX 2.0.10 and Hyperscript 0.9.91, remove unpkg CDN

- Download htmx.min.js v2.0.10 and _hyperscript.min.js v0.9.91 locally
- Update head-scripts.html to load from /static/ instead of unpkg CDN
- Remove https://unpkg.com from CSP script-src whitelist
- Update all documentation references to reflect self-hosted paths
- No breaking changes: all hx-* attributes are HTMX 2.0 compatible
This commit is contained in:
juanatsap
2026-05-14 12:59:30 +01:00
parent 20f0e79343
commit 8f4d0e9433
10 changed files with 47 additions and 26 deletions
+1 -1
View File
@@ -1316,7 +1316,7 @@ end
<script type="text/hyperscript" src="/static/hyperscript/color-theme._hs"></script> <script type="text/hyperscript" src="/static/hyperscript/color-theme._hs"></script>
<!-- 2. Then load hyperscript library --> <!-- 2. Then load hyperscript library -->
<script src="https://unpkg.com/hyperscript.org@0.9.14"></script> <script src="/static/hyperscript/_hyperscript.min.js"></script>
``` ```
**Benefits:** **Benefits:**
+2 -2
View File
@@ -988,7 +988,7 @@ isHTMX := r.Header.Get("HX-Request") != ""
hx-push-url="/?lang=en" hx-push-url="/?lang=en"
class="lang-btn"> class="lang-btn">
English English
</button> </button>
<button <button
hx-get="/cv?lang=es" hx-get="/cv?lang=es"
@@ -1669,7 +1669,7 @@ func RateLimitMiddleware(limiter *IPRateLimiter) func(http.Handler) http.Handler
class="lang-btn active" class="lang-btn active"
hx-get="/cv?lang=en" hx-get="/cv?lang=en"
hx-target="#cv-content" hx-target="#cv-content"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-push-url="/?lang=en" hx-push-url="/?lang=en"
onclick="setActive(this)"> onclick="setActive(this)">
English English
+2 -2
View File
@@ -147,7 +147,7 @@ static/hyperscript/
<script type="text/hyperscript" src="/static/hyperscript/keyboard._hs"></script> <script type="text/hyperscript" src="/static/hyperscript/keyboard._hs"></script>
<script type="text/hyperscript" src="/static/hyperscript/zoom._hs"></script> <script type="text/hyperscript" src="/static/hyperscript/zoom._hs"></script>
<script type="text/hyperscript" src="/static/hyperscript/pdf-modal._hs"></script> <script type="text/hyperscript" src="/static/hyperscript/pdf-modal._hs"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.14"></script> <script src="/static/hyperscript/_hyperscript.min.js"></script>
``` ```
## Required Functions ## Required Functions
@@ -288,5 +288,5 @@ end
--- ---
**Last Updated**: 2025-11-30 **Last Updated**: 2025-11-30
**Hyperscript Version**: 0.9.14 **Hyperscript Version**: 0.9.91
**Status**: MANDATORY - ALWAYS FOLLOW **Status**: MANDATORY - ALWAYS FOLLOW
+1 -1
View File
@@ -202,7 +202,7 @@ func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler)
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Contact Form</title> <title>Contact Form</title>
<!-- Include HTMX --> <!-- Include HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script> <script src="/static/htmx/htmx.min.js"></script>
<style> <style>
.form-group { margin-bottom: 1rem; } .form-group { margin-bottom: 1rem; }
label { display: block; margin-bottom: 0.5rem; } label { display: block; margin-bottom: 0.5rem; }
+11 -11
View File
@@ -170,7 +170,7 @@ POST /switch-language
```go ```go
// Strong CSP policy // Strong CSP policy
Content-Security-Policy: default-src 'self'; Content-Security-Policy: default-src 'self';
script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
... ...
@@ -221,8 +221,8 @@ go-git/go-git v5.16.4 // Git operations (no shell commands)
**Frontend Dependencies:** **Frontend Dependencies:**
```javascript ```javascript
// index.html - Using CDN with SRI // index.html - Using CDN with SRI
htmx.org@1.9.10 (SRI: sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX...) htmx 2.0.10 (self-hosted at /static/htmx/htmx.min.js)
hyperscript.org@0.9.14 (no SRI - ADD THIS) hyperscript 0.9.91 (self-hosted at /static/hyperscript/_hyperscript.min.js)
iconify-icon@2.1.0 (no SRI - ADD THIS) iconify-icon@2.1.0 (no SRI - ADD THIS)
``` ```
@@ -259,9 +259,9 @@ iconify-icon@2.1.0 (no SRI - ADD THIS)
**Recommendations:** **Recommendations:**
```html ```html
<!-- Add SRI hashes --> <!-- HTMX and Hyperscript are now self-hosted (no SRI needed) -->
<script src="https://unpkg.com/hyperscript.org@0.9.14" <script src="/static/htmx/htmx.min.js"></script>
integrity="sha384-[GENERATE_SRI_HASH]" <script src="/static/hyperscript/_hyperscript.min.js"></script>
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js" <script src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js"
@@ -1112,7 +1112,7 @@ server {
add_header Cross-Origin-Embedder-Policy "require-corp" always; add_header Cross-Origin-Embedder-Policy "require-corp" always;
# CSP (delegated to Go app, but backup here) # CSP (delegated to Go app, but backup here)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net https://matomo.morenorub.io; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://api.iconify.design https://matomo.morenorub.io; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://matomo.morenorub.io; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://api.iconify.design https://matomo.morenorub.io; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;
# Hide Nginx version # Hide Nginx version
server_tokens off; server_tokens off;
@@ -1224,10 +1224,10 @@ go mod tidy
### Frontend Dependencies ### Frontend Dependencies
```bash ```bash
# Check CDN resources for updates # HTMX and Hyperscript are self-hosted (update by downloading new versions)
# HTMX: https://unpkg.com/htmx.org@latest # HTMX: static/htmx/htmx.min.js (currently 2.0.10)
# Hyperscript: https://unpkg.com/hyperscript.org@latest # Hyperscript: static/hyperscript/_hyperscript.min.js (currently 0.9.91)
# Iconify: https://cdn.jsdelivr.net/npm/iconify-icon@latest # Iconify (CDN): https://cdn.jsdelivr.net/npm/iconify-icon@latest
# Generate SRI hashes # Generate SRI hashes
https://www.srihash.org/ https://www.srihash.org/
+1 -1
View File
@@ -222,7 +222,7 @@ func (h *CVHandler) renderContactError(w http.ResponseWriter, r *http.Request, e
} }
// Render the error template // Render the error template
// Return 200 OK with error content - HTMX 1.9.x logs console.error for non-2xx responses // Return 200 OK with error content - HTMX logs console.error for non-2xx responses
// Validation errors are expected form feedback, not system errors // Validation errors are expected form feedback, not system errors
w.Header().Set(c.HeaderContentType, c.ContentTypeHTML) w.Header().Set(c.HeaderContentType, c.ContentTypeHTML)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
+1 -1
View File
@@ -32,7 +32,7 @@ func SecurityHeaders(next http.Handler) http.Handler {
// Content Security Policy (comprehensive) // Content Security Policy (comprehensive)
csp := "default-src 'self'; " + csp := "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net https://esm.sh https://matomo.txeo.club; " + "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://esm.sh https://matomo.txeo.club; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " + "font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " + "img-src 'self' data: https:; " +
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5 -7
View File
@@ -2,10 +2,8 @@
<!-- Device Detection - Detect real mobile devices vs desktop browser --> <!-- Device Detection - Detect real mobile devices vs desktop browser -->
<script src="/static/js/device-detection.js"></script> <script src="/static/js/device-detection.js"></script>
<!-- HTMX with SRI (Subresource Integrity) --> <!-- HTMX 2.0.10 (self-hosted) -->
<script src="https://unpkg.com/htmx.org@1.9.10" <script src="/static/htmx/htmx.min.js"></script>
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
<!-- Hyperscript Functions - Must load BEFORE hyperscript library --> <!-- Hyperscript Functions - Must load BEFORE hyperscript library -->
<!-- NOTE: cv-functions.js removed - hyperscript def statements are globally available --> <!-- NOTE: cv-functions.js removed - hyperscript def statements are globally available -->
@@ -23,12 +21,12 @@
<!-- NOTE: footer-buttons-interaction.js removed - moved to hyperscript on footer element --> <!-- NOTE: footer-buttons-interaction.js removed - moved to hyperscript on footer element -->
<!-- NOTE: scroll-at-bottom-handler.js removed - duplicate of handleScroll() in utils._hs --> <!-- NOTE: scroll-at-bottom-handler.js removed - duplicate of handleScroll() in utils._hs -->
<!-- Hyperscript - Declarative event handling for enhanced interactivity --> <!-- Hyperscript 0.9.91 (self-hosted) -->
<script src="https://unpkg.com/hyperscript.org@0.9.14"></script> <script src="/static/hyperscript/_hyperscript.min.js"></script>
<!-- Ninja Keys - Lazy loaded on CMD+K (see body-scripts for loader) --> <!-- Ninja Keys - Lazy loaded on CMD+K (see body-scripts for loader) -->
<!-- Iconify - Load synchronously for immediate rendering --> <!-- Iconify - Load synchronously for immediate rendering -->
<!-- Using unpkg CDN (more reliable than code.iconify.design) --> <!-- Using jsdelivr CDN (more reliable than code.iconify.design) -->
<script src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/iconify-icon@2.1.0/dist/iconify-icon.min.js"></script>
{{end}} {{end}}