rails_vite 0.3.0.rc1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e7210773488e0deadb365f2fb2ab6782d1b347d77ca2f63007eada5cbe6c59f
4
- data.tar.gz: 687c142b64c7eb24431696f56c105be88b06c68686689d40005f773f8d70aebf
3
+ metadata.gz: fc4469f0512e076ff2acb18a110d7b346082837a0b46ef4d7369907df851883d
4
+ data.tar.gz: f6e624fcb760ca2b5d61c2a7954d98c491f3150a44a735cc1d62de21b2692763
5
5
  SHA512:
6
- metadata.gz: d4a84d843acb2feb2c9f80d26386b5071ca8c4a8093332d8e71880cddbbf608351709f30fb7cef147b25f7114818eb2b09b8d35ea0808affdf19b67594574446
7
- data.tar.gz: e483f34f03fed33786db337c84b5cb290e7ce2ad65e3c73b7294f5e75b77aabffe934dac00b2cb55d79517b65181b699b43f7c4ef9feedadf2ea1190ec1781a6
6
+ metadata.gz: c46090d913dd40e3fd9e9c78b3010917463ce9a3fc297c810d7f2f5fd7418298299d1a8e7c165bb96e44a4685b611323ce173f1ab26cda04d98d8f3584298691
7
+ data.tar.gz: 2f092d13f0a7d3ff5d25ada3faf133d77fc4e660e376ff5c6301168fd68980b0b84b4cfdee1fbdaf6f8d18db49b8cdc536ca1a080b03a493309bad270402acdf
data/CHANGELOG.md CHANGED
@@ -5,7 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog],
6
6
  and this project adheres to [Semantic Versioning].
7
7
 
