Skip to content

feat: Docker deployment + JS minification#278

Merged
msallin merged 4 commits into
masterfrom
feat/docker-deployment-and-minification
May 25, 2026
Merged

feat: Docker deployment + JS minification#278
msallin merged 4 commits into
masterfrom
feat/docker-deployment-and-minification

Conversation

@msallin

@msallin msallin commented May 24, 2026

Copy link
Copy Markdown
Owner

Adds a standalone Docker shape for OnaPlotter and minifies the hand-written JS interop layer on Release publish.

Three deliverables

1. JS minification on Release publish

OnaPlotter.csproj runs esbuild --minify against wwwroot/js/**/*.js in publish output (Release only). Cuts ~30-40% off the raw JS payload; brotli stacks on top. Skipped in Debug to keep dev iterations fast. Test files (*.test.js, __tests__/) are also excluded from publish entirely so they don't ship to clients.

Requires npm ci before dotnet publish in Release. publish.yml updated to install. CheckEsbuildPresent MSBuild target surfaces a clear error if node_modules/esbuild is missing.

2. OnaPlotter.Server Kestrel host

New project hosts the published Blazor WASM bundle via UseBlazorFrameworkFiles + MapFallbackToFile. PublishAot=true gated on Release so dotnet run --project OnaPlotter.Server stays fast for local dev; the AOT-compiled binary is what Docker ships.

Runtime config via env vars:

  • SK_SERVER_URL → rewrites wwwroot/appsettings.json SignalK:ServerUrl
  • BASE_HREF → rewrites the <base href> in wwwroot/index.html

Mutations are hand-rolled (no JsonSerializer / Regex) to keep the AOT graph minimal. The standalone WASM dev path (dotnet run --project OnaPlotter) is untouched.

3. Multi-arch Docker image + GHCR workflow

Dockerfile is multi-stage on dotnet/runtime-deps:10.0-alpine. Build stage compiles both AOTs (WASM AOT for the client + NativeAOT for the host); runtime stage carries only the self-contained binary + the static bundle. Final image ~35-50 MB depending on arch.

.github/workflows/docker.yml builds linux/amd64 + linux/arm64 on their native runners (no QEMU), pushes per-arch tags to ghcr.io/msallin/ona-plotter, then composes a multi-arch manifest. Pre-release tags (v*-alpha/beta/rc) skip the :latest move so it stays on the last GA.

Verification

  • dotnet build OnaPlotter.slnx — passes
  • dotnet run --project OnaPlotter.Server — serves / and /_framework/blazor.webassembly.js (200 OK, ~10 KB index, ~60 KB blazor.webassembly.js)
  • dotnet publish OnaPlotter.Server -c Debug -o ... — produces a publish layout with wwwroot/index.html + _framework/ merged correctly
  • Release publish + Docker build not verified locally; will run on first tag push.

Run

After a release tag fires the new workflow:

docker run -p 8080:8080 \
  -e SK_SERVER_URL=https://my-sk.example:3000 \
  ghcr.io/msallin/ona-plotter:<version>

For local dev:

dotnet run --project OnaPlotter.Server
# or for tight UI iteration on the WASM standalone path:
dotnet run --project OnaPlotter

msallin added 4 commits May 24, 2026 18:26
Adds esbuild as a devDependency and an MSBuild target that minifies
the hand-written JS interop layer (leafletInterop, aisLayer, etc.)
during Release publish. Drops 30-40% off the raw JS payload; brotli
stacks on top. Skipped in Debug to keep dev iterations fast.

Also excludes *.test.js / *.test.mjs / __tests__/ from the published
bundle so test code doesn't end up in the static asset tree.
New OnaPlotter.Server project hosts the published Blazor WASM
bundle via UseBlazorFrameworkFiles + MapFallbackToFile. PublishAot
is gated on Release so 'dotnet run --project OnaPlotter.Server'
stays fast for local dev; the AOT-compiled binary is what Docker
ships.

Two env vars template the static bundle at startup:

  SK_SERVER_URL  - rewrites wwwroot/appsettings.json SignalK:ServerUrl
  BASE_HREF      - rewrites the <base href> in wwwroot/index.html

JSON / HTML mutation is hand-rolled to keep the AOT graph free of
JsonSerializer + Regex. The standalone WASM dev path ('dotnet run
--project OnaPlotter') keeps working unchanged.
Multi-stage Dockerfile based on dotnet/runtime-deps:10.0-alpine.
Build stage compiles the WASM bundle (AOT) + the Kestrel host
(NativeAOT) in one publish; runtime stage carries only the
self-contained binary + the static bundle. Final image lands
around 35-50 MB depending on arch.

GH Actions workflow builds linux/amd64 + linux/arm64 on their
native runners (no QEMU), pushes per-arch tags to GHCR, then
composes a multi-arch manifest as :version and :latest. Pre-
release tags (v*-alpha/beta/rc) publish :version but skip :latest.
The new MinifyPublishedJs MSBuild target needs node_modules/esbuild
to exist when 'dotnet publish' runs in Release. Adding npm ci to
publish.yml mirrors what the Dockerfile and local deploy.ps1 users
already do; without it the workflow would fail with the clear
CheckEsbuildPresent error at publish time.
@msallin msallin merged commit 53328ab into master May 25, 2026
2 of 3 checks passed
@msallin msallin deleted the feat/docker-deployment-and-minification branch May 25, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant