mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 08:48:05 +00:00
perf(web): vendor CDN assets locally — LAN-speed loads, fully offline-capable
Font Awesome, CodeMirror, and htmx were fetched from cdnjs/unpkg on every fresh page load, adding third-party round-trips on a device that often lives on a local network (and htmx was local-only in AP mode, meaning two different loading behaviors to reason about). - Vendored pinned copies under static/v3/vendor/: Font Awesome 6.0.0 (css/all.min.css + the 8 webfonts it references relatively) and CodeMirror 5.65.2 (core, javascript mode, closebrackets/matchbrackets addons, base + monokai css) - ~1.1 MB total, exact versions the CDN tags pinned. - htmx + sse + json-enc extensions now load from the existing local copies (verified 1.9.10, matching the CDN pin) on EVERY network, not just AP mode; the pinned CDN copies remain as a one-shot rescue fallback, mirroring the pattern Alpine already used. The convoluted isAPMode source-flipping logic collapses away. - Dropped the CDN preconnect/dns-prefetch hints (no longer on the critical path). - Fixed a latent bug while relinking CodeMirror: the loader requested mode/json/json.min.js, which does not exist on cdnjs (HTTP 404 verified) - it 404'd on every JSON-editor open. JSON highlighting comes from the javascript mode; the phantom entry is removed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
35a8fbb5be
commit
a10152c995
@@ -0,0 +1 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(P){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},S=P.Pos;function k(e,n){return"pairs"==n&&"string"==typeof e?e:("object"==typeof e&&null!=e[n]?e:t)[n]}P.defineOption("autoCloseBrackets",!1,function(e,n,t){t&&t!=P.Init&&(e.removeKeyMap(i),e.state.closeBrackets=null),n&&(r(k(n,"pairs")),e.state.closeBrackets=n,e.addKeyMap(i))});var i={Backspace:function(e){var n=y(e);if(!n||e.getOption("disableInput"))return P.Pass;for(var t=k(n,"pairs"),r=e.listSelections(),i=0;i<r.length;i++){if(!r[i].empty())return P.Pass;var a=s(e,r[i].head);if(!a||t.indexOf(a)%2!=0)return P.Pass}for(i=r.length-1;0<=i;i--){var o=r[i].head;e.replaceRange("",S(o.line,o.ch-1),S(o.line,o.ch+1),"+delete")}},Enter:function(r){var e=y(r),n=e&&k(e,"explode");if(!n||r.getOption("disableInput"))return P.Pass;for(var i=r.listSelections(),t=0;t<i.length;t++){if(!i[t].empty())return P.Pass;var a=s(r,i[t].head);if(!a||n.indexOf(a)%2!=0)return P.Pass}r.operation(function(){var e=r.lineSeparator()||"\n";r.replaceSelection(e+e,null),O(r,-1),i=r.listSelections();for(var n=0;n<i.length;n++){var t=i[n].head.line;r.indentLine(t,null,!0),r.indentLine(t+1,null,!0)}})}};function r(e){for(var n=0;n<e.length;n++){var t=e.charAt(n),r="'"+t+"'";i[r]||(i[r]=function(n){return function(e){return function(i,e){var n=y(i);if(!n||i.getOption("disableInput"))return P.Pass;var t=k(n,"pairs"),r=t.indexOf(e);if(-1==r)return P.Pass;for(var a,o=k(n,"closeBefore"),s=k(n,"triples"),l=t.charAt(r+1)==e,c=i.listSelections(),f=r%2==0,h=0;h<c.length;h++){var u,d=c[h],p=d.head,g=i.getRange(p,S(p.line,p.ch+1));if(f&&!d.empty())u="surround";else if(!l&&f||g!=e)if(l&&1<p.ch&&0<=s.indexOf(e)&&i.getRange(S(p.line,p.ch-2),p)==e+e){if(2<p.ch&&/\bstring/.test(i.getTokenTypeAt(S(p.line,p.ch-2))))return P.Pass;u="addFour"}else if(l){d=0==p.ch?" ":i.getRange(S(p.line,p.ch-1),p);if(P.isWordChar(g)||d==e||P.isWordChar(d))return P.Pass;u="both"}else{if(!f||!(0===g.length||/\s/.test(g)||-1<o.indexOf(g)))return P.Pass;u="both"}else u=l&&function(e,n){var t=e.getTokenAt(S(n.line,n.ch+1));return/\bstring/.test(t.type)&&t.start==n.ch&&(0==n.ch||!/\bstring/.test(e.getTokenTypeAt(n)))}(i,p)?"both":0<=s.indexOf(e)&&i.getRange(p,S(p.line,p.ch+3))==e+e+e?"skipThree":"skip";if(a){if(a!=u)return P.Pass}else a=u}var v=r%2?t.charAt(r-1):e,b=r%2?e:t.charAt(r+1);i.operation(function(){if("skip"==a)O(i,1);else if("skipThree"==a)O(i,3);else if("surround"==a){for(var e=i.getSelections(),n=0;n<e.length;n++)e[n]=v+e[n]+b;i.replaceSelections(e,"around");for(e=i.listSelections().slice(),n=0;n<e.length;n++)e[n]=(t=e[n],r=void 0,r=0<P.cmpPos(t.anchor,t.head),{anchor:new S(t.anchor.line,t.anchor.ch+(r?-1:1)),head:new S(t.head.line,t.head.ch+(r?1:-1))});i.setSelections(e)}else"both"==a?(i.replaceSelection(v+b,null),i.triggerElectric(v+b),O(i,-1)):"addFour"==a&&(i.replaceSelection(v+v+v+v,"before"),O(i,1));var t,r})}(e,n)}}(t))}}function y(e){var n=e.state.closeBrackets;return n&&!n.override&&e.getModeAt(e.getCursor()).closeBrackets||n}function O(e,n){for(var t=[],r=e.listSelections(),i=0,a=0;a<r.length;a++){var o=r[a];o.head==e.getCursor()&&(i=a);o=o.head.ch||0<n?{line:o.head.line,ch:o.head.ch+n}:{line:o.head.line-1};t.push({anchor:o,head:o})}e.setSelections(t,i)}function s(e,n){n=e.getRange(S(n.line,n.ch-1),S(n.line,n.ch+1));return 2==n.length?n:null}r(t.pairs+"`")});
|
||||
@@ -0,0 +1 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(r){var u=/MSIE \d/.test(navigator.userAgent)&&(null==document.documentMode||document.documentMode<8),k=r.Pos,p={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function y(t){return t&&t.bracketRegex||/[(){}[\]]/}function f(t,e,n){var r=t.getLineHandle(e.line),i=e.ch-1,c=n&&n.afterCursor;null==c&&(c=/(^| )cm-fat-cursor($| )/.test(t.getWrapperElement().className));var a=y(n),c=!c&&0<=i&&a.test(r.text.charAt(i))&&p[r.text.charAt(i)]||a.test(r.text.charAt(i+1))&&p[r.text.charAt(++i)];if(!c)return null;a=">"==c.charAt(1)?1:-1;if(n&&n.strict&&0<a!=(i==e.ch))return null;r=t.getTokenTypeAt(k(e.line,i+1)),n=o(t,k(e.line,i+(0<a?1:0)),a,r,n);return null==n?null:{from:k(e.line,i),to:n&&n.pos,match:n&&n.ch==c.charAt(0),forward:0<a}}function o(t,e,n,r,i){for(var c=i&&i.maxScanLineLength||1e4,a=i&&i.maxScanLines||1e3,o=[],h=y(i),l=0<n?Math.min(e.line+a,t.lastLine()+1):Math.max(t.firstLine()-1,e.line-a),s=e.line;s!=l;s+=n){var u=t.getLine(s);if(u){var f=0<n?0:u.length-1,m=0<n?u.length:-1;if(!(u.length>c))for(s==e.line&&(f=e.ch-(n<0?1:0));f!=m;f+=n){var g=u.charAt(f);if(h.test(g)&&(void 0===r||(t.getTokenTypeAt(k(s,f+1))||"")==(r||""))){var d=p[g];if(d&&">"==d.charAt(1)==0<n)o.push(g);else{if(!o.length)return{pos:k(s,f),ch:g};o.pop()}}}}}return s-n!=(0<n?t.lastLine():t.firstLine())&&null}function e(t,e,n){for(var r=t.state.matchBrackets.maxHighlightLineLength||1e3,i=n&&n.highlightNonMatching,c=[],a=t.listSelections(),o=0;o<a.length;o++){var h,l=a[o].empty()&&f(t,a[o].head,n);l&&(l.match||!1!==i)&&t.getLine(l.from.line).length<=r&&(h=l.match?"CodeMirror-matchingbracket":"CodeMirror-nonmatchingbracket",c.push(t.markText(l.from,k(l.from.line,l.from.ch+1),{className:h})),l.to&&t.getLine(l.to.line).length<=r&&c.push(t.markText(l.to,k(l.to.line,l.to.ch+1),{className:h})))}if(c.length){u&&t.state.focused&&t.focus();function s(){t.operation(function(){for(var t=0;t<c.length;t++)c[t].clear()})}if(!e)return s;setTimeout(s,800)}}function i(t){t.operation(function(){t.state.matchBrackets.currentlyHighlighted&&(t.state.matchBrackets.currentlyHighlighted(),t.state.matchBrackets.currentlyHighlighted=null),t.state.matchBrackets.currentlyHighlighted=e(t,!1,t.state.matchBrackets)})}function c(t){t.state.matchBrackets&&t.state.matchBrackets.currentlyHighlighted&&(t.state.matchBrackets.currentlyHighlighted(),t.state.matchBrackets.currentlyHighlighted=null)}r.defineOption("matchBrackets",!1,function(t,e,n){n&&n!=r.Init&&(t.off("cursorActivity",i),t.off("focus",i),t.off("blur",c),c(t)),e&&(t.state.matchBrackets="object"==typeof e?e:{},t.on("cursorActivity",i),t.on("focus",i),t.on("blur",c))}),r.defineExtension("matchBrackets",function(){e(this,!0)}),r.defineExtension("findMatchingBracket",function(t,e,n){return f(this,t,e=n||"boolean"==typeof e?n?(n.strict=e,n):e?{strict:!0}:null:e)}),r.defineExtension("scanForBracket",function(t,e,n,r){return o(this,t,e,n,r)})});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.cm-s-monokai.CodeMirror{background:#272822;color:#f8f8f2}.cm-s-monokai div.CodeMirror-selected{background:#49483e}.cm-s-monokai .CodeMirror-line::selection,.cm-s-monokai .CodeMirror-line>span::selection,.cm-s-monokai .CodeMirror-line>span>span::selection{background:rgba(73,72,62,.99)}.cm-s-monokai .CodeMirror-line::-moz-selection,.cm-s-monokai .CodeMirror-line>span::-moz-selection,.cm-s-monokai .CodeMirror-line>span>span::-moz-selection{background:rgba(73,72,62,.99)}.cm-s-monokai .CodeMirror-gutters{background:#272822;border-right:0}.cm-s-monokai .CodeMirror-guttermarker{color:#fff}.cm-s-monokai .CodeMirror-guttermarker-subtle{color:#d0d0d0}.cm-s-monokai .CodeMirror-linenumber{color:#d0d0d0}.cm-s-monokai .CodeMirror-cursor{border-left:1px solid #f8f8f0}.cm-s-monokai span.cm-comment{color:#75715e}.cm-s-monokai span.cm-atom{color:#ae81ff}.cm-s-monokai span.cm-number{color:#ae81ff}.cm-s-monokai span.cm-comment.cm-attribute{color:#97b757}.cm-s-monokai span.cm-comment.cm-def{color:#bc9262}.cm-s-monokai span.cm-comment.cm-tag{color:#bc6283}.cm-s-monokai span.cm-comment.cm-type{color:#5998a6}.cm-s-monokai span.cm-attribute,.cm-s-monokai span.cm-property{color:#a6e22e}.cm-s-monokai span.cm-keyword{color:#f92672}.cm-s-monokai span.cm-builtin{color:#66d9ef}.cm-s-monokai span.cm-string{color:#e6db74}.cm-s-monokai span.cm-variable{color:#f8f8f2}.cm-s-monokai span.cm-variable-2{color:#9effff}.cm-s-monokai span.cm-type,.cm-s-monokai span.cm-variable-3{color:#66d9ef}.cm-s-monokai span.cm-def{color:#fd971f}.cm-s-monokai span.cm-bracket{color:#f8f8f2}.cm-s-monokai span.cm-tag{color:#f92672}.cm-s-monokai span.cm-header{color:#ae81ff}.cm-s-monokai span.cm-link{color:#ae81ff}.cm-s-monokai span.cm-error{background:#f92672;color:#f8f8f0}.cm-s-monokai .CodeMirror-activeline-background{background:#373831}.cm-s-monokai .CodeMirror-matchingbracket{text-decoration:underline;color:#fff!important}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -97,26 +97,20 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Resource hints for CDN resources -->
|
||||
<link rel="preconnect" href="https://unpkg.com" crossorigin>
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://unpkg.com">
|
||||
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
|
||||
|
||||
<!-- HTMX for dynamic content loading -->
|
||||
<!-- Use local files when in AP mode (192.168.4.x) to avoid CDN dependency -->
|
||||
<!-- HTMX for dynamic content loading.
|
||||
Local (vendored) files are the primary source for every network —
|
||||
LAN-speed loads, fully offline-capable; the pinned CDN copies remain
|
||||
only as a last-ditch rescue if a local file somehow fails. -->
|
||||
<script>
|
||||
(function() {
|
||||
// Detect AP mode by IP address
|
||||
const isAPMode = window.location.hostname === '192.168.4.1' ||
|
||||
window.location.hostname.startsWith('192.168.4.');
|
||||
|
||||
// In AP mode, use local files; otherwise use CDN
|
||||
const htmxSrc = isAPMode ? '/static/v3/js/htmx.min.js' : 'https://unpkg.com/htmx.org@1.9.10';
|
||||
const sseSrc = isAPMode ? '/static/v3/js/htmx-sse.js' : 'https://unpkg.com/htmx.org/dist/ext/sse.js';
|
||||
const jsonEncSrc = isAPMode ? '/static/v3/js/htmx-json-enc.js' : 'https://unpkg.com/htmx.org/dist/ext/json-enc.js';
|
||||
|
||||
// Load HTMX with fallback
|
||||
const htmxSrc = '/static/v3/js/htmx.min.js';
|
||||
const sseSrc = '/static/v3/js/htmx-sse.js';
|
||||
const jsonEncSrc = '/static/v3/js/htmx-json-enc.js';
|
||||
const htmxCdn = 'https://unpkg.com/htmx.org@1.9.10';
|
||||
const sseCdn = 'https://unpkg.com/htmx.org@1.9.10/dist/ext/sse.js';
|
||||
const jsonEncCdn = 'https://unpkg.com/htmx.org@1.9.10/dist/ext/json-enc.js';
|
||||
|
||||
// Load a script with an optional one-shot fallback source
|
||||
function loadScript(src, fallback, onLoad) {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
@@ -134,42 +128,37 @@
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
// Load HTMX core
|
||||
loadScript(htmxSrc, isAPMode ? 'https://unpkg.com/htmx.org@1.9.10' : '/static/v3/js/htmx.min.js', function() {
|
||||
|
||||
function htmxReady() {
|
||||
debugLog('HTMX loaded successfully');
|
||||
window.dispatchEvent(new Event('htmx:ready'));
|
||||
// Load extensions after core loads
|
||||
loadScript(sseSrc, sseCdn);
|
||||
loadScript(jsonEncSrc, jsonEncCdn);
|
||||
}
|
||||
|
||||
// Load HTMX core (local first, CDN rescue), then the extensions
|
||||
loadScript(htmxSrc, htmxCdn, function() {
|
||||
// Wait a moment for HTMX to initialize, then verify
|
||||
setTimeout(function() {
|
||||
// Verify HTMX loaded
|
||||
if (typeof htmx === 'undefined') {
|
||||
console.error('HTMX failed to load, trying fallback...');
|
||||
const fallbackSrc = isAPMode ? 'https://unpkg.com/htmx.org@1.9.10' : '/static/v3/js/htmx.min.js';
|
||||
if (fallbackSrc !== htmxSrc) {
|
||||
loadScript(fallbackSrc, null, function() {
|
||||
setTimeout(function() {
|
||||
if (typeof htmx !== 'undefined') {
|
||||
debugLog('HTMX loaded from fallback');
|
||||
window.dispatchEvent(new Event('htmx:ready'));
|
||||
// Load extensions after core loads
|
||||
loadScript(sseSrc, isAPMode ? 'https://unpkg.com/htmx.org/dist/ext/sse.js' : '/static/v3/js/htmx-sse.js');
|
||||
loadScript(jsonEncSrc, isAPMode ? 'https://unpkg.com/htmx.org/dist/ext/json-enc.js' : '/static/v3/js/htmx-json-enc.js');
|
||||
} else {
|
||||
console.error('HTMX failed to load from both primary and fallback sources');
|
||||
// Trigger fallback content loading
|
||||
window.dispatchEvent(new Event('htmx-load-failed'));
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
} else {
|
||||
console.error('HTMX failed to load and no fallback available');
|
||||
window.dispatchEvent(new Event('htmx-load-failed'));
|
||||
}
|
||||
} else {
|
||||
debugLog('HTMX loaded successfully');
|
||||
window.dispatchEvent(new Event('htmx:ready'));
|
||||
// Load extensions after core loads
|
||||
loadScript(sseSrc, isAPMode ? 'https://unpkg.com/htmx.org/dist/ext/sse.js' : '/static/v3/js/htmx-sse.js');
|
||||
loadScript(jsonEncSrc, isAPMode ? 'https://unpkg.com/htmx.org/dist/ext/json-enc.js' : '/static/v3/js/htmx-json-enc.js');
|
||||
if (typeof htmx !== 'undefined') {
|
||||
htmxReady();
|
||||
return;
|
||||
}
|
||||
// Loaded but not initialized (e.g. corrupt cached copy):
|
||||
// one CDN rescue attempt, mirroring the Alpine fallback.
|
||||
console.error('HTMX not initialized after load, trying CDN rescue...');
|
||||
loadScript(htmxCdn, null, function() {
|
||||
setTimeout(function() {
|
||||
if (typeof htmx !== 'undefined') {
|
||||
htmxReady();
|
||||
} else {
|
||||
console.error('HTMX failed to load from both local and CDN sources');
|
||||
// Trigger fallback content loading
|
||||
window.dispatchEvent(new Event('htmx-load-failed'));
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
})();
|
||||
@@ -840,10 +829,10 @@
|
||||
</script>
|
||||
|
||||
<!-- CodeMirror for JSON editing - lazy loaded when needed -->
|
||||
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css"></noscript>
|
||||
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css"></noscript>
|
||||
<link rel="preload" href="{{ url_for('static', filename='v3/vendor/codemirror/codemirror.min.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="{{ url_for('static', filename='v3/vendor/codemirror/codemirror.min.css') }}"></noscript>
|
||||
<link rel="preload" href="{{ url_for('static', filename='v3/vendor/codemirror/theme/monokai.min.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="{{ url_for('static', filename='v3/vendor/codemirror/theme/monokai.min.css') }}"></noscript>
|
||||
<!-- CodeMirror scripts loaded on demand when JSON editor is opened -->
|
||||
<script>
|
||||
// Lazy load CodeMirror when needed
|
||||
@@ -851,12 +840,15 @@
|
||||
if (window.CodeMirror) return Promise.resolve();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Vendored CodeMirror 5.65.2 (note: the previously listed CDN
|
||||
// mode/json/json.min.js never existed — it 404'd on every
|
||||
// editor open; JSON highlighting comes from the javascript
|
||||
// mode, so it's simply dropped here)
|
||||
const scripts = [
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/json/json.min.js',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/closebrackets.min.js',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/matchbrackets.min.js'
|
||||
'/static/v3/vendor/codemirror/codemirror.min.js',
|
||||
'/static/v3/vendor/codemirror/mode/javascript/javascript.min.js',
|
||||
'/static/v3/vendor/codemirror/addon/edit/closebrackets.min.js',
|
||||
'/static/v3/vendor/codemirror/addon/edit/matchbrackets.min.js'
|
||||
];
|
||||
|
||||
let loaded = 0;
|
||||
@@ -875,8 +867,9 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Font Awesome icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Font Awesome icons (vendored 6.0.0 — css/all.min.css references
|
||||
../webfonts/ relatively, so both live under v3/vendor/fontawesome/) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='v3/vendor/fontawesome/css/all.min.css') }}">
|
||||
|
||||
<!-- Custom v3 styles -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='v3/app.css') }}">
|
||||
|
||||
Reference in New Issue
Block a user