openvibers/openvibe.live Commits
Route bundles: the broadcast desk and Arena load when you go there, not on every page
Changes
@@@ -5117,7 +5117,50 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig<script src="/js/ov-skeleton.js?v=1" defer></script><script src="/js/ov-odometer.js?v=3" defer></script><script src="/js/ov-density.js?v=3" defer></script>--<script src="/js/app.js?v=175" defer></script>++<!-- ── Route bundles ──────────────────────────────────────────────────────────++ Measured with Chrome's precise coverage on a loaded home page: 14.3% of the JavaScript this++ page ships actually executes. The broadcast desk and the Arena are the clearest of the rest —++ 471KB and 57KB of source that only ever runs on /broadcast and /arena, parsed and compiled on++ every visit to every other page.++++ They load when the route is opened, and again during idle time after first paint so a later++ navigation is instant. Execution order inside a bundle is preserved (async=false on an++ injected script keeps document order), which matters: broadcast-workspace.js has to run++ before broadcast.js, as the old tag order did.++++ Everything outside these files that reaches into them already guards with++ `typeof fn === 'function'`, so a page where the bundle has not loaded behaves as it did++ before the route existed rather than throwing. -->++<script>++(function () {++ var BUNDLES = {++ broadcast: ['/js/broadcast-workspace.js?v=16', '/js/broadcast.js?v=31'],++ arena: ['/js/arena.js?v=18']++ };++ var started = Object.create(null);++ function one(src) {++ if (started[src]) return started[src];++ started[src] = new Promise(function (resolve) {++ var s = document.createElement('script');++ s.src = src;++ s.async = false; // keeps execution in the order they are appended++ s.onload = function () { resolve(true); };++ s.onerror = function () { console.warn('[routes] failed to load', src); resolve(false); };++ document.head.appendChild(s);++ });++ return started[src];++ }++ window.ovLoadRoute = function (name) {++ var list = BUNDLES[name];++ if (!list) return Promise.resolve();++ return Promise.all(list.map(one)); // appended together, so they run in order++ };++ window.ovPrefetchRoutes = function () {++ for (var k in BUNDLES) window.ovLoadRoute(k);++ };++})();++</script>++<script src="/js/app.js?v=176" defer></script><script src="/js/stream-player.js?v=22" defer></script><script src="/js/stream-pip.js?v=1" defer></script><script src="/js/chat-shared.js?v=3" defer></script>@@@ -5181,15 +5224,12 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig<script src="/js/broadcast-media-setup.js?v=1" defer></script><script src="/js/broadcast-devices.js?v=2" defer></script><script src="/js/broadcast-vods.js?v=2" defer></script>--<script src="/js/broadcast-workspace.js?v=16" defer></script>--<script src="/js/broadcast.js?v=30" defer></script><script src="/js/call.js?v=3" defer></script><script src="/js/voice-channels.js?v=2" defer></script><script src="/js/settings.js?v=12" defer></script><script src="/js/staff-console.js?v=1" defer></script><script src="/js/dashboard-moderation.js?v=5" defer></script><script src="/js/ai-viewers.js?v=3" defer></script>--<script src="/js/arena.js?v=18" defer></script><script src="/js/recap.js?v=1" defer></script><script src="/js/context-cards.js?v=4" defer></script><script src="/js/powerchat.js?v=10" defer></script>@@@ -1145,6 +1145,32 @@ function setPageTitle(name) {}window.setPageTitle = setPageTitle;++/** No-op fallback: if the inline bundle loader is missing, routes still resolve. */++if (typeof window.ovLoadRoute !== 'function') window.ovLoadRoute = () => Promise.resolve();++++/**++ * Warm the route bundles once the page has gone quiet.++ *++ * Taking them off the initial parse is the win; making the reader wait for a download when they++ * finally click "Go live" would just move the cost somewhere more annoying. requestIdleCallback++ * fetches them while nothing else is happening, so the first navigation to those routes is as++ * instant as it was when they shipped on every page — without costing anything on the pages that++ * never use them.++ */++(function warmRouteBundles() {++ let done = false;++ const go = () => {++ if (done) return; done = true;++ try { if (typeof window.ovPrefetchRoutes === 'function') window.ovPrefetchRoutes(); } catch { /* */ }++ };++ const start = () => {++ if ('requestIdleCallback' in window) requestIdleCallback(go, { timeout: 12000 });++ else setTimeout(go, 6000);++ };++ if (document.readyState === 'complete') setTimeout(start, 2500);++ else window.addEventListener('load', () => setTimeout(start, 2500), { once: true });++})();++function routeFromURL() {// Remove the server-rendered SEO prerender block once the SPA takes over (it's crawlable// content for no-JS scrapers; JS clients render the real interactive page instead).@@@ -1193,8 +1219,9 @@ function routeFromURL() {navigate('/dashboard', true);return;} else if (segments[0] === 'broadcast') {++ // Show the shell first so the route feels instant, then fill it once its bundle is in.showPage('broadcast');-- loadBroadcastPage();++ ovLoadRoute('broadcast').then(() => { if (typeof loadBroadcastPage === 'function') loadBroadcastPage(); });} else if (segments[0] === 'admin') {window.location.href = `${getOpenVibeToolsUrl()}/admin`;return;@@@ -1233,7 +1260,7 @@ function routeFromURL() {if (typeof loadRecapPage === 'function') loadRecapPage(segments[1]);} else if (segments[0] === 'arena') {showPage('arena');-- if (typeof loadArenaPage === 'function') loadArenaPage(segments);++ ovLoadRoute('arena').then(() => { if (typeof loadArenaPage === 'function') loadArenaPage(segments); });} else if (segments[0] === 'updates') {showPage('updates');loadUpdatesPage();@@@ -5754,7 +5754,10 @@ function _initPipDragResize() {preview.addEventListener('pointercancel', onPointerUp);}--document.addEventListener('DOMContentLoaded', () => {++// This file is loaded on demand when /broadcast is opened, which is almost always after++// DOMContentLoaded has already fired — a listener added then never runs. Run immediately if the++// document is already parsed, and wait only when it genuinely is not.++const _bcBoot = () => {initBroadcastSettingsListeners();// Restoring the saved tab can issue broadcast API calls and force a layout on a hidden page.// Only do it once the broadcast desk is the route the visitor is actually on.@@@ -5829,7 +5832,9 @@ document.addEventListener('DOMContentLoaded', () => {// ── PiP drag/resize on preview ──_initPipDragResize();--});++};++if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _bcBoot);++else _bcBoot();// ═══════════════════════════════════════════════════════════════// Media Request PiP Player (inline on broadcast page)
@@ -5117,7 +5117,50 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
@@ -5117,7 +5117,50 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
<script src="/js/ov-skeleton.js?v=1" defer></script>
<script src="/js/ov-skeleton.js?v=1" defer></script>
<script src="/js/ov-odometer.js?v=3" defer></script>
<script src="/js/ov-odometer.js?v=3" defer></script>
<script src="/js/ov-density.js?v=3" defer></script>
<script src="/js/ov-density.js?v=3" defer></script>
-<script src="/js/app.js?v=175" defer></script>
+<!-- ── Route bundles ──────────────────────────────────────────────────────────
+ Measured with Chrome's precise coverage on a loaded home page: 14.3% of the JavaScript this
+ page ships actually executes. The broadcast desk and the Arena are the clearest of the rest —
+ 471KB and 57KB of source that only ever runs on /broadcast and /arena, parsed and compiled on
+ every visit to every other page.
+
+ They load when the route is opened, and again during idle time after first paint so a later
+ navigation is instant. Execution order inside a bundle is preserved (async=false on an
+ injected script keeps document order), which matters: broadcast-workspace.js has to run
+ before broadcast.js, as the old tag order did.
+
+ Everything outside these files that reaches into them already guards with
+ `typeof fn === 'function'`, so a page where the bundle has not loaded behaves as it did
+ before the route existed rather than throwing. -->
+<script>
+(function () {
+ var BUNDLES = {
+ broadcast: ['/js/broadcast-workspace.js?v=16', '/js/broadcast.js?v=31'],
+ arena: ['/js/arena.js?v=18']
+ };
+ var started = Object.create(null);
+ function one(src) {
+ if (started[src]) return started[src];
+ started[src] = new Promise(function (resolve) {
+ var s = document.createElement('script');
+ s.src = src;
+ s.async = false; // keeps execution in the order they are appended
+ s.onload = function () { resolve(true); };
+ s.onerror = function () { console.warn('[routes] failed to load', src); resolve(false); };
+ document.head.appendChild(s);
+ });
+ return started[src];
+ }
+ window.ovLoadRoute = function (name) {
+ var list = BUNDLES[name];
+ if (!list) return Promise.resolve();
+ return Promise.all(list.map(one)); // appended together, so they run in order
+ };
+ window.ovPrefetchRoutes = function () {
+ for (var k in BUNDLES) window.ovLoadRoute(k);
+ };
+})();
+</script>
+<script src="/js/app.js?v=176" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
@@ -5181,15 +5224,12 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
@@ -5181,15 +5224,12 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
<script src="/js/broadcast-media-setup.js?v=1" defer></script>
<script src="/js/broadcast-media-setup.js?v=1" defer></script>
<script src="/js/broadcast-devices.js?v=2" defer></script>
<script src="/js/broadcast-devices.js?v=2" defer></script>
<script src="/js/broadcast-vods.js?v=2" defer></script>
<script src="/js/broadcast-vods.js?v=2" defer></script>
-<script src="/js/broadcast-workspace.js?v=16" defer></script>
-<script src="/js/broadcast.js?v=30" defer></script>
<script src="/js/call.js?v=3" defer></script>
<script src="/js/call.js?v=3" defer></script>
<script src="/js/voice-channels.js?v=2" defer></script>
<script src="/js/voice-channels.js?v=2" defer></script>
<script src="/js/settings.js?v=12" defer></script>
<script src="/js/settings.js?v=12" defer></script>
<script src="/js/staff-console.js?v=1" defer></script>
<script src="/js/staff-console.js?v=1" defer></script>
<script src="/js/dashboard-moderation.js?v=5" defer></script>
<script src="/js/dashboard-moderation.js?v=5" defer></script>
<script src="/js/ai-viewers.js?v=3" defer></script>
<script src="/js/ai-viewers.js?v=3" defer></script>
-<script src="/js/arena.js?v=18" defer></script>
<script src="/js/recap.js?v=1" defer></script>
<script src="/js/recap.js?v=1" defer></script>
<script src="/js/context-cards.js?v=4" defer></script>
<script src="/js/context-cards.js?v=4" defer></script>
<script src="/js/powerchat.js?v=10" defer></script>
<script src="/js/powerchat.js?v=10" defer></script>
@@ -1145,6 +1145,32 @@ function setPageTitle(name) {
@@ -1145,6 +1145,32 @@ function setPageTitle(name) {
}
}
window.setPageTitle = setPageTitle;
window.setPageTitle = setPageTitle;
+/** No-op fallback: if the inline bundle loader is missing, routes still resolve. */
+if (typeof window.ovLoadRoute !== 'function') window.ovLoadRoute = () => Promise.resolve();
+
+/**
+ * Warm the route bundles once the page has gone quiet.
+ *
+ * Taking them off the initial parse is the win; making the reader wait for a download when they
+ * finally click "Go live" would just move the cost somewhere more annoying. requestIdleCallback
+ * fetches them while nothing else is happening, so the first navigation to those routes is as
+ * instant as it was when they shipped on every page — without costing anything on the pages that
+ * never use them.
+ */
+(function warmRouteBundles() {
+ let done = false;
+ const go = () => {
+ if (done) return; done = true;
+ try { if (typeof window.ovPrefetchRoutes === 'function') window.ovPrefetchRoutes(); } catch { /* */ }
+ };
+ const start = () => {
+ if ('requestIdleCallback' in window) requestIdleCallback(go, { timeout: 12000 });
+ else setTimeout(go, 6000);
+ };
+ if (document.readyState === 'complete') setTimeout(start, 2500);
+ else window.addEventListener('load', () => setTimeout(start, 2500), { once: true });
+})();
+
function routeFromURL() {
function routeFromURL() {
// Remove the server-rendered SEO prerender block once the SPA takes over (it's crawlable
// Remove the server-rendered SEO prerender block once the SPA takes over (it's crawlable
// content for no-JS scrapers; JS clients render the real interactive page instead).
// content for no-JS scrapers; JS clients render the real interactive page instead).
@@ -1193,8 +1219,9 @@ function routeFromURL() {
@@ -1193,8 +1219,9 @@ function routeFromURL() {
navigate('/dashboard', true);
navigate('/dashboard', true);
return;
return;
} else if (segments[0] === 'broadcast') {
} else if (segments[0] === 'broadcast') {
+ // Show the shell first so the route feels instant, then fill it once its bundle is in.
showPage('broadcast');
showPage('broadcast');
- loadBroadcastPage();
+ ovLoadRoute('broadcast').then(() => { if (typeof loadBroadcastPage === 'function') loadBroadcastPage(); });
} else if (segments[0] === 'admin') {
} else if (segments[0] === 'admin') {
window.location.href = `${getOpenVibeToolsUrl()}/admin`;
window.location.href = `${getOpenVibeToolsUrl()}/admin`;
return;
return;
@@ -1233,7 +1260,7 @@ function routeFromURL() {
@@ -1233,7 +1260,7 @@ function routeFromURL() {
if (typeof loadRecapPage === 'function') loadRecapPage(segments[1]);
if (typeof loadRecapPage === 'function') loadRecapPage(segments[1]);
} else if (segments[0] === 'arena') {
} else if (segments[0] === 'arena') {
showPage('arena');
showPage('arena');
- if (typeof loadArenaPage === 'function') loadArenaPage(segments);
+ ovLoadRoute('arena').then(() => { if (typeof loadArenaPage === 'function') loadArenaPage(segments); });
} else if (segments[0] === 'updates') {
} else if (segments[0] === 'updates') {
showPage('updates');
showPage('updates');
loadUpdatesPage();
loadUpdatesPage();
@@ -5754,7 +5754,10 @@ function _initPipDragResize() {
@@ -5754,7 +5754,10 @@ function _initPipDragResize() {
preview.addEventListener('pointercancel', onPointerUp);
preview.addEventListener('pointercancel', onPointerUp);
}
}
-document.addEventListener('DOMContentLoaded', () => {
+// This file is loaded on demand when /broadcast is opened, which is almost always after
+// DOMContentLoaded has already fired — a listener added then never runs. Run immediately if the
+// document is already parsed, and wait only when it genuinely is not.
+const _bcBoot = () => {
initBroadcastSettingsListeners();
initBroadcastSettingsListeners();
// Restoring the saved tab can issue broadcast API calls and force a layout on a hidden page.
// Restoring the saved tab can issue broadcast API calls and force a layout on a hidden page.
// Only do it once the broadcast desk is the route the visitor is actually on.
// Only do it once the broadcast desk is the route the visitor is actually on.
@@ -5829,7 +5832,9 @@ document.addEventListener('DOMContentLoaded', () => {
@@ -5829,7 +5832,9 @@ document.addEventListener('DOMContentLoaded', () => {
// ── PiP drag/resize on preview ──
// ── PiP drag/resize on preview ──
_initPipDragResize();
_initPipDragResize();
-});
+};
+if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _bcBoot);
+else _bcBoot();
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
// Media Request PiP Player (inline on broadcast page)
// Media Request PiP Player (inline on broadcast page)