diff --git a/CLAUDE.md b/CLAUDE.md index 3cdf508e..17909b53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,13 +6,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co `shinyreact` is a monorepo providing React UI infrastructure for Shiny (Python and R). It provides zero UI components — it is the bridge between a Shiny server that contains only reactive computation and a React client the app author owns. -The repo ships one first-class pattern: the **`ui.tsx` pattern** — `set_react_page()` (Python Express) / `page_react_html()` (Python Core, R) bootstraps a static `www/index.html` hosting a React client whose entry conventionally lives in `ui.tsx`; the client and server communicate through the `useShinyInput` / `useShinyOutputValue` hook family. See `DESIGN.md` for background. +The repo ships one first-class pattern: the **`ui.tsx` pattern** — `set_react_page()` (Python Express) / `page_react()` (Python Core, R) bootstraps a React client whose entry conventionally lives in `ui.tsx` (compiled to `www/ui.js`, discovered automatically); the client and server communicate through the `useShinyInput` / `useShinyOutputValue` hook family. See `DESIGN.md` for background. ## Terminology **`ui.tsx`** is the canonical name of the pattern. **Never write "SPA", "Single Page App", "Single-Page Application", "traditional pattern", `client-ui`, or `ui-object`** in new content (docs, comments, commit messages, PR/issue text). -- **`ui.tsx` pattern** — UI defined in a client-side codebase whose entry is conventionally `ui.tsx` (or `App.jsx`, or `app.js` for no-build); bootstrapped from the app file via `set_react_page()` / `page_react_html()`. `ui.tsx` is the *idiomatic* canonical name — examples may use simpler variants like `www/app.js` (no-build) or `src/App.jsx` (Vite + JSX). Treat `ui.tsx` as a *role label* for the React entry, not a strict filename requirement. +- **`ui.tsx` pattern** — UI defined in a client-side codebase whose entry is conventionally `ui.tsx` (or `ui.jsx`, or `ui.js` for no-build); bootstrapped from the app file via `set_react_page()` / `page_react()` (or `page_react_html()` for apps that own a full HTML document). `ui.tsx` is the *idiomatic* canonical name — examples use the same role at different tiers: `www/ui.js` (no-build) or `src/ui.jsx` (Vite + JSX, built to `www/ui.js`). Treat `ui.tsx` as a *role label* for the React entry, not a strict filename requirement. - The phrase "traditional Shiny" is fine when it refers to vanilla Shiny (no shinyreact involved). - The repo formerly also shipped an **`app.py` pattern** (server-side JSON-spec rendering via `Node` / `render_react` / `page_react`). It was removed in #168; see the tracking comment on #167 for git-history pointers if you encounter stale references. @@ -91,17 +91,19 @@ The JS output (`pkg-js/dist/shinyreact.js`) is a self-contained IIFE that bundle - `@shinyreact.reactive_output` — `Renderer[Jsonifiable]` subclass; passes raw JSON data through for `useShinyOutputValue()` hooks, with no placeholder (`auto_output_ui()` returns `None`) - `shinyreact.send_message(session, type, data)` — sends `shinyReactMessage` custom messages consumed by `useShinyMessageHandler()` -- `shinyreact.set_react_page(path="www/index.html")` — Express helper that serves a static `www/index.html`; auto-discovers `HTMLDependency` objects from traditional Shiny renderers and injects the shinyreact dep -- `shinyreact.page_react_html(path="www/index.html")` — Core-mode helper that serves a static `www/index.html` as the `ui` argument of `App(ui=..., server=...)`; attaches the shinyreact dep. The Core counterpart to the Express-only `set_react_page()`. Unlike `set_react_page`, it does not auto-discover renderer dependencies +- `shinyreact.set_react_page(path=None)` — Express helper; with no args serves `www/index.html` when present, else discovers `www/ui.js` / `www/ui.css` and emits no body HTML. Auto-discovers `HTMLDependency` objects from traditional Shiny renderers and injects the shinyreact dep +- `shinyreact.page_react(src_dir=None, js_file="ui.js", css_file="ui.css", title=None)` — Core-mode zero-config page: discovers `www/ui.js` / `www/ui.css` next to the calling module, serves them as an mtime-versioned dependency (cache-busted), title defaults to the app folder name; the client appends its own mount container to `` +- `shinyreact.page_react_html(path="www/index.html")` — Core-mode helper that serves an HTML file as the `ui` argument of `App(ui=..., server=...)`; attaches the shinyreact dep. In Python the file is a body *fragment* (full-document support is blocked on an upstream py-shiny gap); in R it must be a complete document with a `{{ headContent() }}` marker (htmltools `htmlTemplate()`). Neither auto-discovers renderer dependencies - `shinyreact.page_bare(...)` / `shinyreact.page_react_dep(...)` — escape-hatch page builder and app-bundle `HTMLDependency` helper - Bookmark restore + protocol handshake: page entry points emit a ` diff --git a/examples/01-hello/www/main.css b/examples/01-hello/www/ui.css similarity index 100% rename from examples/01-hello/www/main.css rename to examples/01-hello/www/ui.css diff --git a/examples/01-hello/www/app.js b/examples/01-hello/www/ui.js similarity index 98% rename from examples/01-hello/www/app.js rename to examples/01-hello/www/ui.js index b5a85903..65c170d9 100644 --- a/examples/01-hello/www/app.js +++ b/examples/01-hello/www/ui.js @@ -183,7 +183,7 @@ function App() { ); } -// No mount div in index.html — create the container and append it to . +// No mount div in the generated page — create the container and append it to . // The script is deferred, so document.body is parsed by the time this runs. const root = ReactDOM.createRoot( document.body.appendChild(document.createElement("div")), diff --git a/examples/02-columns/README.md b/examples/02-columns/README.md index dfcd901f..e727861d 100644 --- a/examples/02-columns/README.md +++ b/examples/02-columns/README.md @@ -21,9 +21,8 @@ The result: ~20 lines of server logic, versus ~80 lines wrestling with observers examples/02-columns/ ├── app.py # set_react_page() + 1 reactive_output (column_data) + 1 reactive.effect on input.move_item └── www/ - ├── index.html - ├── app.js # raw React.createElement (Column + ItemRow components) - └── main.css + ├── ui.js # raw React.createElement (Column + ItemRow components) + └── ui.css ``` Same no-build shape as [01-hello](../01-hello/). No `package.json`, no bundler. diff --git a/examples/02-columns/www/index.html b/examples/02-columns/www/index.html deleted file mode 100644 index 7a54efb1..00000000 --- a/examples/02-columns/www/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/examples/02-columns/www/main.css b/examples/02-columns/www/ui.css similarity index 100% rename from examples/02-columns/www/main.css rename to examples/02-columns/www/ui.css diff --git a/examples/02-columns/www/app.js b/examples/02-columns/www/ui.js similarity index 89% rename from examples/02-columns/www/app.js rename to examples/02-columns/www/ui.js index 6369a707..2130e1cd 100644 --- a/examples/02-columns/www/app.js +++ b/examples/02-columns/www/ui.js @@ -90,5 +90,9 @@ function App() { ); } -const root = ReactDOM.createRoot(document.getElementById("root")); +// No mount div in the generated page -- create the container and append it +// to . The script is deferred, so document.body is parsed by now. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); root.render(h(App)); diff --git a/examples/03-columns-shadcn/.gitignore b/examples/03-columns-shadcn/.gitignore index 9558ca43..a790fdb1 100644 --- a/examples/03-columns-shadcn/.gitignore +++ b/examples/03-columns-shadcn/.gitignore @@ -1,4 +1,4 @@ node_modules/ package-lock.json -www/app.js -www/style.css +www/ui.js +www/ui.css diff --git a/examples/03-columns-shadcn/README.md b/examples/03-columns-shadcn/README.md index d1de682c..a6d0a8cb 100644 --- a/examples/03-columns-shadcn/README.md +++ b/examples/03-columns-shadcn/README.md @@ -19,23 +19,22 @@ examples/03-columns-shadcn/ ├── vite.config.js # lib-mode IIFE; React → window.shinyreact ├── src/ │ ├── App.jsx # composes Column → ItemRow with shadcn Card/Button -│ ├── main.jsx # mounts via window.shinyreact.React/ReactDOM +│ ├── ui.jsx # mounts via window.shinyreact.React/ReactDOM (appends its own container) │ ├── index.css # Tailwind v4 + shadcn theme tokens │ ├── lib/utils.js # cn() = clsx + tailwind-merge │ └── components/ui/ │ ├── button.jsx # actual shadcn Button (cva variants) │ └── card.jsx # actual shadcn Card stack └── www/ - ├── index.html # 3 lines, committed - ├── app.js # built by Vite (gitignored) - └── style.css # built by Vite (gitignored) + ├── ui.js # built by Vite (gitignored) + └── ui.css # built by Vite (gitignored) ``` ## Build plumbing Worth understanding because every shadcn-style example in this repo uses the same setup: -- `vite.config.js` is in **lib mode** with `format: "iife"`, output filename `app.js`. We can't use Vite's regular HTML pipeline because we need a single self-contained bundle that reuses the page's existing React. +- `vite.config.js` is in **lib mode** with `format: "iife"`, output filename `ui.js` — the name `set_react_page()` discovers, so the app file needs no arguments and no `index.html`. We can't use Vite's regular HTML pipeline because we need a single self-contained bundle that reuses the page's existing React. - `react`, `react-dom`, `react-dom/client` are listed as `external` and mapped via `rollupOptions.output.globals` to `window.shinyreact.React` / `window.shinyreact.ReactDOM`. The IIFE bundle reuses the React instance that owns the shinyreact hooks (mixing two React copies would break the hooks). - `@tailwindcss/vite` wires Tailwind v4 in directly; the shadcn design tokens live in `src/index.css`. - `define: { "process.env.NODE_ENV": '"production"' }` is set because lib mode does not auto-replace it (it assumes a downstream bundler will). Without it the bundled React jsx-runtime hits a `process is not defined` error in the browser. diff --git a/examples/03-columns-shadcn/src/main.jsx b/examples/03-columns-shadcn/src/main.jsx deleted file mode 100644 index 7e1137b6..00000000 --- a/examples/03-columns-shadcn/src/main.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import "@/index.css"; - -import App from "@/App"; - -const { React, ReactDOM } = window.shinyreact; - -const root = ReactDOM.createRoot(document.getElementById("root")); -root.render(React.createElement(App)); diff --git a/examples/03-columns-shadcn/src/ui.jsx b/examples/03-columns-shadcn/src/ui.jsx new file mode 100644 index 00000000..e19e6126 --- /dev/null +++ b/examples/03-columns-shadcn/src/ui.jsx @@ -0,0 +1,12 @@ +import "@/index.css"; + +import App from "@/App"; + +const { React, ReactDOM } = window.shinyreact; + +// No index.html in this example -- the server page is generated by +// set_react_page() discovery, so the app appends its own mount container. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); +root.render(React.createElement(App)); diff --git a/examples/03-columns-shadcn/vite.config.js b/examples/03-columns-shadcn/vite.config.js index 0afcf25a..7962d843 100644 --- a/examples/03-columns-shadcn/vite.config.js +++ b/examples/03-columns-shadcn/vite.config.js @@ -22,14 +22,16 @@ export default defineConfig({ emptyOutDir: false, cssCodeSplit: false, lib: { - entry: path.resolve(__dirname, "src/main.jsx"), + entry: path.resolve(__dirname, "src/ui.jsx"), formats: ["iife"], name: "ColumnsShadcn", - fileName: () => "app.js", + fileName: () => "ui.js", }, rollupOptions: { external: ["react", "react-dom", "react-dom/client"], output: { + // Name the emitted CSS asset ui.css (Vite 5 lib mode defaults to style.css). + assetFileNames: "ui.[ext]", globals: { react: "window.shinyreact.React", "react-dom": "window.shinyreact.ReactDOM", diff --git a/examples/03-columns-shadcn/www/index.html b/examples/03-columns-shadcn/www/index.html deleted file mode 100644 index 11059352..00000000 --- a/examples/03-columns-shadcn/www/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/examples/04-shadcn/.gitignore b/examples/04-shadcn/.gitignore index 9558ca43..a790fdb1 100644 --- a/examples/04-shadcn/.gitignore +++ b/examples/04-shadcn/.gitignore @@ -1,4 +1,4 @@ node_modules/ package-lock.json -www/app.js -www/style.css +www/ui.js +www/ui.css diff --git a/examples/04-shadcn/README.md b/examples/04-shadcn/README.md index 9b24b6b3..563fe163 100644 --- a/examples/04-shadcn/README.md +++ b/examples/04-shadcn/README.md @@ -30,7 +30,7 @@ examples/04-shadcn/ ├── README.md ├── src/ │ ├── App.jsx # composes the cards -│ ├── main.jsx # mounts via window.shinyreact.React/ReactDOM +│ ├── ui.jsx # mounts via window.shinyreact.React/ReactDOM (appends its own container) │ ├── index.css # Tailwind v4 + shadcn theme tokens │ ├── lib/utils.js # cn() = clsx + tailwind-merge │ └── components/ @@ -46,16 +46,15 @@ examples/04-shadcn/ │ ├── input.jsx │ └── separator.jsx └── www/ - ├── index.html # 3 lines, committed - ├── app.js # built by Vite (gitignored) - └── style.css # built by Vite (gitignored) + ├── ui.js # built by Vite (gitignored) + └── ui.css # built by Vite (gitignored) ``` ## Build plumbing The non-obvious bit is how the bundle stays compatible with the page-level `window.shinyreact` runtime: -- `vite.config.js` is in **lib mode** with format `iife`, output filename `app.js`. +- `vite.config.js` is in **lib mode** with format `iife`, output filename `ui.js` — the name `set_react_page()` discovers, so the app file needs no arguments and no `index.html`. - `react`, `react-dom`, and `react-dom/client` are listed as `external` and mapped via `rollupOptions.output.globals` to `window.shinyreact.React` / `window.shinyreact.ReactDOM`. The IIFE bundle reuses the React instance that owns the shinyreact hooks (mixing two React copies would break the hooks). - `react`/`react-dom` are still listed as `devDependencies` so `react/jsx-runtime` resolves at build time when Vite's automatic JSX transform inlines it. - Tailwind v4 is wired in through `@tailwindcss/vite`; the shadcn design tokens live in `src/index.css`. diff --git a/examples/04-shadcn/src/main.jsx b/examples/04-shadcn/src/main.jsx deleted file mode 100644 index 7e1137b6..00000000 --- a/examples/04-shadcn/src/main.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import "@/index.css"; - -import App from "@/App"; - -const { React, ReactDOM } = window.shinyreact; - -const root = ReactDOM.createRoot(document.getElementById("root")); -root.render(React.createElement(App)); diff --git a/examples/04-shadcn/src/ui.jsx b/examples/04-shadcn/src/ui.jsx new file mode 100644 index 00000000..e19e6126 --- /dev/null +++ b/examples/04-shadcn/src/ui.jsx @@ -0,0 +1,12 @@ +import "@/index.css"; + +import App from "@/App"; + +const { React, ReactDOM } = window.shinyreact; + +// No index.html in this example -- the server page is generated by +// set_react_page() discovery, so the app appends its own mount container. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); +root.render(React.createElement(App)); diff --git a/examples/04-shadcn/vite.config.js b/examples/04-shadcn/vite.config.js index 0afcf25a..7962d843 100644 --- a/examples/04-shadcn/vite.config.js +++ b/examples/04-shadcn/vite.config.js @@ -22,14 +22,16 @@ export default defineConfig({ emptyOutDir: false, cssCodeSplit: false, lib: { - entry: path.resolve(__dirname, "src/main.jsx"), + entry: path.resolve(__dirname, "src/ui.jsx"), formats: ["iife"], name: "ColumnsShadcn", - fileName: () => "app.js", + fileName: () => "ui.js", }, rollupOptions: { external: ["react", "react-dom", "react-dom/client"], output: { + // Name the emitted CSS asset ui.css (Vite 5 lib mode defaults to style.css). + assetFileNames: "ui.[ext]", globals: { react: "window.shinyreact.React", "react-dom": "window.shinyreact.ReactDOM", diff --git a/examples/04-shadcn/www/index.html b/examples/04-shadcn/www/index.html deleted file mode 100644 index 11059352..00000000 --- a/examples/04-shadcn/www/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/examples/05-temperature/www/index.html b/examples/05-temperature/www/index.html deleted file mode 100644 index 7a54efb1..00000000 --- a/examples/05-temperature/www/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/examples/05-temperature/www/main.css b/examples/05-temperature/www/ui.css similarity index 100% rename from examples/05-temperature/www/main.css rename to examples/05-temperature/www/ui.css diff --git a/examples/05-temperature/www/app.js b/examples/05-temperature/www/ui.js similarity index 91% rename from examples/05-temperature/www/app.js rename to examples/05-temperature/www/ui.js index 7272c23b..02bfe85d 100644 --- a/examples/05-temperature/www/app.js +++ b/examples/05-temperature/www/ui.js @@ -106,5 +106,9 @@ function App() { ); } -const root = ReactDOM.createRoot(document.getElementById("root")); +// No mount div in the generated page -- create the container and append it +// to . The script is deferred, so document.body is parsed by now. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); root.render(h(App)); diff --git a/examples/06-data-frame/www/index.html b/examples/06-data-frame/www/index.html deleted file mode 100644 index 8145daad..00000000 --- a/examples/06-data-frame/www/index.html +++ /dev/null @@ -1,2 +0,0 @@ -
- diff --git a/examples/06-data-frame/www/app.js b/examples/06-data-frame/www/ui.js similarity index 81% rename from examples/06-data-frame/www/app.js rename to examples/06-data-frame/www/ui.js index a39cab45..0607b1fb 100644 --- a/examples/06-data-frame/www/app.js +++ b/examples/06-data-frame/www/ui.js @@ -34,5 +34,9 @@ function App() { ); } -const root = ReactDOM.createRoot(document.getElementById("root")); +// No mount div in the generated page -- create the container and append it +// to . The script is deferred, so document.body is parsed by now. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); root.render(h(App)); diff --git a/examples/07-plotly/www/index.html b/examples/07-plotly/www/index.html deleted file mode 100644 index 8145daad..00000000 --- a/examples/07-plotly/www/index.html +++ /dev/null @@ -1,2 +0,0 @@ -
- diff --git a/examples/07-plotly/www/app.js b/examples/07-plotly/www/ui.js similarity index 83% rename from examples/07-plotly/www/app.js rename to examples/07-plotly/www/ui.js index 3de37fbe..55b687cf 100644 --- a/examples/07-plotly/www/app.js +++ b/examples/07-plotly/www/ui.js @@ -38,5 +38,9 @@ function App() { ); } -const root = ReactDOM.createRoot(document.getElementById("root")); +// No mount div in the generated page -- create the container and append it +// to . The script is deferred, so document.body is parsed by now. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); root.render(h(App)); diff --git a/examples/08-input-handler/www/index.html b/examples/08-input-handler/www/index.html deleted file mode 100644 index 7a54efb1..00000000 --- a/examples/08-input-handler/www/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/examples/08-input-handler/www/main.css b/examples/08-input-handler/www/ui.css similarity index 100% rename from examples/08-input-handler/www/main.css rename to examples/08-input-handler/www/ui.css diff --git a/examples/08-input-handler/www/app.js b/examples/08-input-handler/www/ui.js similarity index 84% rename from examples/08-input-handler/www/app.js rename to examples/08-input-handler/www/ui.js index ac051262..459530d9 100644 --- a/examples/08-input-handler/www/app.js +++ b/examples/08-input-handler/www/ui.js @@ -53,5 +53,9 @@ function App() { ); } -const root = ReactDOM.createRoot(document.getElementById("root")); +// No mount div in the generated page -- create the container and append it +// to . The script is deferred, so document.body is parsed by now. +const root = ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +); root.render(h(App)); diff --git a/examples/09-hmr/.gitignore b/examples/09-hmr/.gitignore index 9c039c55..b428e492 100644 --- a/examples/09-hmr/.gitignore +++ b/examples/09-hmr/.gitignore @@ -1,3 +1,3 @@ node_modules/ package-lock.json -www/app.js +www/ui.js diff --git a/examples/09-hmr/README.md b/examples/09-hmr/README.md index 2d000165..63bba406 100644 --- a/examples/09-hmr/README.md +++ b/examples/09-hmr/README.md @@ -6,11 +6,11 @@ state — no full page reload, no `vite build` wait. ## How it works -Shiny serves `www/index.html` (which loads `www/app.js` as a module) and the +Shiny serves a `set_react_page()`-generated page (which loads `www/ui.js` as a module) and the reactive WebSocket. A Vite dev server serves your React modules with Fast -Refresh. The dev server writes `www/app.js` as a tiny stub that pulls the HMR +Refresh. The dev server writes `www/ui.js` as a tiny stub that pulls the HMR client + your entry from the dev server; `npm run build` overwrites it with the -real bundle. `index.html` never changes between modes. +real bundle. The served page never changes between modes. Component code lives in `src/App.tsx` (the Fast Refresh boundary). The entry `src/ui.tsx` only mounts it — keep `createRoot()` there, never in a file that @@ -23,7 +23,7 @@ externalized to the shared `window.shinyreact`. ## Develop (two terminals) ```bash -# terminal 1 — Vite dev server (writes www/app.js as a dev stub, serves HMR) +# terminal 1 — Vite dev server (writes www/ui.js as a dev stub, serves HMR) npm install npm run dev @@ -44,7 +44,7 @@ Open the URL Shiny prints. Editing `app.py` reloads via Shiny; editing ## Build for production ```bash -npm run build # writes www/app.js (the real bundle) +npm run build # writes www/ui.js (the real bundle) uv run shiny run app.py ``` diff --git a/examples/09-hmr/src/ui.tsx b/examples/09-hmr/src/ui.tsx index bfef27f9..19a65ee2 100644 --- a/examples/09-hmr/src/ui.tsx +++ b/examples/09-hmr/src/ui.tsx @@ -6,4 +6,8 @@ import App from "./App"; // clean. Editing THIS file triggers a full reload (rare); editing App.tsx does // not. Calling createRoot in a file that also defines components would defeat // Fast Refresh. -ReactDOM.createRoot(document.getElementById("root")!).render(); +// No index.html in this example -- the server page is generated by +// set_react_page() discovery, so the app appends its own mount container. +ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), +).render(); diff --git a/examples/09-hmr/vite-dev-stub.js b/examples/09-hmr/vite-dev-stub.js index 10b481dc..2139b090 100644 --- a/examples/09-hmr/vite-dev-stub.js +++ b/examples/09-hmr/vite-dev-stub.js @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; -// The contents of the dev-mode `www/app.js`. Shiny serves this file statically. +// The contents of the dev-mode `www/ui.js`. Shiny serves this file statically. // It (1) installs the React Fast Refresh preamble that @vitejs/plugin-react // normally injects into the HTML it serves — but here Shiny serves the page, so // we install it ourselves — then (2) dynamically imports Vite's HMR client and @@ -22,7 +22,7 @@ export function makeDevStub(origin, entry) { } // Vite plugin (serve only). On dev-server start it writes `outFile` (e.g. -// www/app.js) as the dev stub, and removes it on shutdown so a later plain +// www/ui.js) as the dev stub, and removes it on shutdown so a later plain // `shiny run` doesn't load a stub pointing at a dead dev server. `vite build` // (apply:"serve" excludes this plugin) overwrites `outFile` with the real bundle. export function shinyreactDevStub({ entry, outFile }) { diff --git a/examples/09-hmr/vite.config.js b/examples/09-hmr/vite.config.js index 0962756a..c0a1dae0 100644 --- a/examples/09-hmr/vite.config.js +++ b/examples/09-hmr/vite.config.js @@ -17,8 +17,8 @@ export default defineConfig(({ command }) => ({ }, plugins: [ react(), - // serve only: writes www/app.js as the dev stub (apply:"serve" inside). - shinyreactDevStub({ entry: ENTRY, outFile: "www/app.js" }), + // serve only: writes www/ui.js as the dev stub (apply:"serve" inside). + shinyreactDevStub({ entry: ENTRY, outFile: "www/ui.js" }), ], resolve: { // One React instance across App.tsx and the bundled shiny-react source. @@ -43,7 +43,7 @@ export default defineConfig(({ command }) => ({ entry: path.resolve(__dirname, ENTRY), formats: ["iife"], name: "HmrExample", - fileName: () => "app.js", + fileName: () => "ui.js", }, rollupOptions: { external: ["react", "react-dom", "react-dom/client"], diff --git a/examples/09-hmr/www/index.html b/examples/09-hmr/www/index.html deleted file mode 100644 index a80fc4b3..00000000 --- a/examples/09-hmr/www/index.html +++ /dev/null @@ -1,2 +0,0 @@ -
- diff --git a/examples/10-bookmarking/README.md b/examples/10-bookmarking/README.md index 14a43fea..2aa87737 100644 --- a/examples/10-bookmarking/README.md +++ b/examples/10-bookmarking/README.md @@ -2,7 +2,7 @@ Demonstrates bookmark restoration in shinyreact: -- `page_react_html()` emits a ` diff --git a/examples/10-bookmarking/www/styles.css b/examples/10-bookmarking/www/ui.css similarity index 100% rename from examples/10-bookmarking/www/styles.css rename to examples/10-bookmarking/www/ui.css diff --git a/examples/10-bookmarking/www/app.js b/examples/10-bookmarking/www/ui.js similarity index 89% rename from examples/10-bookmarking/www/app.js rename to examples/10-bookmarking/www/ui.js index f8247baa..25240c7f 100644 --- a/examples/10-bookmarking/www/app.js +++ b/examples/10-bookmarking/www/ui.js @@ -81,5 +81,9 @@ ); } - ReactDOM.createRoot(document.getElementById("root")).render(h(App)); + // No mount div in the generated page -- create the container and append it + // to . The script is deferred, so document.body is parsed by now. + ReactDOM.createRoot( + document.body.appendChild(document.createElement("div")), + ).render(h(App)); })(); diff --git a/examples/README.md b/examples/README.md index 4c9e8e4d..9be7cb14 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,8 +2,8 @@ Runnable example apps for the `ui.tsx` pattern: the server contains only reactive computation, and the UI is defined in a client-side React codebase -whose entry is conventionally `ui.tsx` (simpler variants like `www/app.js` -for no-build or `src/App.jsx` for Vite + JSX fill the same role). +whose entry is conventionally `ui.tsx` (simpler variants like `www/ui.js` +for no-build or `src/ui.jsx` for Vite + JSX fill the same role). Examples are Python unless noted; [01-hello](01-hello/) also ships an `app.R` showing the same app on the R package. @@ -18,5 +18,5 @@ showing the same app on the R package. | [06-data-frame](06-data-frame/) | Embeds `@render.data_frame` via `ShinyOutput` and `set_react_page()` | | [07-plotly](07-plotly/) | Embeds `@render_plotly` via `ShinyOutput` and `set_react_page()` | | [08-input-handler](08-input-handler/) | `useShinyInput` with `type="shiny.datetime"` — client sends unix seconds; server `input.when()` is a `datetime.datetime` via Shiny's built-in handler | -| [09-hmr](09-hmr/) | React Fast Refresh in dev (Vite dev server alongside Shiny); the `app.py` and no-build `www/app.js` paths reload too | -| [10-bookmarking](10-bookmarking/) | Bookmark restoration: URL query string (or server-stored state) hydrates `useShinyInput` initial values via a head `