---
lang: ja-jp
breaks: true
---
# Fluent UI Blazor デモにある `CodeSnippet` を`net8.0` のプロジェクトテンプレートでも動作するようにする 2024-03-19
## `CodeSnippet`
> microsoft/fluentui-blazor
> https://github.com/microsoft/fluentui-blazor

> https://github.com/microsoft/fluentui-blazor/blob/main/examples/Demo/Shared/Components/CodeSnippet.razor
> https://github.com/microsoft/fluentui-blazor/blob/main/examples/Demo/Shared/Components/CodeSnippet.razor.css
> https://github.com/microsoft/fluentui-blazor/blob/main/examples/Demo/Shared/Components/CodeSnippet.razor.js
>
## テンプレートよりプロジェクトを作成する。


## プロジェクト作成直後

## `CodeSnippet` 関連のモジュールをコピーして貼り付ける。

:::info
* ~~`CodeSnippet.razor.js` のプロパティから、「新しい場合はコピーする」に変更する。~~
* 2024-03-21 「コピーしない」でも動作するっぽい。

:::

## `App.razor` に 以下を追加する。
```javascript=
<!-- highlight -->
<script type="text/javascript" src="/js/highlight-extensions.js"></script>
```
```razor=
・・・
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<!-- highlight -->
<script type="text/javascript" src="/js/highlight-extensions.js"></script>
</body>
・・・
```
## `CodeSnippet.razor.cs` の `namespace` を書き換える
```csharp=
namespace HighlightJsApp.Client.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
```
## `CodeSnippet.razor.cs` 内、`CodeSnippet.razor.js` のパスを書き換える
```csharp=
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("hljs.highlightElement", codeElement);
_jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import",
"/Components/CodeSnippet.razor.js");
await _jsModule.InvokeVoidAsync("addCopyButton");
}
}
```
## `CodeSnippet` を使用するサンプルを作成する
* 以下のコードを、`Home.razor`、`Weather.razor`、`Counter.razor` に貼り付ける。
* `Home.razor`、`Weather.razor`には、`@rendermode InteractiveServer` を貼り付ける。
* `@using HighlightJsApp.Client.Components` を先頭付近に追加する。
```razer=
<CodeSnippet Language="xml">@(@"<FluentDesignTheme StorageName=""theme"" />")</CodeSnippet>
<CodeSnippet Language="xml">
@(@"<!-- Set the default theme -->
<script src=""_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js"" type=""text/javascript""></script>
<loading-theme storage-name=""theme""></loading-theme>")
</CodeSnippet>
<CodeSnippet Language="css">
::deep fluent-tabs::part(activeIndicator) {
width: 85%;
}
</CodeSnippet>
<CodeSnippet Language="language-xml">@("<FluentIcon Value=\"@(new Icons.Regular.Size24.Bookmark())\" />")</CodeSnippet>
<CodeSnippet>
@@using Microsoft.FluentUI.AspNetCore.Components.
<FluentCard Width="400px" Height="250px">
<h2>Hello World!</h2>
<FluentButton Appearance="@@Appearance.Accent">Click Me</FluentButton>
</FluentCard>
</CodeSnippet>
```
## 動作確認
### 一見、正常に動作ししているように見える


### しかし、別のページに移動すると
* コードが強調表示されていない


## ページ遷移すると、スタイルシートとJavaScriptが `Head`と`Body`から消えてしまう
> DOM の操作
> https://learn.microsoft.com/ja-jp/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-8.0#interaction-with-the-dom
> 
> コンポーネントのレンダリング後 (OnAfterRender{Async})
> https://learn.microsoft.com/ja-jp/aspnet/core/blazor/components/lifecycle?view=aspnetcore-8.0#after-component-render-onafterrenderasync
> 
## 正常に動作するように修正する。
### `highlight-extensions.js` のグローバル部分をメソッド内に移動する。
* `hljs_OnAfterRender` メソッドを作成して、グローバル部分を切り取って貼り付ける。
```javascript=
export function hljs_OnAfterRender() {
// Add Stylesheets
hljs_addStylesheet('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/styles/vs.min.css', 'highlight-light', null);
hljs_addStylesheet('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/styles/vs2015.min.css', 'highlight-dark', 'disabled');
hljs_addInlineStylesheet(`pre[class~="snippet"] {
--font-monospace: "courier";
--type-ramp-base-font-variations: unset;
font-weight: bold;
}`);
// Add Scripts
const highlight = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/highlight.min.js');
// Add custom code
highlight.onload = () => {
const hljsRazor = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/highlightjs-cshtml-razor@2.1.1/dist/cshtml-razor.min.js');
// Switch highlight Dark/Light theme
const theme = document.querySelector('loading-theme > fluent-design-theme');
if (theme != null) {
theme.addEventListener('onchange', (e) => {
if (e.detail.name == 'mode') {
if (e.detail.newValue === 'undefined') return;
const isDark = e.detail.newValue.includes('dark');
hljs_ColorSwitcher(isDark);
}
});
}
// Detect system theme changing
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
hljs_ColorSystem();
});
// First/default theme
hljs_ColorSystem();
}
}
```
### `highlight-extensions.js` の `body`タグに追加している部分を `head` に変更する。
```javascript=
//document.body.appendChild(script);
document.head.appendChild(script);
```
```javascript=
// Add a <script> to the <body> element
function hljs_addJavaScript(src) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.async = true;
script.onerror = () => {
// Error occurred while loading script
console.error('Error occurred while loading script', src);
};
//document.body.appendChild(script);
document.head.appendChild(script);
return script;
}
```
## `HighlightExtensions.razor` と その `.cs` ファイルを作成する。
`

* `HighlightExtensions.razor` は空のファイル。
* `HighlightExtensions.razor.cs`
```razor=
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace HighlightJsApp.Components;
public partial class HighlightExtensions
{
private IJSObjectReference _jsModule = default!;
[Inject]
protected IJSRuntime JSRuntime { get; set; } = default!;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import",
"/js/highlight-extensions.js"
);
await _jsModule.InvokeVoidAsync("hljs_OnAfterRender");
}
}
}
```
### `CodeSnippet.razor.cs` の `OnAfterRenderAsync` メソッドを改造する。
* エラーになったら3回までリトライするように修正する。
```csharp=
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
int count = 3; // 3回位までにしておく。
while (true)
{
try
{
await JSRuntime.InvokeVoidAsync("hljs.highlightElement", codeElement);
break;
}
catch (Microsoft.JSInterop.JSException)
{
if (count > 0)
{
// 「Microsoft.JSInterop.JSException: 'Could not find 'hljs.highlightElement' ('hljs' was undefined).」
// `HighlightExtensions.razor`の`OnAfterRenderAsync`が完了する前に動作すると、必ずエラーが発生するので。準備ができるまで待機する。
await Task.Delay(100);
}
else
{
throw;
}
}
count--;
}
_jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import",
"/Components/CodeSnippet.razor.js");
await _jsModule.InvokeVoidAsync("addCopyButton");
}
}
```
### `App.razor` を変更する。
* `head`タグの最後で `<HighlightExtensions @rendermode="InteractiveServer" />` を呼び出す。
* `/js/highlight-extensions.js` の呼び出しを削除する。
```htmlembedded=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="HighlightJsApp.styles.css" />
<link rel="icon" type="image/png" href="favicon.ico" />
<HeadOutlet />
<HighlightExtensions @rendermode="InteractiveServer" />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<!-- highlight -->
<!--
<script type="text/javascript" src="/js/highlight-extensions.js"></script>
-->
</body>
</html>
```
## 以上で、ページ遷移しても正常に動作する。


###### tags: `Fluent UI Blazor` `CodeSnippet` `highlight.js`