ASP.NET Core Blazor JavaScript со статическим отображением на стороне сервера (статический SSR)

В этой статье объясняется, как загрузить JavaScript (JS) в статическую отрисовку на стороне Blazor Web App сервера (статический SSR) и расширенную навигацию.

Некоторые приложения зависят от выполнения задач инициализации, относящихся JS к каждой странице. При использовании Blazorрасширенной функции навигации, которая позволяет пользователю избежать перезагрузки всей страницы, конкретная JS страница может не выполняться повторно, как ожидалось при каждом возникновении расширенной навигации страницы.

Чтобы избежать этой проблемы, мы не рекомендуем полагаться на элементы, относящиеся к странице <script> , расположенные за пределами файла макета, примененного к компоненту. Вместо этого скрипты должны зарегистрировать afterWebStartedJS инициализатор для выполнения логики инициализации и использовать прослушиватель событий (blazor.addEventListener("enhancedload", callback)) для прослушивания обновлений страниц, вызванных расширенной навигацией.

В следующем примере показано, как настроить JS код для запуска при первоначальной загрузке или обновлении статической отрисовки страницы с расширенной навигацией.

PageWithScript Следующий пример компонента — это компонент в приложении, для выполнения скриптов с помощью статического SSR и расширенной навигации. Следующий пример компонента включает PageScript компонент из Razor библиотеки классов (RCL), который добавляется в решение далее в этой статье.

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), добавьте следующий модуль.

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);
}

Добавьте следующий 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 при возникновении любого расширенного обновления страницы, включая улучшенную навигацию.

Чтобы повторно использовать один и тот же модуль между страницами, но вызовы onLoad и onDispose обратные вызовы, вызываемые при каждом изменении страницы, добавьте строку запроса в конец скрипта, чтобы он распознался как другой модуль. Приложение может принять соглашение об использовании имени компонента в качестве значения строки запроса. В следующем примере строка запроса имеет значение "counter", так как ссылка на этот PageScript компонент помещается в Counter компонент. Это просто предложение, и вы можете использовать любую схему строки запроса, которую вы предпочитаете.

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

Чтобы отслеживать изменения в определенных элементах DOM, используйте MutationObserver шаблон в JS клиенте. Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения Blazor ASP.NET Core (взаимодействие JS).

Пример реализации без использования RCL

Подход, описанный в этой статье, можно реализовать непосредственно в не Blazor Web App используя библиотеку Razor классов (RCL). Пример см. в разделе "Включение создания QR-кода для приложений проверки подлинности TOTP" в ASP.NET Core Blazor Web App.