次の方法で共有


ASP.NET Core Blazor JavaScript と静的サーバー側レンダリング (静的 SSR)

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

この記事では、静的サーバー側レンダリング (静的 SSR) とJSを使用する Blazor Web App で JavaScript () を読み込む方法について説明します。

一部のアプリは、JS が各ページに固有の初期化タスクの実行に依存する場合があります。 Blazor の拡張ナビゲーション機能はページ全体の再読み込みを可能にするものですが、これを使用するとき、JS が再実行されない可能性があります (拡張ページ ナビゲーションの際は通常は再実行されます)。

この問題を回避するため、コンポーネントに適用されているレイアウト ファイルの外に置かれているページ固有の <script> 要素に依存することはお勧めしません。 代わりに、スクリプトは初期化ロジックを実行し、イベント リスナーを使用して拡張ナビゲーションによって引き起こされるページの更新をリッスンするために、afterWebStartedJS 初期化子 を登録する必要があります。

イベント

次のイベント リスナーの例では、{CALLBACK} プレースホルダーはコールバック関数です。

  • 拡張ナビゲーション開始 (enhancednavigationstart) は、拡張ナビゲーションが発生する前にコールバックをトリガーします。

    blazor.addEventListener("enhancednavigationstart", {CALLBACK});
    
  • 拡張ナビゲーションの終了 (enhancednavigationend) は、拡張ナビゲーションが発生した後にコールバックをトリガーします。

    blazor.addEventListener("enhancednavigationend", {CALLBACK});
    
  • 強化されたナビゲーション ページ読み込み (enhancedload) は、ストリーミングの更新など、強化されたナビゲーションによってページが更新されるたびにコールバックをトリガーします。

    blazor.addEventListener("enhancedload", {CALLBACK});
    

この問題を回避するため、コンポーネントに適用されているレイアウト ファイルの外に置かれているページ固有の <script> 要素に依存することはお勧めしません。 代わりに、スクリプトは初期化ロジックを実行し、イベント リスナーを使用して拡張ナビゲーションによって引き起こされるページの更新をリッスンするために、afterWebStartedJS 初期化子 を登録する必要があります。

blazor.addEventListener("enhancedload", {CALLBACK});

前の例では、{CALLBACK} プレースホルダーはコールバック関数です。

拡張ページ読み込みスクリプトの例

次の例は、拡張ナビゲーションのある静的にレンダリングされたページが最初に読み込まれたか、更新されたときに実行する JS コードを構成する 1 つの方法を示しています。

次の PageWithScript コンポーネントの例は、静的 SSR と拡張ナビゲーションを使ってスクリプトを実行する必要があるアプリ内のコンポーネントです。 次のコンポーネントの例には、この記事の後半でソリューションに追加される PageScript クラス ライブラリ (RCL) の Razor コンポーネントが含まれています。

Components/Pages/PageWithScript.razor:

@page "/page-with-script"
@using BlazorPageScript

<PageTitle>Enhanced Load Script Example</PageTitle>

<PageScript Src="./Components/Pages/PageWithScript.razor.js" />

Welcome to my page.

Blazor Web App で、次の併置された JS ファイルを追加します。

  • onLoad は、スクリプトがページに追加されたときに呼び出されます。
  • onUpdate は、強化された更新プログラムの後にスクリプトがページにまだ存在する場合に呼び出されます。
  • onDispose は、強化された更新後にスクリプトがページから削除されたときに呼び出されます。

Components/Pages/PageWithScript.razor.js:

export function onLoad() {
  console.log('Loaded');
}

export function onUpdate() {
  console.log('Updated');
}

export function onDispose() {
  console.log('Disposed');
}

Razor クラス ライブラリ (RCL) (RCL の例では BlazorPageScriptという名前) で、次のモジュールを追加します。これは、JS 初期化子です。

wwwroot/BlazorPageScript.lib.module.js:

