Dela via


ASP.NET Core Blazor JavaScript med statisk återgivning på serversidan (statisk SSR)

Notera

Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Den här artikeln beskriver hur du laddar in JavaScript (JS) i en Blazor Web App med statisk serverbaserad rendering (statisk SSR) och förbättrad navigation.

Vissa appar är beroende av JS för att utföra initieringsuppgifter som är specifika för varje sida. När du använder Blazor:s förbättrade navigeringsfunktion, som gör att användaren kan undvika att läsa in hela sidan igen, kan sidspecifika JS inte köras som förväntat varje gång en förbättrad sidnavigering sker.

För att undvika det här problemet rekommenderar vi inte att du förlitar oss på sidspecifika <script> element som placeras utanför layoutfilen som tillämpas på komponenten. Skript bör i stället registrera en afterWebStartedJS initierare för att utföra initieringslogik och använda en händelselyssnare för att lyssna efter siduppdateringar som orsakas av förbättrad navigering.

Evenemang

I följande exempel på händelselyssnare är platshållaren {CALLBACK} återanropsfunktionen.

  • Förbättrad navigeringsstart (enhancednavigationstart) utlöser ett återanrop innan en förbättrad navigering sker:

    blazor.addEventListener("enhancednavigationstart", {CALLBACK});
    
  • Förbättrad navigeringsslut (enhancednavigationend) utlöser ett återanrop efter att en förbättrad navigering har inträffat:

    blazor.addEventListener("enhancednavigationend", {CALLBACK});
    
  • Förbättrad sidinläsning vid navigering (enhancedload) utlöser ett återanrop varje gång sidan uppdateras på grund av förbättrad navigering, inklusive strömmande uppdateringar:

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

För att undvika det här problemet rekommenderar vi inte att du förlitar oss på sidspecifika <script> element som placeras utanför layoutfilen som tillämpas på komponenten. Skript bör i stället registrera en afterWebStartedJS initierare för att utföra initieringslogik och använda en händelselyssnare för att lyssna efter siduppdateringar som orsakas av förbättrad navigering:

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

I föregående exempel är platshållaren {CALLBACK} återanropsfunktionen.

Exempel på utökat sidinläsningsskript

I följande exempel visas ett sätt att konfigurera JS kod som ska köras när en statiskt renderad sida med förbättrad navigering ursprungligen läses in eller uppdateras.

Följande PageWithScript komponentexempel är en komponent i appen som kräver att skript körs med statisk SSR och förbättrad navigering. Följande komponentexempel innehåller en PageScript komponent från ett Razor klassbibliotek (RCL) som läggs till i lösningen senare i den här artikeln.

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.

I Blazor Web Applägger du till följande sorterade JS fil:

  • onLoad anropas när skriptet läggs till på sidan.
  • onUpdate anropas när skriptet fortfarande finns på sidan efter en förbättrad uppdatering.
  • onDispose anropas när skriptet tas bort från sidan efter en förbättrad uppdatering.

Components/Pages/PageWithScript.razor.js:

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

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

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

I en Razor-klassbibliotek (RCL) (exemplet RCL heter BlazorPageScript) lägger du till följande modul, som är en JS initierare.

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

Lägg inte till en <script>-tagg i appens rotkomponent, som vanligtvis är App-komponenten, för BlazorPageScript.lib.module.js eftersom modulen i det här fallet är en JS initierare (afterWebStarted). JS initierare identifieras och läses in automatiskt av Blazor ramverket.

I RCL lägger du till följande PageScript komponent.

PageScript.razor:

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

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

Den PageScript komponenten fungerar normalt på den översta nivån på en sida.

Om du placerar PageScript komponenten i en apps layout (till exempel MainLayout.razor), vilket resulterar i en delad PageScript mellan sidor som använder layouten, körs komponenten bara onLoad efter en fullständig sidbelastning och onUpdate när en förbättrad siduppdatering sker, inklusive förbättrad navigering.

Om du vill återanvända samma modul mellan sidor, men har onLoad och onDispose återanrop som anropas vid varje sidändring, lägger du till en frågesträng i slutet av skriptet så att den identifieras som en annan modul. En app kan använda konventionen om att använda komponentens namn som frågesträngsvärde. I följande exempel är frågesträngen "counter" eftersom den här PageScript komponentreferensen placeras i en Counter komponent. Det här är bara ett förslag och du kan använda det frågesträngsschema som du föredrar.

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

Om du vill övervaka ändringar i specifika DOM-element använder du MutationObserver-mönstret i JS på klienten. Mer information finns i ASP.NET Core Blazor JavaScript-samverkan (JS interop).

Implementering utan att använda en RCL

Metoden som beskrivs i den här artikeln kan implementeras direkt i en Blazor Web App utan att använda ett Razor klassbibliotek (RCL) genom att flytta tillgångarna i RCL:en till appen. Det är dock praktiskt att använda en RCL eftersom RCL kan paketeras i ett NuGet-paket för förbrukning av Blazor appar i en organisation.

Om du flyttar tillgångarna till en Blazor Web Appmåste du byta namn på modulen (BlazorPageScript.lib.module.js) så att den matchar appen enligt filnamnsreglerna för JS initierare. Om filen inte namnges korrekt kan Blazor inte identifiera och läsa in modulen, och händelsen afterWebStarted körs inte automatiskt när appen startas.