From a6fdcdfd4e1ea6d3e6dda4dac9779a57dad6cd35 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 19 Aug 2026 16:53:01 -0400 Subject: [PATCH] fix(examples): mount www/ in Core-mode apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit page_react_html() reads index.html but does not serve the files index.html references. Shiny Express mounts the app directory's www/ at "/" automatically (shiny/express/_run.py); Core's App() leaves static_assets None, so the page loaded and then 404'd on its own app.js and main.css. Both Core examples were affected — 01-hello/app-core.py and, less visibly, 10-bookmarking/app.py, which rendered a blank page because its entire client bundle 404'd. Both now pass static_assets={"/": /www}. Documents the requirement where a Core user will actually meet it: a "Serving the client's files" section in page_react_html()'s docstring and the Core snippet in pkg-py/README.md. R needs no counterpart — shiny::runApp() serves www/ next to app.R automatically. Docs-and-examples only; no package behavior change, so no new tests. --- examples/01-hello/README.md | 7 ++++++- examples/01-hello/app-core.py | 7 ++++++- examples/10-bookmarking/app.py | 11 ++++++++++- pkg-py/README.md | 11 +++++++++-- pkg-py/src/shinyreact/_page.py | 21 +++++++++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/examples/01-hello/README.md b/examples/01-hello/README.md index 840c64e..2e11c28 100644 --- a/examples/01-hello/README.md +++ b/examples/01-hello/README.md @@ -37,7 +37,7 @@ never tears the SVG down and re-mounts it. ``` examples/01-hello/ ├── app.py # Express: set_react_page() + 2 reactive_output outputs -├── app-core.py # Core: page_react_html() + App(app_ui, server), same outputs +├── app-core.py # Core: page_react_html() + App(..., static_assets=), same outputs ├── app.R # R: page_react_html() + reactive_output, same outputs ├── faithful.py # Old Faithful waiting times + a stdlib-only binner (Python) ├── faithful.csv # base R's `faithful` dataset, exported for the Python servers @@ -56,6 +56,11 @@ return `NULL` until the client's first `bins` message arrives (Python raises a silent exception instead), and wrap the histogram vectors in `I()` so a single-bin result still serializes as a JSON array rather than a scalar. +`app-core.py` passes `static_assets={"/": .../www}` to `App()`. Shiny Express +(`app.py`) and R's `runApp()` (`app.R`) both mount the app directory's `www/` +automatically; Core's `App()` does not, so without it `index.html` loads and +then 404s on `app.js` and `main.css`. + ## Bridge primitives used - `from shinyreact import reactive_output, set_react_page` (Express server, `app.py`) / `page_react_html` (Core server, `app-core.py`); `library(shinyreact)` with `page_react_html()` + `reactive_output()` in `app.R` diff --git a/examples/01-hello/app-core.py b/examples/01-hello/app-core.py index a96e7a0..0fbb6fb 100644 --- a/examples/01-hello/app-core.py +++ b/examples/01-hello/app-core.py @@ -1,3 +1,5 @@ +from pathlib import Path + from faithful import histogram, waiting from shiny import App, Inputs, Outputs, Session from shinyreact import page_react_html, reactive_output @@ -16,4 +18,7 @@ def dist_caption(): return f"{len(waiting)} eruptions in {n} bin{'' if n == 1 else 's'}" -app = App(app_ui, server) +# Core apps must mount www/ themselves. Shiny Express auto-serves the app +# directory's www/ at "/", but App() does not — without this, index.html loads +# and then 404s on app.js and main.css. +app = App(app_ui, server, static_assets={"/": Path(__file__).parent / "www"}) diff --git a/examples/10-bookmarking/app.py b/examples/10-bookmarking/app.py index c38cbde..ca8c5e1 100644 --- a/examples/10-bookmarking/app.py +++ b/examples/10-bookmarking/app.py @@ -1,3 +1,5 @@ +from pathlib import Path + import shinyreact from shiny import App, Inputs, Outputs, Session, reactive @@ -21,4 +23,11 @@ async def _on_bookmark_click() -> None: await session.bookmark() -app = App(app_ui, server, bookmark_store="url") +# Core apps must mount www/ themselves — App() has no equivalent of Shiny +# Express's automatic www/ static mount. +app = App( + app_ui, + server, + static_assets={"/": Path(__file__).parent / "www"}, + bookmark_store="url", +) diff --git a/pkg-py/README.md b/pkg-py/README.md index 4984ea9..4653dae 100644 --- a/pkg-py/README.md +++ b/pkg-py/README.md @@ -40,13 +40,20 @@ def greeting(): Pair with a `www/index.html` that loads your React client (no-build `app.js` or a built bundle from `src/ui.tsx`). See the [examples catalog](../examples/README.md) for file layouts and dev workflows, from no-build to Vite + HMR. -In Core mode, use `page_react_html()`: +In Core mode, use `page_react_html()`. Core apps must also mount `www/` +themselves — Shiny Express does this automatically, `App()` does not, and +without it the page loads and then 404s on its own scripts and stylesheets: ```python +from pathlib import Path from shiny import App import shinyreact -app = App(shinyreact.page_react_html("www/index.html"), server) +app = App( + shinyreact.page_react_html("www/index.html"), + server, + static_assets={"/": Path(__file__).parent / "www"}, +) ``` ### Sending messages to React components diff --git a/pkg-py/src/shinyreact/_page.py b/pkg-py/src/shinyreact/_page.py index 4f1f68c..fa7d537 100644 --- a/pkg-py/src/shinyreact/_page.py +++ b/pkg-py/src/shinyreact/_page.py @@ -236,6 +236,27 @@ def page_react_html(path: str | Path = "www/index.html") -> TagList: Unlike :func:`set_react_page`, this does not auto-discover dependencies from traditional Shiny renderers — it only attaches the shinyreact bundle. + Serving the client's files + -------------------------- + This function only reads ``index.html``; it does not serve the sibling + files that ``index.html`` references. Shiny Express mounts the app + directory's ``www/`` at ``/`` automatically, but :class:`shiny.App` does + not, so a Core app must do it explicitly — otherwise the page loads and + then 404s on its own scripts and stylesheets:: + + from pathlib import Path + from shiny import App + from shinyreact import page_react_html + + app = App( + page_react_html(), + server, + static_assets={"/": Path(__file__).parent / "www"}, + ) + + (R's ``shiny::runApp()`` serves ``www/`` next to ``app.R`` automatically, + so the R counterpart needs no equivalent.) + Args: path: Path to the HTML file. Absolute paths are used verbatim; relative paths resolve against the caller module's directory, or