Skip to content

Vite 8, Rails 8#607

Open
andriytyurnikov wants to merge 11 commits intoElMassimo:mainfrom
rubakas:upstream-vite-8
Open

Vite 8, Rails 8#607
andriytyurnikov wants to merge 11 commits intoElMassimo:mainfrom
rubakas:upstream-vite-8

Conversation

@andriytyurnikov
Copy link
Copy Markdown

@andriytyurnikov andriytyurnikov commented Apr 27, 2026

Summary

Brings vite_ruby and the workspace plugins into compatibility with Vite 8 (Rolldown-powered) and refreshes the CI matrix and Ruby framework targets. Also fixes two latent runtime bugs, a sourcemap-in-production security default, and a test-determinism issue surfaced during the upgrade.

Vite 8 compatibility

  • vite-plugin-ruby and vite-plugin-rails peer-dep vite >=8.0.0.
  • examples/rails: gate the @administrator/ alias and server.fs.allow entries on ADMINISTRATOR_ASSETS_PATH actually being set. The previous code used a TypeScript non-null assertion (process.env.X!), so when the env var was unset, server.fs.allow received [undefined]. Vite ≤7 silently dropped non-string entries; Vite 8 strict-validates them and crashes with TypeError [ERR_INVALID_ARG_TYPE]: paths[1] must be of type string.

Bug fixes

  • Closes Sourcemaps enabled in production by default (security issue) #588vite-plugin-ruby: stop forcing build.sourcemap = !isLocal. The inverted default was publishing .js.map files in production (exposing application source) and disabling them in development (breaking debugging). Drop the override; Vite's safe default of false now applies in every mode, and consumers opt in via their own build.sourcemap config when they actually want sourcemaps.
  • vite-plugin-ruby dev middleware: return after res.end() in the /index.html 404 fallback so downstream middleware doesn't write to a closed response.
  • vite_ruby: synchronize dev_server_running? cache with a Mutex. The prior read of @running could race with the write of @running_checked_at and raise NoMethodError on a cold concurrent call from a multi-threaded server (Puma).

Test determinism

  • Fix vite-plugin-rails/example's build script to pin NODE_ENV=production. Without it, vitest's default NODE_ENV=test leaked into the test-driven build, shipping Vue's development bundle (with Object.freeze, prop validators, and @vitejs/plugin-vue's absolute-path __file injection — ~27 KB extra and per-machine paths).
  • build.spec.ts: strip 8-char content hashes and replace SRI values with a <integrity> placeholder so the snapshot survives routine dep bumps that change chunk bytes without changing manifest shape.

CI / tooling

  • Expand the Ruby matrix to 3.3 / 3.4 (gemspec floor) and Rails 7.0–8.1 + edge.
  • Drop Ruby 3.1 / 3.2 (below gemspec floor — bundle install would fail).
  • Bump setup-node to 22 (Vite 8 requires Node 20.19+/22.12+).
  • JS workflow: test both vite-plugin-ruby and vite-plugin-rails, build all workspace packages, install at workspace root.
  • Enable corepack so the test_app's yarn-driven setup finds yarn.
  • Regenerate test/test_app/yarn.lock for the Vite 8 bump.

Related (likely resolved as side effects)

Test plan

  • pnpm -r build — both workspace packages build clean.
  • pnpm -C vite-plugin-ruby test --run and pnpm -C vite-plugin-rails test --run pass.
  • Local examples/rails vite build succeeds with ADMINISTRATOR_ASSETS_PATH pointing at example_engine/app/frontend.
  • Confirm the Ruby matrix passes in your CI.

- Tighten vite peer dep to >=8.0.0 in vite-plugin-ruby and vite-plugin-rails.
- Bump vitest 0.34 to 4.1 in both plugin packages.
- Bump TypeScript ^5 to ^6, tsdown to ^0.21.10, @types/node ^22 to ^24, rollup ^4.2 to ^4.60, plus minor bumps for obug, tinyglobby, vite-plugin-full-reload, @types/debug.
- Tighten vite-plugin-rails workspace dep on vite-plugin-ruby to ^5.2.1 so the rolldown fix is required.
- Bump vite to ^8 in test/test_app and examples/{hanami_bookshelf,padrino_blog_tutorial}; align hanami engines with current Node LTS.
- Relax examples/rails engines from exact node:22/pnpm:9 to >=22/>=9 so it runs on current Node 24 LTS.
- Refresh build.spec.ts hashes and snapshot for Rolldown chunk output.
- Add gemfiles/Gemfile-rails.8.1.x to the CI matrix; Rails 8.1 is the current stable and examples/rails already locks to 8.1.x.
- Bump padrino in examples/padrino_blog_tutorial from 0.15.0 to 0.16.1.
- Bump examples/hanami_bookshelf to Ruby 3.4.5 (Gemfile and .ruby-version), matching examples/rails.
- Raise required_ruby_version to >= 3.3 across all gemspecs (vite_ruby, vite_rails, vite_hanami, vite_padrino, vite_plugin_legacy, vite_rails_legacy). Ruby 3.2 reached EOL in March 2026.
vite_rails_legacy targets Rails 4 (railties < 5), EOL since 2017. Its
actual audience is on legacy Ruby, so the previous commit's bump to
3.3 would orphan them. Restore the original floor.
The example used a TypeScript non-null assertion on
process.env.ADMINISTRATOR_ASSETS_PATH that is unset at build time when
no Rails engine is wired up, leaving [undefined] in server.fs.allow
and an alias resolving to "undefined/...". Vite ≤7 silently tolerated
those undefined entries; Vite 8 strictly path-resolves server.fs.allow
and crashes with "paths[1] argument must be of type string".