const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
  if (!src) {
    throw new Error('Must provide a non-empty value for the "src" attribute.');
  }

  let pageScriptInfo = pageScriptInfoBySrc.get(src);

  if (pageScriptInfo) {
    pageScriptInfo.referenceCount++;
  } else {
    pageScriptInfo = { referenceCount: 1, module: null };
    pageScriptInfoBySrc.set(src, pageScriptInfo);
    initializePageScriptModule(src, pageScriptInfo);
  }
}

function unregisterPageScriptElement(src) {
  if (!src) {
    return;
  }

  const pageScriptInfo = pageScriptInfoBySrc.get(src);
  
  if (!pageScriptInfo) {
    return;
  }

  pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {
  if (src.startsWith("./")) {
    src = new URL(src.substr(2), document.baseURI).toString();
  }

  const module = await import(src);

  if (pageScriptInfo.referenceCount <= 0) {
    return;
  }

  pageScriptInfo.module = module;
  module.onLoad?.();
  module.onUpdate?.();
}

function onEnhancedLoad() {
  for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
    if (referenceCount <= 0) {
      module?.onDispose?.();
      pageScriptInfoBySrc.delete(src);
    }
  }

  for (const { module } of pageScriptInfoBySrc.values()) {
    module?.onUpdate?.();
  }
}

export function afterWebStarted(blazor) {
  customElements.define('page-script', class extends HTMLElement {
    static observedAttributes = ['src'];

    attributeChangedCallback(name, oldValue, newValue) {
      if (name !== 'src') {
        return;
      }

      this.src = newValue;
      unregisterPageScriptElement(oldValue);
      registerPageScriptElement(newValue);
    }

    disconnectedCallback() {
      unregisterPageScriptElement(this.src);
    }
  });

  blazor.addEventListener('enhancedload', onEnhancedLoad);
}

この場合のモジュールは JS 初期化子 (afterWebStarted) であるため、<script> タグをアプリのルート コンポーネント (通常は BlazorPageScript.lib.module.js の場合は App コンポーネント) に追加しないでください。 JS 初期化子が検出され、Blazor フレームワークによって自動的に読み込まれます。

次の PageScript コンポーネントを RCL に追加します。

PageScript.razor:

<page-script src="@Src"></page-script>

@code {
    [Parameter]
    [EditorRequired]
    public string Src { get; set; } = default!;
}

PageScript コンポーネントは通常、ページの最上位レベルで機能します。

PageScript コンポーネントをアプリのレイアウト (たとえば、MainLayout.razor) に配置すると、そのレイアウトを使うページ間で共有 PageScript になり、次に、コンポーネントによりページ全体のリロード後にのみ onLoad が実行され、拡張ナビゲーションなどの拡張ページの更新が発生したときに onUpdate が実行されます。

ページ間で同じモジュールを再利用し、各ページ変更で onLoadonDispose コールバックを呼び出すには、スクリプトの末尾にクエリ文字列を追加して、別のモジュールとして認識されるようにします。 アプリでは、コンポーネントの名前をクエリ文字列値として使用する規則を採用できます。 次の例では、この counter コンポーネント参照が PageScript コンポーネントに配置されるため、クエリ文字列は "Counter" です。 これは単なる提案であり、任意のクエリ文字列スキームを使用できます。

<PageScript Src="./Components/Pages/PageWithScript.razor.js?counter" />

特定の DOM 要素の変更を監視するには、クライアントの MutationObserver で JS パターンを使用します。 詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

RCL を使用しない実装

この記事で説明する方法は、RCL のアセットをアプリに移動することで、Razor クラス ライブラリ (RCL) を使用せずに、Blazor Web App に直接実装できます。 ただし、RCL は組織全体のアプリで使用するために NuGet パッケージにパッケージ化できるため、RCL Blazor の使用は便利です。

アセットを Blazor Web Appに移動する場合は、アプリに合わせてモジュール (BlazorPageScript.lib.module.js) の名前を変更し、JS の初期化子に対応するファイル命名規則に従ってください。 ファイルの名前が正しくない場合、Blazor はモジュールを検出して読み込めず、afterWebStarted イベントはアプリの起動時に自動的に実行されません。