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 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 inicializador afterWebStarted
JS 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 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.