Blazor預設使用了CSS隔離與捆綁,導致CSS修改後不能實時更新(需要重啟程式再次捆綁才生效)。即使手工管理CSS放至wwwroot/css目錄下,也需要重新整理頁面才能更新CSS。
解決方法:
- 使用程式定時掃描各razor頁面對應的CSS變化,有變化時複製到wwwroot\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@static void Main(string[] args) { string _workPath = Path.GetFullPath(args[0]); int interval = int.Parse(args[1]); Action scan = () => { string src = _workPath; string target = Path.Combine(src, "wwwroot", "css"); bool hasChange = false; foreach (var s in Directory.GetFiles(src, "*.razor.css", SearchOption.AllDirectories)) { // 排除bin目錄 if (s.Substring(src.Length + 1).StartsWith("bin")) continue; if (s.Substring(src.Length + 1).StartsWith("wwwroot")) continue; //按檔案路徑複製CSS string t = Path.Combine(target, s.Substring(src.Length + 1)); if (!Directory.Exists(Path.GetDirectoryName(t))) Directory.CreateDirectory(Path.GetDirectoryName(t)!); t = t.Replace(".razor.css", ".css"); if (!File.Exists(t) || new FileInfo(s).LastWriteTime > new FileInfo(t).LastWriteTime) { File.Copy(s, t, true); hasChange = true; Console.WriteLine($"updated {s.Substring(src.Length + 1)}"); } } if (hasChange) File.WriteAllText(Path.Combine(Path.Combine(Path.GetDirectoryName(target), "data", "cssChangeTime.json")), DateTime.Now.Ticks.ToString()); }; scan(); Console.WriteLine("ok,scan..."); //檔案監視沒有工作,那就定時掃描 System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = interval * 1000; timer.Elapsed += (s, e) => scan(); timer.Enabled = true; Console.Read(); }
在index.html中增加函式
@@@code<!-- 除錯時 --> <link href="./css/Layouts/UserLayout.css" rel="stylesheet"> <script> window.refreshStylesWithPrefix = (s) => { var prefix = window.location.origin + '/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@
@inject IJSRuntime JSRuntime long cssLastChangeTime = 0 ; async void adjustReloadCss(object state) { var newTime = long.Parse(await HttpClient.GetStringAsync(@"data/cssChangeTime.json")); if (newTime > cssLastChangeTime) { await JSRuntime.InvokeVoidAsync("refreshStylesWithPrefix", ""); cssLastChangeTime = newTime; } } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); #if DEBUG _timer = new Timer(adjustReloadCss, null, TimeSpan.Zero, TimeSpan.FromSeconds(2)); #endif }