Repository navigation
v0.26.0
Breaking changes
- Rails 7.2 and Ruby 3.4 are now the minimums. Rails 7.1 and Ruby 3.3 are no longer supported.
Proscenium::ReactComponentableand@rubygems/proscenium/react-managerare gone. Proscenium still bundles JSX, but no longer ships a React integration. An app that used them needs its own component manager.- The Linux gems now name their libc:
x86_64-linux-gnuandaarch64-linux-gnu.x86_64-linuxandaarch64-linuxare no longer published. A barex86_64-linuxmatched glibc and musl alike, so Alpine installed a glibc library it could not load. If your lockfile pins one, runbundle lock --add-platform x86_64-linux-gnu. RubyGems 3.3.22 or later is now required, because older versions cannot tell the variants apart. - There are no musl gems. A host that matches no platform gem now gets
Proscenium::Builder::UnsupportedPlatform, naming its platform, instead of adlopenfailure. Go's c-shared libraries cannot be loaded by musl (golang/go#54805). - The platform-less gem no longer ships a compiled library. The released 0.25.2 carried an x86-64 Linux ELF in it.
- The in-engine npm registry and its route are removed. Gem dependency contexts (below) replace them. Proscenium no longer depends on
json.
Features
RubyGem NPM dependencies
A gem can now ship frontend code that imports npm packages, declared in a package.json of its own, and an app installs them with its own package manager. See RubyGem NPM dependencies and the gem author guide.
bundle exec proscenium installwrites a small, dependency-only package.json for each participating gem under.proscenium/packages/, registers them with your package manager, and runs its install. The changes topnpm-workspace.yaml,package.jsonand.gitignoreare spliced in, keeping your comments and style, and shown as a diff first. It replacespnpm installorbun install.install --frozen, for CI, changes nothing you commit and fails if anything is out of date.--productionkeeps the committed context of a gem in an excluded group.- pnpm 11 (from 11.11.0) and 12, and Bun 1.4, are supported. Yarn and npm are refused before anything is written.
bundle exec proscenium doctor(alsoinspect) reports each gem's source and context status, and lists every problem it finds with its fix. It exits 4 when it finds one.bundle exec proscenium gem checkchecks a gem directory or a built.gemagainst the author contract, with no bundle needed.psmis a shorter name for the same command:bundle exec psm install.- Errors explain themselves. Every failure has a
PSM-Ecode and says what happened and how to fix it.--jsonemits stable, versioned, newline-delimited events. - A gem's bare imports resolve from its own context, so it gets the versions it declares, while a shared peer such as React keeps the app's single copy.
- The engine refuses to build while a context is stale or an install is running, and names the fix. In development it notices changes to the files the contexts come from within a second, without a restart.
- Opt-in at both ends. A gem takes part only when its gemspec sets
proscenium.dependenciesto"true", or the app opts it in undergemOverrides. An app that has not adopted contexts resolves exactly as before.
Windows
- An
x64-mingw-ucrtgem is now built and published. CI runs the Go, Ruby andbun testsuites on Windows.
Fixes
Security and robustness
- A Go panic fails the build instead of killing the Ruby process. There was no
recoverin the shipped Go, so a panic in a plugin took down the Puma worker, or the daemon behindbun test. - A double-encoded request path is no longer checked as one file and built as another.
%252e%252ewas built as a literal%2e%2esegment but probed on disk as... - Precompile refuses an
OutputDiroutside the app root. Compile deletes the output directory before building, and a value such aspublic/../.., or an absolute path, used to escape the root. - One stylesheet's mixins can no longer expand without limit (#150). A chain where each mixin includes the one before it twice could pin a worker for minutes from a few hundred bytes of CSS. A parse now expands at most 100,000 mixins, inserting at most 4 MiB of definitions, nested at most 100 deep.
- A build sends Ruby at most 100 errors and 100 warnings, with each message cut at 10 KiB. A few kilobytes of CSS could send Ruby over 100 MB of diagnostics before.
- Malformed CSS no longer hangs or loops. This covers an unterminated
@mixin, two mixins ending in-->that include each other, and a@mixininside its own definition. Malformed CSS is returned unchanged for esbuild to report at the right line, instead of being re-rendered wrongly. - A missing file in a gem, or an unreadable locales directory, no longer exposes absolute paths in a build error that can reach the browser, logs and error trackers.
- A chunk is never served half-written. This was also released in 0.25.3.
CSS and CSS modules
- A CSS module class name now matches its stylesheet for paths holding
@,+or a space. Ruby and Go built the class name's suffix by different rules, so the style silently did not apply. This includesnode_modules/@scope/.... css_moduleaccepts Arrays, and ignoresnil,falseand blank names. Any other non-String name raisesTransformErrornaming it, notNoMethodError.- A CSS module imported from JS passes on its build's warnings.
- A form feed is treated as CSS whitespace, so
.a\2E\fbkeeps its classa.b. - A
@define-mixinanywhere but a file's root now warns.
Side loading
- Collection partials are side loaded.
render partial: 'x', collection: itemsnever side loaded the partial's JS and CSS. - Side load options no longer leak between renders and requests. A Proc in
sideload_assets_optionsran once per process, and its first answer became every request's. sideload_assetsin a partial rendered with a block applies to that partial, not its caller.- A zero-argument lambda works as a
sideload_assetsoption (#115). - A repeat side load of the same asset notifies once, instead of once per render.
Environment variables
RAILS_ENVandNODE_ENVare always defined. Configuring env vars withoutRAILS_ENVamong them failed every build.- Env var values holding a quote or newline are escaped. Before, they failed every build.
- A blank
RAILS_ENVorNODE_ENVkeeps the default. env_varsaccepts an Array, Symbols or a single String.- A name that is not a JS identifier raises
ArgumentErrornaming it, in every environment.
Resolution and gems
- An SVG imported from JSX is parsed as markup, not compiled as code. It no longer imports React, and an SVG with more than one child element now renders them all.
- An alias is applied before resolving the gem when unbundled, so an alias onto another gem resolves to that gem.
- Gem paths map to
@rubygemswith a prefix match, not a regex, so a gem path holding+or(maps correctly. - A file under nested gem roots belongs to the innermost gem in both Ruby and Go, so the browser no longer loads it twice under two URLs.
Importer.importaccepts a remote CSS module URL outside production.- The resolver follows a manifest loaded or reset after a resolve.
- Strings from Go are tagged UTF-8.
- The gem lookup run for every module no longer allocates a string per gem. It now allocates nothing for a path outside every gem, the common case.
i18n
- A locales directory that cannot be read fails the build. Before, every translation silently went missing for the life of the process.
Build
- Go 1.27.1 on every platform. The macOS and Windows libraries were built with Go 1.25.7, which no longer gets security fixes. The Linux ones were built with whatever xgo's floating
latestimage held. xgo is now pinned to thego-1.27.1image.golang.org/x/*,pprof, ginkgo and gomega are updated. - esbuild-internal is now v0.28.2-9b682755. It recovers panics in plugin callbacks, writes output atomically, and retries refused renames.
- Every gem is updated, Rails 8.1.4 included.
- Releases run from a GitHub Actions workflow with trusted publishing. Every platform gem is installed from a generated index and loaded before anything publishes.
Full changelog: v0.25.2...v0.26.0