Compartir vía


ASP.NET Core Blazor JavaScript con representación estática del lado servidor (SSR estática)

En este artículo se explica cómo cargar JavaScript (JS) en una Blazor Web App con representación estática del lado servidor (SSR estática) y navegación mejorada.

Algunas aplicaciones dependen de JS para realizar tareas de inicialización específicas de cada página. Cuando se usa la característica de navegación mejorada de Blazor, que permite al usuario evitar volver a cargar toda la página, es posible que el JS específico de la página no se vuelva a ejecutar según lo esperado cada vez que se produzca una navegación de página mejorada.

Para evitar este problema, no se recomienda confiar en elementos específicos <script> de página colocados fuera del archivo de diseño aplicado al componente. En su lugar, los scripts deben registrar un afterWebStartedJS inicializador para realizar la lógica de inicialización y utilizar un detector de eventos (blazor.addEventListener("enhancedload", callback)) para escuchar las actualizaciones de la página causadas por la navegación mejorada.

En el ejemplo siguiente se muestra una manera de configurar el código JS para que se ejecute cuando se carga o actualiza inicialmente una página representada estáticamente con navegación mejorada.

El siguiente ejemplo de componente PageWithScript es un componente de la aplicación que requiere que los scripts se ejecuten con SSR estático y navegación mejorada. En el siguiente ejemplo de componente se incluye un componente PageScript de una biblioteca de clases (RCL) Razor que se agrega a la solución más adelante en este artículo.

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.

En la Blazor Web App, agrega el siguiente archivo JS colocado:

  • onLoad se llama cuando se agrega el script a la página.
  • onUpdate se llama cuando el script todavía existe en la página después de una actualización mejorada.
  • onDispose se llama cuando se quita el script de la página después de una actualización mejorada.

Components/Pages/PageWithScript.razor.js:

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

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

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

En una Razor biblioteca de clases (RCL) (la RCL de ejemplo se denomina BlazorPageScript), agregue el siguiente módulo.

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

En la RCL, agregue el siguiente componente PageScript.

PageScript.razor:

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

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

El componente PageScript funciona normalmente en el nivel superior de una página.

Si coloca el componente PageScript en el diseño de una aplicación (por ejemplo, MainLayout.razor), lo que da como resultado un PageScript compartido entre las páginas que usan el diseño, el componente solo se ejecuta onLoad después de que se vuelva a cargar una página completa y onUpdate cuando se produzca cualquier actualización de página mejorada, incluida la navegación mejorada.

Para volver a usar el mismo módulo entre páginas, pero tiene el onLoad y onDispose devoluciones de llamada invocadas en cada cambio de página, anexe una cadena de consulta al final del script para que se reconozca como un módulo diferente. Una aplicación podría adoptar la convención de usar el nombre del componente como valor de cadena de consulta. En el ejemplo siguiente, la cadena de consulta es "counter" porque esta referencia de componente PageScript se coloca en un componente de Counter. Esto es simplemente una sugerencia y puede usar cualquier esquema de cadena de consulta que prefiera.

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

Para supervisar los cambios en elementos DOM específicos, use el patrón MutationObserver en JS en el cliente. Para más información, vea Interoperabilidad de JavaScript en Blazor de ASP.NET Core (interoperabilidad de JS).

Implementación de ejemplo sin usar una RCL

El enfoque descrito en este artículo se puede implementar directamente en una Blazor Web App sin usar una biblioteca de clases (RCL) Razor. Por ejemplo, consulta Habilitar la generación de código QR para aplicaciones de autenticador TOTP en ASP.NET Core Blazor Web App.