Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
825d8af
apply Bswup improvements #12408
msynk May 31, 2026
c646784
further improvements
msynk Jun 1, 2026
733a05a
resolve review comments
msynk Jun 2, 2026
de9c330
resolve review comments II
msynk Jun 4, 2026
23b29b3
resolve review comments III
msynk Jun 5, 2026
088c6d1
resolve review comments IV
msynk Jun 6, 2026
c161919
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 6, 2026
8c11506
resovle review comments V
msynk Jun 6, 2026
ea3d330
resolve review comments VI
msynk Jun 6, 2026
a71d04f
remove json serializer
msynk Jun 6, 2026
ddd6829
fix progress component
msynk Jun 6, 2026
9d73fbf
resolve local review findimgs
msynk Jun 14, 2026
6c96a71
Merge branch '12408-bswup-improvements' of https://github.com/msynk/b…
msynk Jun 14, 2026
ce3cd61
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 16, 2026
5377236
resolve review comments V
msynk Jun 21, 2026
a450c91
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 21, 2026
d6f1bd1
resolve review comments VI
msynk Jun 22, 2026
bd800d8
resolve review comments VII
msynk Jun 27, 2026
3623e25
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 27, 2026
754eb63
resolve review comments VIII
msynk Jun 27, 2026
70d385e
fix build issues
msynk Jun 28, 2026
a1ee5c5
resolve review comments IX
msynk Jun 28, 2026
f92cdaa
resolve review comments X
msynk Jun 28, 2026
20addce
resolve review comments XI
msynk Jun 28, 2026
73401e9
fix variable definition issue
msynk Jun 28, 2026
7721833
restructure demo projects
msynk Jun 29, 2026
3e6197a
improve FullDemo
msynk Jun 30, 2026
021176a
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 30, 2026
571f537
resolve review comments XII
msynk Jul 3, 2026
968fc99
resolve review comments XIII
msynk Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Bswup/Bit.Bswup.Demo/Pages/HomePage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<PageTitle>Home</PageTitle>

<h1>222</h1>
<h1>111</h1>
Comment thread
msynk marked this conversation as resolved.

<h1>Hello, world!</h1>

Expand Down
4 changes: 3 additions & 1 deletion src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

self.assetsExclude = [/\.scp\.css$/, /weather\.json$/];
self.caseInsensitiveUrl = true;
self.precachedAssetsInclude = [/favicon\.ico$/, /icon-512\.png$/, /bit-bw-64\.png$/];

self.externalAssets = [
{
"url": "not-found/script.file.js"
}
];
// 'lax' opts into best-effort installs: the demo intentionally references a non-existent
// asset to exercise the progress / error reporting UI. Under the default 'strict' setting
// that would abort the install. See README.md > errorTolerance.
self.errorTolerance = 'lax';

self.importScripts('_content/Bit.Bswup/bit-bswup.sw.js');
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

self.assetsExclude = [/\.scp\.css$/, /weather\.json$/];
self.caseInsensitiveUrl = true;
self.precachedAssetsInclude = [/favicon\.ico$/, /icon-512\.png$/, /bit-bw-64\.png$/];

