ASP.NET Core Blazor JavaScript com renderização estática do lado do servidor (SSR estática)

Este artigo explica como carregar JavaScript (JS) em um aplicativo Web Blazor com a renderização estática do lado servidor (SSR estática) e navegação aprimorada.

Alguns aplicativos dependem de JS para executar tarefas de inicialização específicas para cada página. Ao usar o recurso de navegação aprimorada de Blazor, que permite ao usuário evitar recarregar a página inteira, o JS específico da página pode não ser executado novamente como esperado sempre que ocorrer uma navegação de página aprimorada.

Para evitar esse problema, não recomendamos confiar em elementos <script> específicos da página colocados fora do arquivo de layout aplicado ao componente. Em vez disso, os scripts devem registrar um inicializador afterWebStartedJS para executar a lógica de inicialização e usar um ouvinte de eventos (blazor.addEventListener("enhancedload", callback)) para escutar atualizações de página causadas pela navegação aprimorada.

O exemplo a seguir demonstra uma maneira de configurar o código JS para ser executado quando uma página renderizada estaticamente com navegação aprimorada for inicialmente carregada ou atualizada.

O exemplo de componente PageWithScript a seguir é um componente no aplicativo que exige que os scripts sejam executados com SSR estático e navegação aprimorada. O exemplo de componente a seguir inclui um componente PageScript de uma RCL (biblioteca de classes) Razor que é adicionado à solução mais adiante neste artigo.

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.

No aplicativo Web Blazor, adicione o arquivoJS agrupado a seguir:

  • onLoad é chamado quando o script é adicionado à página.
  • onUpdate é chamado quando o script ainda existe na página após uma atualização aprimorada.
  • onDispose é chamado quando o script é removido da página após uma atualização aprimorada.

Components/Pages/PageWithScript.razor.js:

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

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

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

Em uma RCL (Biblioteca de classes Razor) (o exemplo da RCL é nomeado como BlazorPageScript), adicione o módulo a seguir.

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

Na RCL, adicione o componente PageScript a seguir.

PageScript.razor:

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

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

O componente PageScript funciona normalmente no nível superior de uma página.

Se você colocar o componente PageScript em um layout do aplicativo (por exemplo, MainLayout.razor), o que resultará em um PageScript compartilhado entre as páginas que usam o layout, o componente só executará o onLoad após um recarregamento total da página e o onUpdate quando ocorrer qualquer atualização de página aprimorada, incluindo navegação aprimorada.

Para reutilizar o mesmo módulo entre as páginas, mas ter os retornos de chamada onLoad e onDispose invocados em cada alteração de página, acrescente uma cadeia de caracteres de consulta ao final do script para que ele seja reconhecido como um módulo diferente. Um aplicativo pode adotar a convenção de usar o nome do componente como o valor da cadeia de caracteres de consulta. No exemplo a seguir, a cadeia de caracteres de consulta é "counter" porque essa referência de componente PageScript é colocada em um componente Counter. Essa é apenas uma sugestão e você pode usar qualquer esquema de cadeia de caracteres de consulta que preferir.

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

Para monitorar alterações em elementos específicos do DOM, use o padrão MutationObserver no JS no cliente. Para obter mais informações, confira Interoperabilidade ASP.NET Core Blazor JavaScript (interoperabilidade JS).

Implementação de exemplo sem usar uma RCL

A abordagem descrita neste artigo pode ser implementada diretamente em um aplicativo Web Blazor sem usar uma RCL (biblioteca de classes) Razor. Para ver um exemplo, confira Habilitar a geração de código QR em aplicativos autenticadores TOTP em um aplicativo Web Blazor do ASP.NET Core.