1// Copyright (C) 2021 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {featureFlags} from '../core/feature_flags'; 16 17let lastReloadDialogTime = 0; 18const kMinTimeBetweenDialogsMs = 10000; 19const changedPaths = new Set<string>(); 20 21export function initLiveReload() { 22 const monitor = new EventSource('/live_reload'); 23 monitor.onmessage = (msg) => { 24 const change = String(msg.data); 25 console.log('Live reload:', change); 26 changedPaths.add(change); 27 if (change.endsWith('.css')) { 28 reloadCSS(); 29 } else if (change.endsWith('.html') || change.endsWith('.js')) { 30 reloadDelayed(); 31 } 32 }; 33 monitor.onerror = (err) => { 34 // In most cases the error is fired on reload, when the socket disconnects. 35 // Delay the error and the reconnection, so in the case of a reload we don't 36 // see any midleading message. 37 setTimeout(() => console.error('LiveReload SSE error', err), 1000); 38 }; 39} 40 41function reloadCSS() { 42 const css = document.querySelector('link[rel=stylesheet]'); 43 if (!css) return; 44 const parent = css.parentElement!; 45 parent.removeChild(css); 46 parent.appendChild(css); 47} 48 49const rapidReloadFlag = featureFlags.register({ 50 id: 'rapidReload', 51 name: 'Development: rapid live reload', 52 defaultValue: false, 53 description: 54 'During development, instantly reload the page on change. ' + 55 'Enables lower latency of live reload at the cost of potential ' + 56 'multiple re-reloads.', 57 devOnly: true, 58}); 59 60function reloadDelayed() { 61 setTimeout( 62 () => { 63 let pathsStr = ''; 64 for (const path of changedPaths) { 65 pathsStr += path + '\n'; 66 } 67 changedPaths.clear(); 68 if (Date.now() - lastReloadDialogTime < kMinTimeBetweenDialogsMs) return; 69 const reload = 70 rapidReloadFlag.get() || confirm(`${pathsStr}changed, click to reload`); 71 lastReloadDialogTime = Date.now(); 72 if (reload) { 73 window.location.reload(); 74 } 75 }, 76 rapidReloadFlag.get() ? 0 : 1000, 77 ); 78} 79