Compartilhar via


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

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como carregar JavaScript (JS) em um Blazor Web App com a SSR estática (renderização estática do lado servidor) 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 afterWebStartedJS inicializador para executar a lógica de inicialização e usar um ouvinte de eventos para escutar atualizações de página causadas pela navegação aprimorada.

Eventos

Nos exemplos de ouvinte de eventos a seguir, o {CALLBACK} espaço reservado é a função de retorno de chamada.

  • O início de navegação aprimorado (enhancednavigationstart) aciona um retorno de chamada antes que o processo de navegação aprimorado ocorra:

    blazor.addEventListener("enhancednavigationstart", {CALLBACK});
    
  • O final de navegação aprimorada (enhancednavigationend) dispara um retorno de chamada depois que ocorre uma navegação aprimorada:

    blazor.addEventListener("enhancednavigationend", {CALLBACK});
    
  • O carregamento da página de navegação aprimorada (enhancedload) aciona um retorno de chamada sempre que a página é atualizada devido a uma navegação aprimorada, incluindo atualizações de streaming:

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

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 afterWebStartedJS inicializador para executar a lógica de inicialização e usar um ouvinte de eventos para escutar atualizações de página causadas pela navegação aprimorada:

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

No exemplo anterior, o espaço reservado {CALLBACK} é a função retorno de chamada.

Exemplo de script de carregamento de página aprimorado

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 Blazor Web App, adicione o arquivo JS colocado 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 Blazor Web App sem usar uma RCL (biblioteca de classes do Razor). Para ver um exemplo, confira Habilitar a geração de código QR em aplicativos autenticadores TOTP em um Blazor Web App do ASP.NET Core.