一種Blazor開發模式下CSS的動態載入方法

秦秋随發表於2024-05-08

Blazor預設使用了CSS隔離與捆綁,導致CSS修改後不能實時更新(需要重啟程式再次捆綁才生效)。即使手工管理CSS放至wwwroot/css目錄下,也需要重新整理頁面才能更新CSS。

解決方法:

  1. 使用程式定時掃描各razor頁面對應的CSS變化,有變化時複製到wwwroot\css對應目錄下。這樣可以保留隔離的結構,release模式下可以繼續隔離
  2. 在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
}
@@#

相關文章