//self.externalAssets = [
// {
Expand Down
8 changes: 4 additions & 4 deletions src/Bswup/Bit.Bswup.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<File Path="README.md" />
</Folder>
<Folder Name="/FullDemo/">
<Project Path="FullDemo/Client/Bit.Bswup.Demo.Client.csproj" />
<Project Path="FullDemo/Server/Bit.Bswup.Demo.Server.csproj" />
<Project Path="FullDemo/Client/Bit.Bswup.FullDemo.Client.csproj" />
<Project Path="FullDemo/Server/Bit.Bswup.FullDemo.Server.csproj" />
</Folder>
<Folder Name="/NewDemo/">
<Project Path="Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/Bit.Bswup.NewDemo.Client.csproj" />
<Project Path="Bit.Bswup.NewDemo/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.csproj" />
<Project Path="NewDemo/Bit.Bswup.NewDemo.Client/Bit.Bswup.NewDemo.Client.csproj" />
<Project Path="NewDemo/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.csproj" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</Folder>
<Project Path="Bit.Bswup.Demo/Bit.Bswup.Demo.csproj" />
<Project Path="Bit.Bswup/Bit.Bswup.csproj" />
Expand Down
124 changes: 98 additions & 26 deletions src/Bswup/Bit.Bswup/Bit.Bswup.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
<PropertyGroup>
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
<IsTrimmable>true</IsTrimmable>
<ResolveStaticWebAssetsInputsDependsOn Condition="'$(TargetFramework)' == 'net10.0'">
BeforeBuildTasks;
$(ResolveStaticWebAssetsInputsDependsOn)
</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -21,41 +17,117 @@
<PackageReference Condition="'$(TargetFramework)' == 'net10.0'" Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0" />
</ItemGroup>

<!-- Keep the build inputs (tsconfig + package manifests) out of the published static web
assets while still showing them in the project. -->
<ItemGroup>
<Content Remove="tsconfig.json" />
<None Include="tsconfig.json" />
<Content Remove="package*.json;tsconfig*.json" />
<None Include="package*.json;tsconfig*.json" />
</ItemGroup>

<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<!-- Restores npm packages, compiles the TypeScript bundles and CSS, then (in Release) minifies
the JS bundles. The sub-targets are incremental via their Inputs/Outputs; a full
regenerate-from-source on each `dotnet build`/`pack` is forced by
BuildBswupAssetsOnCrossTargeting deleting the outputs first (see its comment). -->
<Target Name="BuildBswupAssets" DependsOnTargets="InstallNodejsDependencies;BuildJavaScript;MinifyAssets;BuildCss" />

<Target Name="BeforeBuildTasks" AfterTargets="CoreCompile" Condition="'$(TargetFramework)' == 'net10.0'">
<CallTarget Targets="InstallNodejsDependencies"/>
<CallTarget Targets="BuildJavaScript"/>
<CallTarget Targets="BuildCss"/>
<!-- The bundles (wwwroot/bit-bswup*.js and wwwroot/bit-bswup.progress.css) are generated,
git-ignored files. On a clean build they do not exist when the default "wwwroot/**"
Content glob is evaluated, so the static web asset discovery in
ResolveProjectStaticWebAssets would miss them and consumers get a 404 for
_content/Bit.Bswup/bit-bswup.js (this is what breaks the demo on a fresh checkout).
ResolveProjectStaticWebAssets runs BeforeTargets="AssignTargetPaths" and does NOT depend
on ResolveStaticWebAssetsInputs, so the old ResolveStaticWebAssetsInputsDependsOn hook
never ran early enough. Hooking BeforeTargets="ResolveProjectStaticWebAssets" guarantees
the bundles are generated first (DependsOnTargets) and then explicitly added to
@(Content) so discovery picks them up. The Remove-before-Include keeps each to a single
Content entry (avoiding a duplicate / conflicting-target-path error) for builds where the
glob already captured them. -->
<Target Name="IncludeBswupAssetsAsStaticWebAsset"
BeforeTargets="ResolveProjectStaticWebAssets"
DependsOnTargets="BuildBswupAssets">
<ItemGroup>
<Content Remove="wwwroot\bit-bswup.js" />
<Content Include="wwwroot\bit-bswup.js" />
<Content Remove="wwwroot\bit-bswup.progress.js" />
<Content Include="wwwroot\bit-bswup.progress.js" />
<Content Remove="wwwroot\bit-bswup.sw.js" />
<Content Include="wwwroot\bit-bswup.sw.js" />
<Content Remove="wwwroot\bit-bswup.sw-cleanup.js" />
<Content Include="wwwroot\bit-bswup.sw-cleanup.js" />
<Content Remove="wwwroot\bit-bswup.progress.css" />
<Content Include="wwwroot\bit-bswup.progress.css" />
</ItemGroup>
</Target>

<Target Name="InstallNodejsDependencies" Inputs="package.json" Outputs="node_modules\.package-lock.json">
<!-- Force a full regenerate-from-source in the single outer (cross-targeting) build, which is
what `dotnet build` / `dotnet pack` (no -f) use. Removing the generated outputs (and the
minify stamp) first means the incremental sub-targets cannot treat them as "up to date",
so tsc/esbuild always re-emit the bundles from the TypeScript/CSS sources. This is what
makes a build reliably overwrite whatever currently sits in wwwroot - stale, unminified,
or manually edited content alike. The regeneration runs exactly once here, before the
parallel inner per-TFM builds start; those then see the fresh outputs and skip (the
sub-targets are incremental), which avoids the esbuild/Move race across inner builds. -->
<Target Name="BuildBswupAssetsOnCrossTargeting"
BeforeTargets="DispatchToInnerBuilds"
Condition="'$(IsCrossTargetingBuild)' == 'true'">
<Delete Files="wwwroot\bit-bswup.js;wwwroot\bit-bswup.progress.js;wwwroot\bit-bswup.sw.js;wwwroot\bit-bswup.sw-cleanup.js;wwwroot\bit-bswup.progress.css;$(BaseIntermediateOutputPath)$(Configuration)\bit-bswup.min.stamp" />
<CallTarget Targets="BuildBswupAssets" />
</Target>

<Target Name="InstallNodejsDependencies" Inputs="package.json;package-lock.json" Outputs="node_modules\.package-lock.json">
<Exec Command="npm install" StandardOutputImportance="high" StandardErrorImportance="high" />
</Target>

<Target Name="BuildJavaScript" Inputs="Scripts/bit-bswup.ts;Scripts/bit-bswup.progress.ts;Scripts/bit-bswup.sw.ts" Outputs="wwwroot/bit-bswup.js;wwwroot/bit-bswup.progress.js;wwwroot/bit-bswup.sw.js">
<!-- Compiles the TypeScript bundles. Incremental on the .ts/tsconfig inputs vs the emitted
wwwroot .js outputs, so a no-op when nothing changed. This always emits the unminified
bundles; Release minification is handled separately by MinifyAssets. -->
<Target Name="BuildJavaScript" Inputs="Scripts/bit-bswup.ts;Scripts/bit-bswup.progress.ts;Scripts/bit-bswup.sw.ts;Scripts/bit-bswup.sw-cleanup.ts;tsconfig.json;tsconfig.sw.json;package.json;package-lock.json" Outputs="wwwroot/bit-bswup.js;wwwroot/bit-bswup.progress.js;wwwroot/bit-bswup.sw.js;wwwroot/bit-bswup.sw-cleanup.js">
<!-- Page scripts (DOM lib) and service-worker scripts (WebWorker lib) are compiled by
separate tsconfigs because the DOM and WebWorker type libraries cannot coexist in
one compilation. Both emit into wwwroot. -->
<Exec Command="node_modules/.bin/tsc" StandardOutputImportance="high" StandardErrorImportance="high" />
<Exec Condition=" '$(Configuration)' == 'Release' " Command="node_modules/.bin/esbuild wwwroot/bit-bswup.js --minify --outfile=wwwroot/bit-bswup.js --allow-overwrite" StandardOutputImportance="high" StandardErrorImportance="high" />
<Exec Condition=" '$(Configuration)' == 'Release' " Command="node_modules/.bin/esbuild wwwroot/bit-bswup.progress.js --minify --outfile=wwwroot/bit-bswup.progress.js --allow-overwrite" StandardOutputImportance="high" StandardErrorImportance="high" />
<Exec Condition=" '$(Configuration)' == 'Release' " Command="node_modules/.bin/esbuild wwwroot/bit-bswup.sw.js --minify --outfile=wwwroot/bit-bswup.sw.js --allow-overwrite" StandardOutputImportance="high" StandardErrorImportance="high" />
<Exec Command="node_modules/.bin/tsc -p tsconfig.sw.json" StandardOutputImportance="high" StandardErrorImportance="high" />
</Target>

