Skip to content

Embed webui-press template assets and fix client bundling#375

Merged
mohamedmansour merged 5 commits into
mainfrom
webui-press-embed-assets
Jun 30, 2026
Merged

Embed webui-press template assets and fix client bundling#375
mohamedmansour merged 5 commits into
mainfrom
webui-press-embed-assets

Conversation

@mohamedmansour

Copy link
Copy Markdown
Contributor

Why

Installing the docs generator with cargo install microsoft-webui-press ships only the binary, so the default template/ and components/ directories were missing at runtime. Builds failed unless you passed --template pointing at a source checkout, even though the README advertises a plain webui-press build. This makes the published binary self-contained.

A second problem surfaced after per-page script bundling (#373) landed: client components compiled by esbuild inherited whatever tsconfig the consumer project had, so WebUI's decorator semantics were not guaranteed and hydration bundling could break.

What

Embed template assets (6913ba53)

  • Embed template/ and components/ into the binary using the include_dir crate.
  • On build/serve without --template, extract them once into a temp directory keyed by crate version plus an FNV-1a hash of the embedded contents. A .complete sentinel marks a fully extracted cache so repeat runs skip re-extraction; a stale or partial directory is wiped and rebuilt.
  • --template still overrides the bundled assets for local template development.
  • Give the docs search input a stable id/name for form semantics and accessibility.

Fix client bundling (a5abcdf9)

  • Pass --tsconfig-raw to esbuild forcing experimentalDecorators: true and useDefineForClassFields: false, so component bundling always uses WebUI decorator semantics regardless of any tsconfig in the consumer project.

Notes for reviewers

  • Net effect: cargo install microsoft-webui-press followed by webui-press build/serve now works with no source tree and no flags.
  • Extraction targets the system temp dir; the version+hash path plus the .complete sentinel keep it stable and self-healing across runs.
  • Adds one workspace dependency, include_dir.
  • Tests cover embedded extraction (template and components present) and the esbuild decorator args.

mohamedmansour and others added 2 commits June 30, 2026 00:17
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Force esbuild to use legacy TypeScript decorator output for WebUI components and add an accessible name/id pair to the docs search input.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the microsoft-webui-press installed binary self-contained by embedding the default template/ and components/ assets and extracting them on demand, and it hardens the esbuild bundling pipeline so client components always compile with WebUI’s decorator semantics regardless of the consumer’s tsconfig.

Changes:

  • Embed crates/webui-press/template/ and crates/webui-press/components/ into the webui-press binary and extract them into a stable temp cache when --template is not provided.
  • Force esbuild to use a fixed TS config via --tsconfig-raw to ensure decorators/class-fields semantics match WebUI expectations.
  • Minor template markup update to give the docs search input a stable id/name.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/webui-press/template/docs-search/docs-search.html Adds stable id/name attributes to the search input.
crates/webui-press/src/main.rs Embeds template/components and adds extraction + hashing + a regression test.
crates/webui-press/src/bundler.rs Forces esbuild decorator semantics via --tsconfig-raw and adds a test.
crates/webui-press/Cargo.toml Adds include_dir dependency via workspace.
Cargo.toml Adds include_dir to [workspace.dependencies].
Cargo.lock Locks include_dir and macro dependencies.

Comment on lines +128 to +131
let template_dir = root.join("template");
if root.join(".complete").is_file() && template_dir.join("index.html").is_file() {
return Ok(template_dir);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7255c30. is_complete_cache now also requires the sibling components/ directory (alongside .complete and template/index.html), so an externally corrupted cache re-extracts cleanly instead of failing later with a missing-component build error.

Comment thread crates/webui-press/src/main.rs Outdated
Comment on lines +133 to +145
if root.exists() {
fs::remove_dir_all(&root)
.map_err(|e| anyhow::anyhow!("Cannot refresh embedded template assets: {e}"))?;
}
EMBEDDED_TEMPLATE
.extract(root.join("template"))
.map_err(|e| anyhow::anyhow!("Cannot extract embedded template: {e}"))?;
EMBEDDED_COMPONENTS
.extract(root.join("components"))
.map_err(|e| anyhow::anyhow!("Cannot extract embedded components: {e}"))?;
fs::write(root.join(".complete"), [])
.map_err(|e| anyhow::anyhow!("Cannot finalize embedded template assets: {e}"))?;
Ok(template_dir)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7255c30. Extraction now writes into a unique per-process staging dir (<name>.staging-<pid>-<nonce>) and publishes with a single atomic rename; no process ever remove_dir_alls or writes into the published cache. Since the cache is content-addressed, a peer that publishes first is simply reused. A stale/incomplete root is moved aside under a unique name (so concurrent movers never collide) and replaced, with a bounded retry loop to guarantee termination.

mohamedmansour and others added 3 commits June 30, 2026 01:39
anyhow 1.0.102 is flagged by RUSTSEC-2026-0190 (unsoundness in
Error::downcast_mut when context is added then downcast_mut is called),
failing the Lint job's `cargo deny check advisories`. 1.0.103 is the
patched release; this updates the lockfile only.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address PR review feedback on the embedded-asset cache:

- Validate the sibling components/ directory (not just .complete and
  template/index.html) before treating a cache as usable, so an
  externally corrupted cache re-extracts cleanly instead of failing
  later with a confusing missing-component build error.
- Extract into a unique per-process staging directory and publish it
  with a single atomic rename, and never remove_dir_all the published
  cache. Concurrent webui-press runs can no longer race a delete against
  another process's writes or observe a partially extracted cache. A
  stale/incomplete cache is moved aside under a unique name (so
  concurrent movers never collide) and replaced; the retry loop is
  bounded to guarantee termination.

Add a regression test for the cache-completeness contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…rocess

webui-press is invoked one process at a time, so the cross-process race
hardening on the embedded-asset cache was more machinery than the usage
warrants. Drop the per-process nonce, the move-aside-stale dance, and the
bounded publish retry loop.

Keep the parts that matter for the sequential case: content-addressed
cache reuse, the `.complete` sentinel (including the sibling components/
validation), and atomic-rename publish. A non-complete cache is treated
as a stale or interrupted extraction, cleared, re-extracted into a
staging dir, and published in one atomic rename, so an interrupted run
never leaves a half-written cache behind.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mohamedmansour mohamedmansour merged commit 0d7d5da into main Jun 30, 2026
21 checks passed
@mohamedmansour mohamedmansour deleted the webui-press-embed-assets branch June 30, 2026 17:14
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.

3 participants