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 '../common/feature_flags'; 16 17let lastReloadDialogTime = 0; 18const kMinTimeBetweenDialogsMs = 10000; 19const changedPaths = new Set<string>(); 20 21export function initLiveReloadIfLocalhost() { 22 if (!location.origin.startsWith('http://localhost:')) return; 23 24 const monitor = new EventSource('/live_reload'); 25 monitor.onmessage = msg => { 26 const change = msg.data; 27 console.log('Live reload:', change); 28 changedPaths.add(change); 29 if (change.endsWith('.css')) { 30 reloadCSS(); 31 } else if (change.endsWith('.html') || change.endsWith('.js')) { 32 reloadDelayed(); 33 } 34 }; 35 monitor.onerror = (err) => { 36 // In most cases the error is fired on reload, when the socket disconnects. 37 // Delay the error and the reconnection, so in the case of a reload we don't 38 // see any midleading message. 39 setTimeout(() => console.error('LiveReload SSE error', err), 1000); 40 }; 41} 42 43function reloadCSS() { 44 const css = document.querySelector('link[rel=stylesheet]') as HTMLLinkElement; 45 if (!css) return; 46 const parent = css.parentElement!; 47 parent.removeChild(css); 48 parent.appendChild(css); 49} 50 51const rapidReloadFlag = featureFlags.register({ 52 id: 'rapidReload', 53 name: 'Development: rapid live reload', 54 defaultValue: false, 55 description: 'During development, instantly reload the page on change. ' + 56 'Enables lower latency of live reload at the cost of potential ' + 57 'multiple re-reloads.', 58 devOnly: true, 59}); 60 61function reloadDelayed() { 62 setTimeout(() => { 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 }, rapidReloadFlag.get() ? 0 : 1000); 76} 77