<!-- Release-only minification of the JS bundles. Incremental, but deliberately NOT keyed off
a configuration stamp the way tsc is: it is keyed off the emitted wwwroot bundles (the
tsc output) versus a single config-specific, TFM-independent stamp.

Why this exact shape:
- Debug and Release share the same wwwroot output files. A Debug build emits UNMINIFIED
bundles; when it does, those files' timestamps advance past this stamp, so the next
Release build re-minifies them (fixing the "Release didn't overwrite the unminified
files" bug). A plain config stamp could not catch this because a stamp from a prior
Release build survives an intervening Debug build.
- The stamp is TFM-independent ($(BaseIntermediateOutputPath)$(Configuration)\, i.e.
obj\<Config>\) so that in a cross-targeting build the outer build minifies once and
stamps, and the parallel inner per-TFM builds then see this target up to date and SKIP.
Without that, the inner builds would all run esbuild against the same shared wwwroot
files at once and race on the temp/Move (MSB3680). The Move (temp -> final) also avoids
leaving a half-written bundle if an in-place minify were interrupted. -->
<Target Name="MinifyAssets"
Condition="'$(Configuration)' == 'Release'"
DependsOnTargets="BuildJavaScript"
Inputs="wwwroot/bit-bswup.js;wwwroot/bit-bswup.progress.js;wwwroot/bit-bswup.sw.js;wwwroot/bit-bswup.sw-cleanup.js"
Outputs="$(BaseIntermediateOutputPath)$(Configuration)\bit-bswup.min.stamp">
<!-- The outer cross-targeting build never compiles, so MSBuild has not created its
intermediate directory; create it before Touch writes the stamp there. -->
<MakeDir Directories="$(BaseIntermediateOutputPath)$(Configuration)" />
<Exec Command="node_modules/.bin/esbuild wwwroot/bit-bswup.js --minify --outfile=wwwroot/bit-bswup.min.js" StandardOutputImportance="high" StandardErrorImportance="high" />
<Move SourceFiles="wwwroot/bit-bswup.min.js" DestinationFiles="wwwroot/bit-bswup.js" OverwriteReadOnlyFiles="true" />
<Exec Command="node_modules/.bin/esbuild wwwroot/bit-bswup.progress.js --minify --outfile=wwwroot/bit-bswup.progress.min.js" StandardOutputImportance="high" StandardErrorImportance="high" />
<Move SourceFiles="wwwroot/bit-bswup.progress.min.js" DestinationFiles="wwwroot/bit-bswup.progress.js" OverwriteReadOnlyFiles="true" />
<Exec Command="node_modules/.bin/esbuild wwwroot/bit-bswup.sw.js --minify --outfile=wwwroot/bit-bswup.sw.min.js" StandardOutputImportance="high" StandardErrorImportance="high" />
<Move SourceFiles="wwwroot/bit-bswup.sw.min.js" DestinationFiles="wwwroot/bit-bswup.sw.js" OverwriteReadOnlyFiles="true" />
<Exec Command="node_modules/.bin/esbuild wwwroot/bit-bswup.sw-cleanup.js --minify --outfile=wwwroot/bit-bswup.sw-cleanup.min.js" StandardOutputImportance="high" StandardErrorImportance="high" />
<Move SourceFiles="wwwroot/bit-bswup.sw-cleanup.min.js" DestinationFiles="wwwroot/bit-bswup.sw-cleanup.js" OverwriteReadOnlyFiles="true" />
<!-- Touched last so the stamp is always newer than the (re-minified) bundles, keeping the
target up to date until tsc next rewrites them. -->
<Touch Files="$(BaseIntermediateOutputPath)$(Configuration)\bit-bswup.min.stamp" AlwaysCreate="true" />
</Target>

<Target Name="BuildCss" Inputs="Styles/bit-bswup.progress.css" Outputs="wwwroot/bit-bswup.progress.css">
<Exec Command="node_modules/.bin/esbuild Styles/bit-bswup.progress.css --minify --outfile=wwwroot/bit-bswup.progress.css" StandardOutputImportance="high" StandardErrorImportance="high" />
</Target>

<ItemGroup>
<Content Remove="package*.json" />
<Content Remove="tsconfig.json" />
<None Include="package*json" />
<None Include="tsconfig.json" />
</ItemGroup>

</Project>
</Project>
42 changes: 36 additions & 6 deletions src/Bswup/Bit.Bswup/BswupProgress.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,54 @@
[Parameter] public string? Handler { get; set; }
}

