Skip to content

Documentation

Trimming and lazy scripts

Publish only .NET 8, 9 and 10 Core, Extras, Legacy, Icons

An app's users download the CSS and JavaScript of the components the app uses, not the whole library. At publish, each bundle is put back together from only the pieces the app can reach; with lazy scripts, each page also loads only the scripts of what it renders, the first time it uses them.

At a glance

The packages ship their CSS and JavaScript as bundles an app links once, and beside them the pieces those bundles are made of.

Every bundle is built from one piece per component stylesheet, per TypeScript file and per icon glyph. When an app is published, each bundle is put back together from only the pieces the app can use - under the same name and URL, so the <link> and <script> tags, @Assets[""], the import map and cache busting all keep working. A build - and dotnet run and dotnet watch, which run on one - is never touched: what you develop and debug against is always the full set.

What Files Trimmed to
Scripts bit.blazorui.js, bit.blazorui.extras.js, bit.blazorui.legacy.js The scripts the app's code can call, the scripts those use, and the few every app needs
Stylesheets bit.blazorui.css and its fluent, fluent-dark and fluent-light variants, bit.blazorui.extras.css, bit.blazorui.legacy.css The stylesheets of the components the app can render, around the bundle's own general rules and theme
Icons bit.blazorui.icons.css (Bit.BlazorUI.Icons) The glyphs whose names the app's code holds - only when asked for, see Icons
Vendor files Legacy: pdf.js, quilljs, chart.js, marked; Extras: leaflet, openlayers, flags Left out of the publish wherever nothing the app can run loads them
Lazy script modules scripts/modules/*.js of each package, with lazy scripts on The modules the app can import, see Lazy scripts

Never trimmed: the Extras' preset stylesheets (bit.blazorui.fluent2.css, bit.blazorui.material.css, bit.blazorui.cupertino.css), which hold tokens rather than components, the icon fonts, and Bit.BlazorUI.Assets. The build log says what came out:

Bit.BlazorUI: Bit.BlazorUI/scripts/bit.blazorui.js -> 19 of 53 pieces, 377,287 of 990,599 bytes (-62%).
Bit.BlazorUI: Bit.BlazorUI/styles/bit.blazorui.css -> 6 of 88 pieces, 221,789 of 1,359,262 bytes (-84%).
Bit.BlazorUI: _content/Bit.BlazorUI.Legacy/pdf.js/ -> left out (2 files): nothing this app can run loads it.

Turning it on

Where the .NET trimmer runs, it is on already. Elsewhere it is one property in the project you publish.

Your app What to do
Standalone WebAssembly Nothing: published trimmed by default, so the trimming is on and works off what the .NET trimmer kept.
Any project with PublishTrimmed Nothing: the .NET trimmer running turns it on the same way - except on a server hosting a WebAssembly client, whose trimmer knows the server alone (see From the .NET trimmer).
Blazor Web App, Blazor Server Turn it on in the server project: <BitBlazorUITrimAssets>true</BitBlazorUITrimAssets>. The publish then reads your assemblies for what they use.
Blazor Hybrid (MAUI, WPF, WinForms) Not trimmed: the files are read from the device, never downloaded. Lazy scripts still choose which shape of the scripts the app carries.
Razor class library Nothing: the trimming runs in the project that publishes the app, which is the only one that knows what the app uses.
<!-- The server project of a Blazor Web App, or a Blazor Server app -->
<PropertyGroup>
    <BitBlazorUITrimAssets>true</BitBlazorUITrimAssets>
</PropertyGroup>

To publish the full bundles, set it to false - or, for one publish, pass -p:BitBlazorUITrimAssets=false on the command line, which is also the quickest way to confirm whether a problem seen only in production comes from the trimming.

Switches

They decide whether to trim, one kind of file each. What to trim to is the next section's question.

Property Default Trims
BitBlazorUITrimAssets true where the .NET trimmer runs, false elsewhere The default of the three below.
BitBlazorUITrimScripts BitBlazorUITrimAssets The script bundles - and with lazy scripts, the modules and the boot modules.
BitBlazorUITrimStyles BitBlazorUITrimAssets The stylesheets, every Fluent variant alike.
BitBlazorUITrimVendorAssets BitBlazorUITrimAssets The vendor folders nothing the app can run loads. This is the size of the publish, not of a page: a vendor file is never downloaded unless something asks for it.
BitBlazorUITrimIcons false, whatever the others say bit.blazorui.icons.css, down to the glyphs the code names.
<PropertyGroup>
    <BitBlazorUITrimAssets>true</BitBlazorUITrimAssets>             <!-- the default of the next three -->
    <BitBlazorUITrimScripts>true</BitBlazorUITrimScripts>
    <BitBlazorUITrimStyles>true</BitBlazorUITrimStyles>
    <BitBlazorUITrimVendorAssets>true</BitBlazorUITrimVendorAssets> <!-- pdf.js, quill, leaflet, flags... -->
    <BitBlazorUITrimIcons>true</BitBlazorUITrimIcons>               <!-- off unless set -->
</PropertyGroup>

How it knows what the app uses

The switches decide whether to trim; these signals are what it trims to. With none of them in play, nothing is trimmed - never trimmed to nothing.

From the .NET trimmer

The most precise signal there is. Where the publish is trimmed (PublishTrimmed), the types ILLink left in the trimmed Bit.BlazorUI*.dll are the components the app can render, and the interop identifiers left in them the scripts it can call. It then supersedes the scan below - the same question, better answered - and a BitBlazorUIScan written out in the project is said to be unused in the build output.

Two exceptions. A server that hosts a WebAssembly client (the server of a Blazor Web App, whose static web assets carry the client's runtime) is answered by the scan instead: its trimmed assemblies are the server's, and say nothing of a component the client renders without prerendering it - and its PublishTrimmed does not turn the trimming on by itself. And a publish set up to be trimmed whose packages were not trimmed keeps the full bundles.

From your assemblies

Where ILLink does not trim the publish - a Blazor Server app, a Blazor Web App's server, a WebAssembly app with PublishTrimmed off - the publish reads the app's own assemblies for the bit BlazorUI types they reference (a <BitButton> in a .razor file is a reference to Bit.BlazorUI.BitButton), and the untrimmed library assemblies for everything those types reach in turn. BitBlazorUIScan says how:

  • TypeReferences - the default wherever a switch is on. Reads each assembly's type references, which name the namespace too: the precise mode.
  • TypeNames - matches the library's type names against every name each assembly holds, without reading the reference tables. Coarser: an app with a class of its own called BitButton keeps that component too. It over-includes, never misses.
  • None - reads nothing, and the full set is published - unless <BitBlazorUIComponent> items name what the app renders.

The assemblies read are the project's own and everything copied beside it, so the server of a Blazor Web App, which references its WebAssembly client, covers both. The platform's own assemblies (signed with Microsoft's or the .NET Foundation's keys) and the library's are passed over. When none of the assemblies read references the packages, they were not the app's: the publish keeps everything and says so (BITBLAZORUI002) rather than trim to nothing.

<PropertyGroup>
    <BitBlazorUIScan>TypeNames</BitBlazorUIScan> <!-- TypeReferences (default), TypeNames or None -->
</PropertyGroup>

What a component brings with it

  • Its stylesheet, and the stylesheets of the components it renders - a stylesheet is kept for the component of its file name, and the parts of a component family (the many BitFc* types of BitFullCalendar) keep theirs.
  • The scripts its code calls: every interop identifier ("BitBlazorUI.Callouts.toggle") keeps the script exporting it, with every script that one uses - across packages too, an Extras script keeping the core scripts it calls.
  • The scripts it owns: a few scripts act on a component's markup without being called - the keyboard handling of BitNav, BitNavBar, BitButtonGroup, BitTag, BitTimeline, BitAppShell and BitDataGrid, the providers of BitMap - and are kept wherever that component is.
  • The scripts its markup needs: a script acting on what markup says is kept wherever the app's code or markup says it - the theme on the bit-theme attributes, the claim of the Escape key on data-bit-esc, the arguments of the animation and transition events on onanimation* / ontransition*.
  • The scripts every app needs: the version stamp and the keys every component relies on (the core's general and Utils.Keys, the Extras' general), kept even in an app that renders nothing.

Registering the services keeps nothing: AddBitBlazorUIServices() renders nothing, so it is not followed, and the modal and snack bar of an app that never uses them are left out. An app that injects a service - @inject BitModalService - names it, and that keeps what the service renders.

What your own files say

Whatever the signal, the app's own static markup and scripts - the .html, .htm, .js and .mjs static web assets of its projects, not the framework's under _framework - are read as text, and so are the string literals of its code. Their text counts like code of the app's own:

  • an <html bit-theme-system> in a WebAssembly app's index.html keeps the theme's script;
  • a class="bit-icon bit-icon--Add" keeps that glyph;
  • a script of the app's own calling BitBlazorUI.PageVisibility.dispose(...) keeps that script - read however it is reached: window.BitBlazorUI..., globalThis.BitBlazorUI..., through optional chaining (BitBlazorUI.Theme?.onChange), or the namespace itself (const u = BitBlazorUI.Utils, which keeps every script adding to it).

A file the static web assets do not hold - a host page or a script outside the app's projects - is named with a <BitBlazorUIScanFile> item (next section).

The app's own stylesheets are not read. So an app that names theming nowhere - no bit-theme attribute in its markup, no theming API in its code - is published without the theme's script, and without the bit-theme="light" that script writes on <html> as it loads. The page still paints the light theme, which the stylesheets put on a bare :root, but a rule of the app's own selecting [bit-theme="light"] matches nothing: write the attribute on <html> yourself, which keeps the script as well, or keep it with <BitBlazorUIAsset Include="BitTheme" />.

With nothing to go on

No trimmed publish, the scan set to None and no component named: the full bundles are published. Names of scripts, stylesheets or icons alone trim nothing either - a list of glyphs says nothing of the components an app renders - while <BitBlazorUIComponent> items are a signal of their own: the whole of what the app renders.

Keeping what the publish cannot see

What no signal can follow - a component rendered by reflection, a script called from somewhere the publish does not read, an icon named in data - you name in the project.

<ItemGroup>
    <BitBlazorUIComponent Include="BitChart;BitDataGrid" />    <!-- a component, with everything it needs -->
    <BitBlazorUIAsset Include="Callouts;BitButton" />         <!-- a script or a stylesheet, by name -->
    <BitBlazorUIIcon Include="Add;Delete" />                  <!-- a glyph, with BitBlazorUITrimIcons on -->
    <BitBlazorUIScanFile Include="..\Host\wwwroot\index.html" /> <!-- a file to read like the app's own -->
</ItemGroup>
Item Keeps
BitBlazorUIComponent A type of the packages, by its name without generic arity (BitDropdown) - for a component rendered by reflection or through a DynamicComponent built from a type name. Everything it reaches comes with it: its stylesheet, its scripts, the components it renders.
BitBlazorUIAsset A script by its TypeScript file's name (Callouts, BitTheme, Utils.Escape), with every script it uses; or a component stylesheet by its .scss file's name (BitButton), alone - for markup of your own using a component's classes without the component. Name a component in BitBlazorUIComponent when you want everything it needs. The names are in each package's buildTransitive/bit-assets/scripts.json and styles.json.
BitBlazorUIIcon A glyph of Bit.BlazorUI.Icons, with BitBlazorUITrimIcons on - the icon names that come from data. With the switch off the stylesheet is published whole, and the publish says in a line of its own that the items kept nothing.
BitBlazorUIScanFile Nothing by itself: a text file read like the app's own markup (a host page or a script the static web assets do not hold).
BitBlazorUIScanAssembly Which assemblies the scan reads, instead of the project's own and its copy-local references - set it only when that is not where the code using the packages lives.
BitBlazorUILibraryAssembly The untrimmed Bit.BlazorUI*.dll the scan and the component names are read against, for a layout where the copy-local lookup comes back empty or ambiguous - rather than turning the feature off.

A name that is nothing of the packages fails the publish rather than being ignored: a misspelled component would otherwise be one left out, found only in production.

Icons

Trimmed only when you ask for it, because an app can read icon names out of data, where no code names them.

<PropertyGroup>
    <BitBlazorUITrimIcons>true</BitBlazorUITrimIcons>
</PropertyGroup>

The glyphs kept are the ones whose names the app's code and markup hold - BitIconName.Add is a constant, so its value is a string literal of your own code - whether a name stands alone, after the class prefix (bit-icon--Add), or as one word of a longer string. An app whose menu comes from a database, or that lets users pick an icon, names those with <BitBlazorUIIcon>, or leaves the switch off. The @font-face and the font files stay either way. Icons named without Bit.BlazorUI.Icons referenced are a warning (BITBLAZORUI003).

Vendor libraries and files

Libraries and images a component loads at run time from its package's static web assets.

  • Bit.BlazorUI.Legacy - pdf.js (BitPdfReaderLegacy), quilljs (BitRichTextEditorLegacy), chart.js (BitChartLegacy), marked (the legacy markdown components).
  • Bit.BlazorUI.Extras - leaflet and openlayers (BitMap's providers), flags (BitFlag's images).

Each folder is kept wherever something the app can run names it - _content/Bit.BlazorUI.Legacy/pdf.js/ is a literal of the code loading it - and wherever the app's own markup links a file of it. Otherwise the whole folder, its compressed copies and its endpoints are left out of the publish (BitBlazorUITrimVendorAssets).

Lazy scripts

No script tag of the packages at all, and each page downloads the scripts of the components it actually uses, the first time it uses them.

Turning them on

Besides the bundles, every script also ships as an ES module of its own. Turn the lazy scripts on in the project you publish, and remove the <script> tags of the packages from your host page - the stylesheets stay:

<PropertyGroup>
    <BitBlazorUILazyScripts>true</BitBlazorUILazyScripts>
</PropertyGroup>
<!-- No longer needed: -->
<script src="_content/Bit.BlazorUI/scripts/bit.blazorui.js"></script>
<script src="_content/Bit.BlazorUI.Extras/scripts/bit.blazorui.extras.js"></script>
<script src="_content/Bit.BlazorUI.Legacy/scripts/bit.blazorui.legacy.js"></script>

The property drops the bundles from the app's static web assets and turns the mode on at runtime, through the Bit.BlazorUI.LazyScripts runtime switch and a one-line module initializer compiled into the project (runtime switches only reach a .NET 9 WebAssembly app and later; the initializer is what makes it hold on .NET 8, and needs C# 9 or later). Without the property, the modules, the boot modules and the initializers are dropped instead, so a bundle-mode app carries none of them.

How a page loads them

  • As the app starts, Blazor imports each package's JavaScript initializer (scripts/Bit.BlazorUI.lib.module.js), which imports the package's boot module: the scripts that act on the page without being called - the theme, the claim of the Escape key, the arguments of the animation and transition events, the keys every component relies on, and the Extras' window-size variables. This runs before Blazor renders anything, on a statically rendered page too.
  • The first time a component calls into a script, the library imports its module (scripts/modules/Callouts.js), which imports the scripts of its package it uses; a script of the Extras using the core's has those imported first. Each module is imported once per WebAssembly app, Server circuit or WebView page, concurrent first calls share one import, and a failed import - a dropped connection, a 404 during a deploy - is tried again on the next call.
  • A component owning a script that is called by nothing - the keyboard handling of BitNav, BitNavBar, BitButtonGroup, BitTag, BitTimeline, BitAppShell, BitDataGrid - loads it as it first renders what the script acts on: at once for most, once it is dismissible for a BitTag and once an item is clickable for a BitTimeline, so a plain tag or a read-only timeline downloads nothing. A script that fails to import (a lost connection, a 404 mid-deploy) costs the component those keys, never its render, and is imported again the next time it is asked for.
  • A clean-up call into a script the page never ran - a dropdown disposed without ever opening - is skipped rather than downloading the script to release nothing.
  • A synchronous call of WebAssembly imports its module before it runs.

A trimmed lazy publish

The lazy scripts are trimmed the way the bundles are, from the same answer: only the modules of the scripts the app can reach are published, and the boot modules are written again with the boot scripts it keeps - an app that never themes anything does not boot the theme's script. The initializers are never changed (Blazor's own list names them by their fingerprint), and import the rewritten boot module. <BitBlazorUIIncludeScriptModules>true</BitBlazorUIIncludeScriptModules> written out in the project publishes every module instead.

Bit.BlazorUI: Bit.BlazorUI lazy script modules -> 19 of 53 (34 left out: nothing this app can run imports them).
Bit.BlazorUI: Bit.BlazorUI/scripts/bit.blazorui.boot.js -> 3 of 5 pieces, 101 of 163 bytes (-38%).

Bundles or lazy scripts

Bundles (default) Lazy scripts
Downloaded Every script the app can reach, up front The scripts of what a page uses, on first use
Requests One per package One per script, the first time it is used, plus the initializers and boot modules
Host page A <script> tag per package No tag
Theme on first paint As soon as the tag runs As Blazor starts - add the boot module to <head> for sooner (below)
Static pages Every script of the bundle The boot scripts; everything else needs an interactive component to call it

At runtime

The same switch, for a host where the project cannot say it - it decides how the scripts load, never what is published:

builder.Services.AddBitBlazorUIServices(options =>
{
    options.LazyScripts = true;    // or false to insist on the bundles
    options.ContentPath = "https://cdn.example.com/_content/"; // only if the packages' assets are served elsewhere
    options.TrySingleton = false;  // the trySingleton argument of the plain overload
});

// Or directly:
BitBlazorUIScripts.UseLazyScripts();          // optionally with a content path
BitBlazorUIScripts.UseBundledScripts();
var lazy = BitBlazorUIScripts.LazyScriptsEnabled;

These are process-wide toggles, shared by every circuit of a Blazor Server app and meant to be set once at startup, before any component renders. ContentPath is the folder holding Bit.BlazorUI/ and the other packages' folders: a root-relative path or an absolute URL is taken as given, anything else is relative to the base href.

Keeping both shapes

An app choosing the mode at runtime keeps both shapes, and renders its tags only for the bundles. A page that loads a package's bundle never runs that package's modules a second time: the bundle says it ran, and the initializer and every module leave their work to it - as long as the tag runs before Blazor's script starts the app, as it does after blazor.web.js without defer.

<PropertyGroup>
    <BitBlazorUIIncludeScriptBundle>true</BitBlazorUIIncludeScriptBundle>
    <BitBlazorUIIncludeScriptModules>true</BitBlazorUIIncludeScriptModules>
</PropertyGroup>
@if (BitBlazorUIScripts.LazyScriptsEnabled is false)
{
    <script src="_content/Bit.BlazorUI/scripts/bit.blazorui.js"></script>
}

Things to know

  • The theme on the very first paint: import the boot module from <head>, and Blazor's initializer finds it loaded: <script type="module" src="_content/Bit.BlazorUI/scripts/bit.blazorui.boot.js"></script>.
  • A script of your own calling BitBlazorUI.* imports the module it calls first: await import('./_content/Bit.BlazorUI/scripts/modules/PageVisibility.js').
  • A page rendered statically, with no interactive component, runs the boot scripts only - the keyboard handling a component loads as it renders needs it to be interactive.
  • Blazor Hybrid loads the boot module from .NET too, before the first module of a package, so a host that runs no initializer still gets it.
  • Assets on a CDN: set ContentPath to the folder holding the packages' folders.

URLs, fingerprints and caching

A trimmed file replaces the full one at the same URL, defined from its own content.

  • The trimmed bundle keeps the package asset's base and relative path, so it is served where the full one was and the tags stay as they are.
  • Its fingerprint and integrity are computed from the trimmed content, and its compressed .br / .gz copies are derived from it - the full bundle's leave with it.
  • On .NET 9 and later its endpoints carry the label _content/Bit.BlazorUI/..., so @Assets["_content/Bit.BlazorUI/scripts/bit.blazorui.js"] and <ImportMap /> resolve to the trimmed file; the publish fails rather than leave a lookup pointing nowhere.
  • Service-worker asset manifests are computed after the swap, so an offline-capable app caches the trimmed files.
  • The cache busting of Getting started works unchanged.

Projects in a solution

The trimming runs in the one project whose reference closure is the app.

It runs in the project that publishes the app's static web assets - one using the Web SDK or a WebAssembly SDK - and stands down everywhere else: a Razor class library asked for its publish assets would trim against its own references, which are not the app's, and a MAUI hybrid head packages its files at build. The client project of a Blazor Web App, published inside its server's publish, is left to the server, which reads the client's assembly among its references: the client trims nothing there, and says so in its build output.

So set the properties and items on the project you publish, not in a shared Directory.Build.props: from there they reach every class library too, and a project that does not publish an app says so in its build output when a scan or a name landed in it. The switches themselves - BitBlazorUITrimAssets, BitBlazorUILazyScripts - are safe to share.

Build output, warnings and errors

What a publish says about the trimming, and what stops it.

Each trimmed file is one line at normal verbosity: the pieces kept and the bytes saved. At detailed verbosity (-v:d) the publish also lists the scripts, stylesheets and icons it kept, and the assemblies the scan read.

Warning Means
BITBLAZORUI001 A script uses a function of another package that no package of the app exports: the packages are of different versions. Reference the same version of each.
BITBLAZORUI002 None of the assemblies the scan read references the packages, so everything was kept. Add <BitBlazorUIScanAssembly> items for the app's assemblies, or set the scan to None.
BITBLAZORUI003 <BitBlazorUIIcon> items are given, but the app does not reference Bit.BlazorUI.Icons.
<PropertyGroup>
    <NoWarn>$(NoWarn);BITBLAZORUI002</NoWarn>
</PropertyGroup>

The publish fails - rather than publish something that breaks later - on a <BitBlazorUIComponent>, <BitBlazorUIAsset> or <BitBlazorUIIcon> naming nothing of the packages, a BitBlazorUIScan value that is not a mode, a scan written out in the project that cannot find the untrimmed library assemblies (or finds two of one: set <BitBlazorUILibraryAssembly>), and an endpoint label it could not restore.

Troubleshooting

Everything here shows up in a published app only - publish with -p:BitBlazorUITrimAssets=false first, to confirm the trimming is what you are looking at.

A component is unstyled in production, and fine in development

The publish could not see the app use it: a component rendered by reflection or through a DynamicComponent, or classes of a component in markup of your own. Name it with <BitBlazorUIComponent Include="..." /> - or the stylesheet alone with <BitBlazorUIAsset Include="..." />.

BitBlazorUI.x is undefined in the browser console

A script was left out: something calls it from where the publish cannot read - JavaScript loaded from a CDN, or built outside the app's projects. Name the script with <BitBlazorUIAsset Include="..." />, or the file with <BitBlazorUIScanFile Include="..." />. With lazy scripts, a script of your own imports the module it calls first.

Icons come out as empty boxes after a publish

BitBlazorUITrimIcons is on and the names come from data. List them with <BitBlazorUIIcon Include="..." />, or turn the switch off.

A 404 for bit.blazorui.js with lazy scripts on

The host page still has the packages' <script> tags, and the publish no longer has the bundles. Remove the tags, or keep both shapes (see Keeping both shapes).

Nothing was trimmed

No signal was in play: a server app needs BitBlazorUITrimAssets, the scan may be set to None, the project may not be the one that publishes the app (its build output says so), or the scan read no assembly of the app (BITBLAZORUI002). And a build, dotnet run and dotnet watch are never trimmed.

A Blazor Web App ignores the settings of its client project

The client's publish is part of the server's, which is the one that publishes the app's static web assets: set the properties and items on the server project, which reads the client's assembly among its references. The client says so at normal verbosity ("this WebAssembly client is published by the server hosting it").

Reference

Every property and item, in one place.

Property Values Default
BitBlazorUITrimAssets true | false true in a WebAssembly project and wherever PublishTrimmed is (but on a server hosting a WebAssembly client), else false
BitBlazorUITrimScripts true | false BitBlazorUITrimAssets
BitBlazorUITrimStyles true | false BitBlazorUITrimAssets
BitBlazorUITrimVendorAssets true | false BitBlazorUITrimAssets
BitBlazorUITrimIcons true | false false
BitBlazorUIScan TypeReferences | TypeNames | None TypeReferences wherever a switch is on, else None; not used where the publish is trimmed
BitBlazorUILazyScripts true | false false
BitBlazorUIIncludeScriptBundle true | false The opposite of BitBlazorUILazyScripts
BitBlazorUIIncludeScriptModules true | false BitBlazorUILazyScripts; written out as true, every module is published through a trimmed publish
Item Include
BitBlazorUIComponent Type names of the packages
BitBlazorUIAsset Script names (TypeScript file names) and component stylesheet names (.scss file names)
BitBlazorUIIcon Glyph names of Bit.BlazorUI.Icons
BitBlazorUIScanFile Paths of text files to read
BitBlazorUIScanAssembly Paths of the app's assemblies
BitBlazorUILibraryAssembly Paths of the untrimmed Bit.BlazorUI*.dll

And at runtime: AddBitBlazorUIServices(options => ...) with LazyScripts, ContentPath and TrySingleton, and BitBlazorUIScripts with UseLazyScripts(contentPath), UseBundledScripts(), LazyScriptsEnabled and ContentPath.

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.