Make both the alias and the fs.allow entry conditional on the env var
actually being present, so the build works whether or not the engine
fixture is wired up.
ruby.yml:
- Drop Ruby 3.1 and 3.2 from the stable matrix (gemspec floor is 3.3,
  bundle install would fail on those).
- Add Ruby 3.4 to the stable matrix.
- Promote Gemfile-rails.8.0.x to stable; add Gemfile-rails.8.1.x.
- Add Ruby 4.0 as experimental against 8.1.x and edge.
- Bump setup-node from 20 to 22 (Vite 8 requires Node 20.19+/22.12+).

js.yml:
- Bump pnpm action-setup from 9 to 10.
- Test on Node 22 and 24 instead of just 20.
- Run the build + tests for vite-plugin-rails as well as vite-plugin-ruby.
- Install at workspace root with --frozen-lockfile so workspace deps resolve.
- Pass --run to test step so vitest 4 doesn't enter watch mode.
js.yml: vite-plugin-rails tests resolve workspace vite-plugin-ruby
through its built dist/, so the matrix job for rails must also have
vite-plugin-ruby built. Replace the per-package build step with
pnpm -r build to build every workspace package before tests run.

ruby.yml: test/test_app uses yarn.lock and the rake task that exercises
yarn install --frozen-lockfile no longer finds yarn on GitHub's Node 22
image. corepack ships with Node and provides yarn/pnpm shims; enable
it so the test can install test_app's deps.
The vite ^5 to ^8 bump in test/test_app left yarn.lock stale, so
RakeTasksTest's exec of yarn install --frozen-lockfile failed in CI.
- fix(vite-plugin-ruby): return after res.end() in dev /index.html 404
  fallback so downstream middleware doesn't write to a closed response.
- fix(vite_ruby): guard dev_server_running? cache with a Mutex; the prior
  read of @running before @running_checked_at was set could raise on a
  cold concurrent call from a multi-threaded server.
- chore(ci): pin qltysh/qlty-action/coverage to v2.2.0 SHA and tighten
  the upload guard to (push && refs/heads/main).
- chore: drop @vitest/coverage-v8 and the JS coverage upload path; the
  per-package coverage scripts and vitest coverage blocks are removed.
- chore(rubocop): bump TargetRubyVersion / standard inherit_gem 3.0 -> 3.3
  to match the CI matrix floor.
- chore(test): remove dead RUBY_VERSION 2.4 branch in dev_server_proxy_test.
build.spec.ts pinned exact 8-char content hashes (e.g. assets/main-Ddvw3iap.js)
in both the file-list assertion and the snapshot. Any routine bump of Vite,
Rolldown, Vue, or sass that changes a chunk by even a byte forces a manual
hash regeneration even though the manifest shape is unchanged.

Strip the 8-char hash from filenames before asserting, and replace the
sub-resource integrity value with a `<integrity>` placeholder in the
snapshot. The test now fails on structural changes (entry → file mapping,
imports list, css list, etc.) but absorbs byte-level churn from upstream
dependency updates.
vitest sets NODE_ENV=test by default, and build.spec.ts spawns
`npm run build` inheriting that env. Vite respects an externally-set
NODE_ENV — it doesn't overwrite it from --mode production — so the
test-driven build was producing Vue's *development* bundle (Object.freeze,
__file injection with absolute paths, prop validators, dev-only warnings).

Local `npm run build` (no vitest in the loop) shipped Vue's production
bundle. The two outputs diverged by ~27 KB and produced different chunk
hashes — the symptom we previously misdiagnosed as cross-platform drift.

Setting NODE_ENV=production in the build script makes the build match
production semantics regardless of how it's invoked, so the test exercises
the same code real users ship.
ElMassimo#588)

The plugin was setting `build.sourcemap = !isLocal`, which inverted Vite's
safe default:
  - production / staging → sourcemap=true   (publishes .js.map publicly,
                                              exposing application source code)
  - development / test   → sourcemap=false  (breaks debugging where you'd
                                              actually want sourcemaps)

Drop the override entirely. Vite's default (`false`) now applies in every
mode, and consumers opt in to sourcemaps via `build.sourcemap` in their
own vite.config when they actually want them — for production via
external upload to e.g. Sentry, or for development debugging.

The unit test in vite-plugin-ruby/tests/index.spec.ts had been encoding
the inverted behavior; updated to assert `sourcemap` is no longer set by
the plugin. The integration test in vite-plugin-rails drops the `.map`
entries from the expected file list since the example build no longer
emits them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sourcemaps enabled in production by default (security issue)

1 participant