Blazor默認使用了CSS隔離與捆綁,導致CSS修改後不能實時更新(需要重啓程序再次捆綁才生效)。即使手工管理CSS放至wwwroot/css目錄下,也需要刷新頁面才能更新CSS。
解決方法:
- 定時掃描各razor頁面對應的CSS變化,有變化時複製集中到一個CSS中。這樣可以保留隔離的結構,release模式下可以繼續隔離
- 在index.html中增加定時檢測功能,檢測後臺CSS有無變化,有則立即更新CSS,這樣可以避免整頁刷新
禁用捆綁
@@@code<PropertyGroup> <OutputType>Exe</OutputType> <TargetFrameworks>net8.0</TargetFrameworks> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <DisableScopedCssBundling Condition="'$(Configuration)'=='Debug'">true</DisableScopedCssBundling> <!--<ScopedCssEmbeddedResourceNamespace>$(RootNamespace).wwwroot</ScopedCssEmbeddedResourceNamespace>--> <!--<PublishAot>true</PublishAot>--> <!--<InvariantGlobalization>true</InvariantGlobalization>--> </PropertyGroup>
掃描服務類
@@@code@csharp@public interface ICssService { Task<bool> AdjustReloadCss(); } public class CssService : ICssService { private readonly HttpClient _httpClient; public CssService(HttpClient httpClient) { _httpClient = httpClient; //啓動掃描 startScan(); } [Conditional("DEBUG")] void startScan() { string _workPath = AppDomain.CurrentDomain.BaseDirectory; int interval = 3; scan(_workPath); Console.WriteLine("ok,scan..."); //文件監視沒有工作,那就定時掃描 System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = interval * 1000; timer.Elapsed += (s, e) => scan(_workPath); timer.Enabled = true; } /// <summary> /// 掃描,只有有一處更新,則重新生成整個文件 /// </summary> /// <param name = "_workPath"></param> void scan(string _workPath) { int idx = _workPath.IndexOf("bin\\Debug"); //只在DEBUG模式下處理 if (idx == -1) return; string root = _workPath.Substring(0, idx - 1); string wwwroot = Path.Combine(root, "wwwroot"); string cssFile = Path.Combine(wwwroot, "css", $"{nameof(UsbClient)}.css"); DateTime lastChangeTime = DateTime.MinValue; if (File.Exists(cssFile)) { lastChangeTime = new FileInfo(cssFile).LastWriteTime; } bool hasChange = false; //todo:嘗試使用文件監視 List<string> files = Directory.GetFiles(root, "*.razor.css", SearchOption.AllDirectories).Where(s => { if (s.Substring(root.Length + 1).StartsWith("bin") || s.Substring(root.Length + 1).StartsWith("wwwroot")) return false; return true; }).ToList(); foreach (var s in files) { if (new FileInfo(s).LastWriteTime > lastChangeTime) { hasChange = true; break; } } if (hasChange) { //所有文件合併 File.WriteAllLines(cssFile, files.Select(r => File.ReadAllText(r)).ToArray()); File.WriteAllText(Path.Combine(wwwroot, "data", "cssChangeTime.json"), DateTime.Now.Ticks.ToString()); } } long cssLastChangeTime = 0; public async Task<bool> AdjustReloadCss() { #if DEBUG var newTime = long.Parse(await _httpClient.GetStringAsync(@"data/cssChangeTime.json")); if (newTime > cssLastChangeTime) { cssLastChangeTime = newTime; return true; } #endif return false; } }
在index.html中增加函數
@@@code<!-- 調試時 --> <link href="./css/UsbClient.css" rel="stylesheet"> <script> window.refreshStyle = (s) => { var prefix = window.location.origin + '/css/UsbClient.css'; var links = document.querySelectorAll('link[rel="stylesheet"]'); links.forEach(function (link) { if (link.href.startsWith(prefix)) { var newLink = document.createElement('link'); newLink.rel = 'stylesheet'; newLink.type = 'text/css'; newLink.href = link.href; link.parentNode.replaceChild(newLink, link); } }); } </script>
在相應的Layout中增加判斷
@@@code@csharp@
// DEBUG模式下自動刷新CSS(強制重刷) @inject IJSRuntime JSRuntime @inject ICssService cssService #if DEBUG async void adjustReloadCss(object state) { var needReload = await cssService.AdjustReloadCss(); if (needReload) { await JSRuntime.InvokeVoidAsync("refreshStyle", ""); } } private Timer _timer; protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); _timer = new Timer(adjustReloadCss, null, TimeSpan.Zero, TimeSpan.FromSeconds(2)); } #endif