You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Deferred question from #217, which used a repo-relative file:../../pkg-js as a
placeholder until the first npm publish. The placeholder works better than
expected, and that is worth taking seriously before we commit to the registry as
the only distribution channel.
Proposal
Ship the npm-installable client (dist-npm/) inside the pip wheel and the R inst/ directory, and let an app install it from its own project environment:
pip install shinyreact / install.packages("shinyreact") would then control both halves — the server and the client the browser runs — from one
dependency declaration.
Why this isn't just Option C again
decisions/2026-08-17-js-distribution.md investigated and rejected Option C
("npm installs from the server package's assets"). Its disqualifier was DX:
file: deps bake machine-specific absolute paths into package.json/lockfiles,
breaking every collaborator, CI runner, and venv/renv rebuild
That is true of the shape it considered — npm install "$(python -m shinyreact npm-path)", which yields an absolute path. The reframing is that modern
project environments are in-project: uv puts the venv at .venv/, renv puts
the library at renv/library/. Both are relative to the app directory, so package.json and the lockfile stay portable.
#217 verified this property empirically for the repo-relative case:
Works offline / air-gapped, and in environments where the npm registry is
proxied or blocked.
The IIFE zero-build tier is untouched — this changes only where the
bundler tier gets its hooks from.
What has to be answered
This is the actual work of the issue; none of these are obviously fatal, but
none are free either.
Path stability..venv/lib/python3.14/site-packages/ embeds the Python
version; renv's library path embeds R version and platform. A lockfile
pinning either breaks on an upgrade. Options: a generated stable symlink, a
setup command that rewrites the path, or a small resolver plugin.
Environments that aren't in-project. conda, system Python, Docker layer
caching, UV_PROJECT_ENVIRONMENT, R_LIBS_USER — all put the library
somewhere no relative path reaches. Supported-when-in-project, documented
otherwise? Or fall back to the registry?
Tooling opacity. A file:/link: dep is invisible to Renovate, npm audit, and npm's cache. Arguably fine — the thing under version management
is then the pip/CRAN package — but it should be a decision, not a surprise.
Ordering.npm install has to run afteruv sync / renv::restore().
A documented step, or a generated script?
Package size. Shipping dist-npm/ (ESM + .d.ts + sourcemap) on top of
today's www/shinyreact.js (205 KB) + CSS, in both the wheel and inst/.
Does it replace npm publication, or sit beside it? A third-party
component library that wants to peer-depend on @posit/shinyreact (Ship shiny-react bindings as npm packages for popular UI frameworks #32) has
no answer except the registry. Most likely outcome: the registry stays the
public story and this becomes the locked-version alternative — which means
maintaining both, and that cost belongs in the decision.
Shinylive is a non-issue
The obvious objection — "an exported app has no node, no npm, no venv layout" —
does not apply. The npm client is a build-time dependency: npm install and vite build run on the developer's machine and produce www/ui.js. shinylive export runs afterwards and ships that already-compiled bundle. The
toolchain and the exported runtime never meet.
If anything the npm tier is the better shinylive story, since with include_runtime=False (#217) the entire client runtime lives inside the app's
own ui.js rather than being resolved from a server-side HTMLDependency —
worth confirming as part of this work rather than assuming.
Refs
decisions/2026-08-17-js-distribution.md (Options C and E), #217, #218, #172, #32.
Deferred question from #217, which used a repo-relative
file:../../pkg-jsas aplaceholder until the first npm publish. The placeholder works better than
expected, and that is worth taking seriously before we commit to the registry as
the only distribution channel.
Proposal
Ship the npm-installable client (
dist-npm/) inside the pip wheel and the Rinst/directory, and let an app install it from its own project environment:pip install shinyreact/install.packages("shinyreact")would then controlboth halves — the server and the client the browser runs — from one
dependency declaration.
Why this isn't just Option C again
decisions/2026-08-17-js-distribution.mdinvestigated and rejected Option C("npm installs from the server package's assets"). Its disqualifier was DX:
That is true of the shape it considered —
npm install "$(python -m shinyreact npm-path)", which yields an absolute path. The reframing is that modernproject environments are in-project: uv puts the venv at
.venv/, renv putsthe library at
renv/library/. Both are relative to the app directory, sopackage.jsonand the lockfile stay portable.#217 verified this property empirically for the repo-relative case:
Nothing machine-specific in either file.
What it buys
protocol handshake (feat: #shinyreact-config tag + protocol handshake, decide JS distribution #198) stops being the thing standing between a stale
client and a confusing bug, and goes back to being a safety net.
NPM_TOKEN(Add NPM_TOKEN secret for @posit/shinyreact publishing #218), no three-registry releasecoordination, no "which of pip/CRAN/npm did I forget to bump".
proxied or blocked.
bundler tier gets its hooks from.
What has to be answered
This is the actual work of the issue; none of these are obviously fatal, but
none are free either.
.venv/lib/python3.14/site-packages/embeds the Pythonversion; renv's library path embeds R version and platform. A lockfile
pinning either breaks on an upgrade. Options: a generated stable symlink, a
setup command that rewrites the path, or a small resolver plugin.
caching,
UV_PROJECT_ENVIRONMENT,R_LIBS_USER— all put the librarysomewhere no relative path reaches. Supported-when-in-project, documented
otherwise? Or fall back to the registry?
file:/link:dep is invisible to Renovate,npm audit, and npm's cache. Arguably fine — the thing under version managementis then the pip/CRAN package — but it should be a decision, not a surprise.
npm installhas to run afteruv sync/renv::restore().A documented step, or a generated script?
dist-npm/(ESM +.d.ts+ sourcemap) on top oftoday's
www/shinyreact.js(205 KB) + CSS, in both the wheel andinst/.component library that wants to peer-depend on
@posit/shinyreact(Ship shiny-react bindings as npm packages for popular UI frameworks #32) hasno answer except the registry. Most likely outcome: the registry stays the
public story and this becomes the locked-version alternative — which means
maintaining both, and that cost belongs in the decision.
Shinylive is a non-issue
The obvious objection — "an exported app has no node, no npm, no venv layout" —
does not apply. The npm client is a build-time dependency:
npm installandvite buildrun on the developer's machine and producewww/ui.js.shinylive exportruns afterwards and ships that already-compiled bundle. Thetoolchain and the exported runtime never meet.
If anything the npm tier is the better shinylive story, since with
include_runtime=False(#217) the entire client runtime lives inside the app'sown
ui.jsrather than being resolved from a server-sideHTMLDependency—worth confirming as part of this work rather than assuming.
Refs
decisions/2026-08-17-js-distribution.md(Options C and E), #217, #218, #172,#32.