Skip to content

v0.26.0

Choose a tag to compare

@joelmoss joelmoss released this 07 Oct 11:21
· 69 commits to master since this release

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::ReactComponentable and @rubygems/proscenium/react-manager are 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-gnu and aarch64-linux-gnu. x86_64-linux and aarch64-linux are no longer published. A bare x86_64-linux matched glibc and musl alike, so Alpine installed a glibc library it could not load. If your lockfile pins one, run bundle 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 a dlopen failure. 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 install writes 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 to pnpm-workspace.yaml, package.json and .gitignore are spliced in, keeping your comments and style, and shown as a diff first. It replaces pnpm install or bun install.
  • install --frozen, for CI, changes nothing you commit and fails if anything is out of date. --production keeps 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 (also inspect) 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 check checks a gem directory or a built .gem against the author contract, with no bundle needed.
  • psm is a shorter name for the same command: bundle exec psm install.
  • Errors explain themselves. Every failure has a PSM-E code and says what happened and how to fix it. --json emits 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.dependencies to "true", or the app opts it in under gemOverrides. An app that has not adopted contexts resolves exactly as before.

Windows

  • An x64-mingw-ucrt gem is now built and published. CI runs the Go, Ruby and bun test suites on Windows.

Fixes

Security and robustness

  • A Go panic fails the build instead of killing the Ruby process. There was no recover in the shipped Go, so a panic in a plugin took down the Puma worker, or the daemon behind bun test.
  • A double-encoded request path is no longer checked as one file and built as another. %252e%252e was built as a literal %2e%2e segment but probed on disk as ...
  • Precompile refuses an OutputDir outside the app root. Compile deletes the output directory before building, and a value such as public/../.., 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 @mixin inside 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 includes node_modules/@scope/....
  • css_module accepts Arrays, and ignores nil, false and blank names. Any other non-String name raises TransformError naming it, not NoMethodError.
  • A CSS module imported from JS passes on its build's warnings.
  • A form feed is treated as CSS whitespace, so .a\2E\fb keeps its class a.b.
  • A @define-mixin anywhere but a file's root now warns.

Side loading

  • Collection partials are side loaded. render partial: 'x', collection: items never side loaded the partial's JS and CSS.
  • Side load options no longer leak between renders and requests. A Proc in sideload_assets_options ran once per process, and its first answer became every request's.
  • sideload_assets in a partial rendered with a block applies to that partial, not its caller.
  • A zero-argument lambda works as a sideload_assets option (#115).
  • A repeat side load of the same asset notifies once, instead of once per render.

Environment variables

  • RAILS_ENV and NODE_ENV are always defined. Configuring env vars without RAILS_ENV among them failed every build.
  • Env var values holding a quote or newline are escaped. Before, they failed every build.
  • A blank RAILS_ENV or NODE_ENV keeps the default.
  • env_vars accepts an Array, Symbols or a single String.
  • A name that is not a JS identifier raises ArgumentError naming 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 @rubygems with 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.import accepts 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 latest image held. xgo is now pinned to the go-1.27.1 image. 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