<div id="bit-bswup">
@if (ChildContent is not null)
@* Configuration is published as data-* attributes and read by bit-bswup.progress.js when it
loads (it self-initializes from these attributes). This deliberately avoids emitting an
inline <script>:
- a strict Content-Security-Policy (e.g. script-src 'self') would block an inline
script - the same CSP the README recommends for hardening the service worker; and
- inline <script> tags added by an interactive Blazor renderer are not executed by the
browser, so the old approach only worked when this component was part of the
statically-rendered host document.
Razor attribute-encodes these values and the script reads them back via getAttribute, so
no value can break out of the attribute into markup or script. Make sure
_content/Bit.Bswup/bit-bswup.progress.js is referenced on the page. *@
<div id="bit-bswup"
style="display: none;"
data-bit-bswup-config="true"
data-bit-bswup-auto-reload="@(AutoReload ? "true" : "false")"
data-bit-bswup-show-logs="@(ShowLogs ? "true" : "false")"
data-bit-bswup-show-assets="@(ShowAssets ? "true" : "false")"
data-bit-bswup-app-container="@AppContainer"
data-bit-bswup-hide-app="@(HideApp ? "true" : "false")"
data-bit-bswup-auto-hide="@(AutoHide ? "true" : "false")"
data-bit-bswup-handler="@Handler">
@if (ChildContent is not null)
{
@ChildContent
}
else
else
{
<div class="bit-bswup-container">
<p class="bit-bswup-title">New version is available</p>
<p class="bit-bswup-description">Downloading updates, please wait...</p>
<div class="bit-bswup-progress">
<div id="bit-bswup-progress-bar" style="width: 0%"></div>
<div id="bit-bswup-progress-bar"
role="progressbar"
aria-label="Update download progress"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="0"
style="width: 0%"></div>
</div>
<p id="bit-bswup-percent">0 %</p>
<ul id="bit-bswup-assets" style="display: @(ShowAssets ? "block" : "none");"></ul>
<div id="bit-bswup-error" class="bit-bswup-error" style="display: none;" role="alert">
<p class="bit-bswup-error-title">Update failed to install</p>
<p id="bit-bswup-error-message" class="bit-bswup-error-message"></p>
<pre id="bit-bswup-error-details" class="bit-bswup-error-details"></pre>
<button id="bit-bswup-error-retry" type="button">Retry</button>
Comment thread
msynk marked this conversation as resolved.
</div>
</div>
<button id="bit-bswup-reload">Update ready to install!</button>
}
<img style="display: none" src=""
onerror="BitBswupProgress.start(@(AutoReload ? "true" : "false"), @(ShowLogs ? "true" : "false"), @(ShowAssets ? "true" : "false"), '@(AppContainer)', @(HideApp ? "true" : "false"), @(AutoHide ? "true" : "false"), '@(Handler)')">
</div>
Loading
Loading