8
- ## rails_vite@0.3.0.rc1 / rails-vite-plugin@0.3.0-rc.1 - 2026-10-11
8
+ ## rails_vite@0.4.0 / rails-vite-plugin@0.4.0 - 2026-11-12
9
+
10
+ ### Added
11
+
12
+ - `rake vite:auto_build` builds stale assets the way auto build does, so a parallel test runner can build once before it starts its workers. `RailsVite::AutoBuild.build_if_stale` does the same from Ruby. Both do nothing when assets are fresh, and fail with `RailsVite::BuildError` when the build fails (#59) ([@brodienguyen])
13
+
14
+ ### Changed
15
+
16
+ - `RailsVite.digest` is computed once outside development and test, instead of hashing the manifest file on every call (#57) ([@thomasklemm])
17
+ - A failed auto build raises `RailsVite::BuildError` instead of logging and serving the previous assets, and a process does not run it again until a source changes or `rake vite:build` writes a fresh manifest (#59) ([@brodienguyen])
18
+
19
+ ### Fixed
20
+
21
+ - `bin/rails test` keeps the test environment's `eager_load` setting. The `test:prepare` build loaded the app through Rake's `environment` task, which turned eager loading off for the whole run (#62) ([@skryukov])
22
+ - Auto build no longer walks the whole Rails root when Vite's `root` is the source directory (`prependSourceDirToEntries: false`). `log/` and `tmp/` made assets look stale on every request. The plugin now also writes the source directory to its metadata as `sourcePath`, and auto build watches it. Upgrade `rails-vite-plugin` together with the gem: metadata from older plugin versions has no `sourcePath`, so auto build falls back to `sourceDir` (#61) ([@mdesantis])
23
+ - Auto builds hold a file lock in `tmp/`, so parallel test workers and a server beside a test run no longer build into the same directory at once. The first process to find stale assets builds them, and the others wait and reuse that build (#59) ([@brodienguyen])
24
+
25
+ ## rails_vite@0.3.0 / rails-vite-plugin@0.3.0 - 2026-10-11
9
26
 
10
27
  ### Added
11
28
 
@@ -23,7 +40,7 @@ and this project adheres to [Semantic Versioning].
23
40
  - Vite asset URLs are built with Rails' `path_to_asset`, so `asset_host` works as it does for other Rails assets: procs that take the request, `%d` hosts and per-controller hosts. Mailers now use `config.action_mailer.asset_host` (or `config.asset_host`) instead of the controller's. With `relative_url_root`, tags now include the prefix; set Vite's `base` to match (#36) ([@skryukov])
24
41
  - `rake vite:build` loads the Rails environment first, so `config.rails_vite` settings from initializers (such as `vite_executable`) also apply to it and to `bin/rails test` (#42) ([@cole-robertson])
25
42
  - The install generator writes `vite dev` instead of `vite` to `Procfile.dev` (#42) ([@cole-robertson])
26
- - **Breaking:** `vite:build` (and therefore `assets:precompile`) runs the package.json `build` script when one exists, so `"build": "vite build && vite build --ssr"` produces both client and SSR bundles on deploy. Apps without a `build` script keep the bare `vite build`. If package.json already has a `build` script (e.g. one left over from jsbundling-rails), make sure it builds with Vite before upgrading. Test builds and auto-builds always use the bare command, since their extra flags (`--mode test`, `--logLevel warn`) would only reach the last command of a compound script (#38) ([@skryukov])
43
+ - **Breaking:** `vite:build` (and therefore `assets:precompile`) runs the package.json `build` script when one exists, so `"build": "vite build && vite build --ssr"` produces both client and SSR bundles on deploy. Apps without a `build` script keep the bare `vite build`. If package.json already has a `build` script (e.g. one left over from jsbundling-rails), change it to `"build": "vite build"` or remove it before upgrading; otherwise precompile succeeds without Vite assets and production raises `RailsVite::MissingManifestError`. The install generator warns about such a script. Test builds and auto-builds always use the bare command, since their extra flags (`--mode test`, `--logLevel warn`) would only reach the last command of a compound script (#38) ([@skryukov])
27
44
 
28
45
  ### Fixed
29
46
 
@@ -128,6 +145,8 @@ and this project adheres to [Semantic Versioning].
128
145
  [@cole-robertson]: https://github.com/cole-robertson
129
146
  [@madogiwa0124]: https://github.com/madogiwa0124
130
147
  [@janko]: https://github.com/janko
148
+ [@thomasklemm]: https://github.com/thomasklemm
149
+ [@mdesantis]: https://github.com/mdesantis
131
150
 
132
151
  [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
133
152
  [Semantic Versioning]: https://semver.org/spec/v2.0.0.html
data/README.md CHANGED
@@ -48,7 +48,7 @@ bundle install
48
48
  bin/rails generate rails_vite:install
49
49
  ```
50
50
 
51
- This creates `vite.config.ts`, installs dependencies, and updates your layout.
51
+ This creates `vite.config.ts`, installs dependencies, and replaces `javascript_include_tag "application"` and `stylesheet_link_tag "application"` in your layout with `vite_tags`. Other layouts, such as the Rails 8 default, need it by hand: replace `javascript_importmap_tags` with `<%= vite_tags "application" %>`.
52
52
 
53
53
  Start development:
54
54
 
@@ -374,7 +374,15 @@ Rails.application.config.rails_vite.auto_build = false
374
374
 
375
375
  By default, auto build is enabled in development and test (`Rails.env.local?`).
376
376
 
377
- Note: for parallel test runners, disable auto build and use `rake vite:build` before the suite instead.
377
+ Auto builds hold a file lock in `tmp/`, so parallel test workers and a server beside a test run never build at once: the first process to find stale assets builds them, and the others wait and reuse that build. A failed build raises `RailsVite::BuildError`, and a process does not retry it until a source changes.
378
+
379
+ A parallel test runner can build once before it starts its workers, so no worker spends its first test waiting for the build. `rake vite:auto_build` runs the same check, lock and build as the middleware: it builds only when sources changed, does nothing while the dev server runs or with auto build off, and fails with `RailsVite::BuildError` when the build fails. Run it in the environment the workers use:
380
+
381
+ ```bash
382
+ RAILS_ENV=test bin/rails vite:auto_build && bundle exec parallel_rspec
383
+ ```
384
+
385
+ From Ruby, for example in a script that starts the workers itself, call `RailsVite::AutoBuild.build_if_stale`.
378
386
 
379
387
  ## CDN
380
388
 
@@ -431,6 +439,7 @@ Defaults match the plugin defaults — no config needed if you follow convention
431
439
  | Task | Description |
432
440
  |------|-------------|
433
441
  | `rake vite:build` | Build assets for production |
442
+ | `rake vite:auto_build` | Build assets only if sources changed, as [auto build](#auto-build) does |
434
443
  | `rake vite:install` | Install JavaScript dependencies |
435
444
  | `rake vite:clobber` | Remove `public/vite/` |
436
445
 
@@ -533,7 +542,7 @@ In development, jsbundling mode writes `tmp/rails-vite.json` — the same file t
533
542
  ```json
534
543
  // package.json — replace vite-plugin-ruby with rails-vite-plugin
535
544
  - "vite-plugin-ruby": "^5.1.1"
536
- + "rails-vite-plugin": "^0.2.0"
545
+ + "rails-vite-plugin": "^0.4.0"
537
546
  ```
538
547
 
539
548
  ### 2. Replace `vite.config.ts`
@@ -28,6 +28,15 @@ module RailsVite
28
28
  end
29
29
  end
30
30
 
31
+ def warn_about_build_script
32
+ return unless File.exist?("package.json")
33
+
34
+ script = JSON.parse(File.read("package.json")).dig("scripts", "build")
35
+ return if script.blank? || script.match?(/\b(vite|vp)\b/)
36
+
37
+ say %(package.json has a "build" script that assets:precompile will run instead of `vite build`: #{script}. Change it to "vite build" or remove it.), :yellow
38
+ end
39
+
31
40
  def create_vite_config
32
41
  template "vite.config.ts.tt", @esm_package ? "vite.config.ts" : "vite.config.mts"
33
42
  end
@@ -1,3 +1,4 @@
1
+ require "fileutils"
1
2
  require "find"
2
3
 
3
4
  module RailsVite
@@ -5,6 +6,17 @@ module RailsVite
5
6
  NEVER = Time.at(0).freeze
6
7
  SOURCE_CHECK_INTERVAL = 2 # seconds
7
8
 
9
+ # Auto build runs when it is on and no dev server serves the assets.
10
+ def self.enabled?(config)
11
+ config.auto_build? && !config.dev_server_running?
12
+ end
13
+
14
+ # Builds stale assets outside a request, as the middleware does on the
15
+ # first one; for example, before a test runner starts its workers.
16
+ def self.build_if_stale(config = RailsVite.config)
17
+ new(nil, config).build_if_stale if enabled?(config)
18
+ end
19
+
8
20
  def initialize(app, config)
9
21
  @app = app
10
22
  @config = config
@@ -14,30 +26,67 @@ module RailsVite
14
26
  end
15
27
 
16
28
  def call(env)
17
- build! if stale?
29
+ build_if_stale
18
30
  @app.call(env)
19
31
  end
20
32
 
33
+ # Builds the assets when a source is newer than the manifest, or waits for
34
+ # another process's build. Raises BuildError when the build fails.
35
+ def build_if_stale
36
+ build! if stale?
37
+ end
38
+
21
39
  private
22
40
 
23
41
  def build!
24
42
  @mutex.synchronize do
25
43
  return unless stale?
26
44
 
27
- # Build quietly: vite's per-build summary is noise on every auto-build
28
- # (e.g. each local system-test run). Warnings and errors still stream.
29
- Rails.logger.error("rails-vite: build failed") unless system(RailsVite::Tasks.build_env, "#{RailsVite::Tasks.build_command} --logLevel warn", chdir: Rails.root)
45
+ with_build_lock do
46
+ # Another process may have finished a build while this one waited.
47
+ return unless stale?
48
+
49
+ sources = compute_latest_source_mtime
50
+ raise BuildError.new(skipped: true) if @failed_sources == sources
51
+
52
+ # Build quietly: vite's per-build summary is noise on every auto-build
53
+ # (e.g. each local system-test run). Warnings and errors still stream.
54
+ next if system(RailsVite::Tasks.build_env, "#{RailsVite::Tasks.build_command} --logLevel warn", chdir: Rails.root)
55
+
56
+ # Remember the failure for these sources, so this process does not
57
+ # rebuild them until a source changes.
58
+ @failed_sources = sources
59
+ raise BuildError
60
+ end
61
+ @failed_sources = nil
30
62
  @cached_source_mtime = nil
31
63
  @source_mtime_checked_at = nil
32
64
  end
33
65
  end
34
66
 
67
+ # Serializes builds across processes (parallel test workers, a server
68
+ # beside a test run): Vite empties the build directory first, so the lock
69
+ # lives outside it.
70
+ def with_build_lock
71
+ path = Rails.root.join("tmp", "rails-vite-build-#{@config.build_dir}.lock")
72
+ FileUtils.mkdir_p(path.dirname)
73
+ File.open(path, File::RDWR | File::CREAT, 0o644) do |lock|
74
+ lock.flock(File::LOCK_EX)
75
+ yield
76
+ end
77
+ end
78
+
35
79
  def stale?
36
80
  # The manifest file's mtime is our persisted "last build" timestamp: it
37
81
  # lives on disk, so freshness survives process restarts (e.g. a new Rails
38
- # process per local system-test run) without any extra state.
39
- !File.exist?(@config.manifest_path) ||
40
- latest_source_mtime > File.mtime(@config.manifest_path)
82
+ # process per local system-test run) without any extra state. A missing
83
+ # manifest is stale, including one another process's build has just
84
+ # removed: Vite empties the build directory first, so it can vanish
85
+ # between any check and the read. Stale takes the build lock, which
86
+ # waits for that build and then checks again.
87
+ latest_source_mtime > File.mtime(@config.manifest_path)
88
+ rescue Errno::ENOENT
89
+ true
41
90
  end
42
91
 
43
92
  def latest_source_mtime
@@ -52,7 +101,7 @@ module RailsVite
52
101
 
53
102
  def compute_latest_source_mtime
54
103
  latest = NEVER
55
- source_path = Rails.root.join(@config.source_dir).to_s
104
+ source_path = Rails.root.join(@config.source_path).to_s
56
105
 
57
106
  Find.find(source_path, *auto_build_path_matches) do |path|
58
107
  next unless File.file?(path)
@@ -34,6 +34,10 @@ module RailsVite
34
34
  plugin_meta["sourceDir"] || "app/javascript"
35
35
  end
36
36
 
37
+ def source_path
38
+ plugin_meta["sourcePath"].presence || source_dir
39
+ end
40
+
37
41
  def entrypoints_dir
38
42
  plugin_meta["entrypointsDir"]
39
43
  end
@@ -44,7 +48,7 @@ module RailsVite
44
48
  end
45
49
 
46
50
  # Paths or globs relative to Rails.root that auto build checks for changes,
47
- # in addition to source_dir. Directories are walked recursively.
51
+ # in addition to source_path. Directories are walked recursively.
48
52
  def auto_build_paths
49
53
  @auto_build_paths ||= [
50
54
  "vite.config.*",
@@ -6,7 +6,7 @@ module RailsVite
6
6
 
7
7
  initializer "rails_vite.auto_build", after: :load_config_initializers do |app|
8
8
  vite_config = RailsVite.config
9
- if vite_config.auto_build? && !vite_config.dev_server_running?
9
+ if RailsVite::AutoBuild.enabled?(vite_config)
10
10
  app.middleware.use RailsVite::AutoBuild, vite_config
11
11
  end
12
12
  end
@@ -12,6 +12,19 @@ module RailsVite
12
12
  end
13
13
  end
14
14
 
15
+ class BuildError < Error
16
+ # skipped: Vite did not run because these sources already failed to build
17
+ # in an earlier request, so its errors are not in this output.
18
+ def initialize(skipped: false)
19
+ message = if skipped
20
+ "Vite auto build failed earlier for these sources and is not retried until a source changes or the app restarts. Run `rake vite:build` to see its errors."
21
+ else
22
+ "Vite auto build failed; its errors are in the output above. It retries once a source changes, or run `rake vite:build`."
23
+ end
24
+ super(message)
25
+ end
26
+ end
27
+
15
28
  class MissingEntryError < Error
16
29
  def initialize(name, path)
17
30
  super("Entry \"#{name}\" not found in Vite manifest at #{path}.")
@@ -32,7 +32,11 @@ module RailsVite
32
32
  end
33
33
 
34
34
  def digest
35
- Digest::MD5.file(@path).hexdigest
35
+ if Rails.env.local?
36
+ load_digest
37
+ else
38
+ @digest ||= load_digest
39
+ end
36
40
  rescue Errno::ENOENT
37
41
  NO_MANIFEST_DIGEST
38
42
  end
@@ -47,6 +51,10 @@ module RailsVite
47
51
  end
48
52
  end
49
53
 
54
+ def load_digest
55
+ Digest::MD5.file(@path).hexdigest
56
+ end
57
+
50
58
  def load_manifest
51
59
  JSON.parse(File.read(@path))
52
60
  rescue Errno::ENOENT
@@ -1,3 +1,3 @@
1
1
  module RailsVite
2
- VERSION = "0.3.0.rc1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -13,6 +13,15 @@ namespace :vite do
13
13
 
14
14
  Rake::Task["vite:build"].prereqs << :install unless ENV["SKIP_VITE_INSTALL"]
15
15
 
16
+ desc "Build Vite assets the way auto build does, only if sources changed"
17
+ task auto_build: :environment do
18
+ RailsVite::AutoBuild.build_if_stale
19
+ end
20
+
21
+ task :keep_eager_load do
22
+ ActiveSupport.on_load(:before_initialize) { config.rake_eager_load = config.eager_load }
23
+ end
24
+
16
25
  desc "Remove Vite build artifacts"
17
26
  task :clobber do
18
27
  rm_rf Rails.root.join("public", RailsVite.config.asset_prefix.delete_prefix("/"))
@@ -36,7 +45,7 @@ unless ENV["SKIP_VITE_BUILD"]
36
45
 
37
46
  %w[test:prepare spec:prepare db:test:prepare].each do |t|
38
47
  if Rake::Task.task_defined?(t)
39
- Rake::Task[t].enhance(["vite:build"])
48
+ Rake::Task[t].enhance(["vite:keep_eager_load", "vite:build"])
40
49
  break
41
50
  end
42
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_vite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.rc1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov