From a65600e5234d61d65196107474dd6730e95af213 Mon Sep 17 00:00:00 2001 From: Maximo Mussini Date: Tue, 5 May 2026 13:35:58 -0300 Subject: [PATCH 1/2] feat: add support for `vite-plugin-backend-full-bundle` --- test/config_test.rb | 3 ++ test/helper_test.rb | 22 +++++++++++ test/manifest_test.rb | 39 +++++++++++++++++++ vite-plugin-ruby/default.vite.json | 1 + vite-plugin-ruby/src/index.ts | 6 ++- vite-plugin-ruby/src/types.ts | 1 + vite_ruby/default.vite.json | 1 + vite_ruby/lib/vite_ruby/config.rb | 2 +- vite_ruby/lib/vite_ruby/manifest.rb | 60 +++++++++++++++++++++++++++-- 9 files changed, 130 insertions(+), 5 deletions(-) diff --git a/test/config_test.rb b/test/config_test.rb index e6223990..ca4d6777 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -80,6 +80,7 @@ def test_watch_additional_paths def test_auto_build refute @config.auto_build + refute @config.bundled_dev with_rails_env("development") do |config| assert config.auto_build @@ -123,6 +124,7 @@ def test_to_env def test_environment_vars ViteRuby.env.tap(&:clear).merge!( "VITE_RUBY_AUTO_BUILD" => "true", + "VITE_RUBY_BUNDLED_DEV" => "true", "VITE_RUBY_HOST" => "example.com", "VITE_RUBY_PORT" => "1920", "VITE_RUBY_HTTPS" => "true", @@ -139,6 +141,7 @@ def test_environment_vars @config = resolve_config assert @config.auto_build + assert @config.bundled_dev assert_equal "example.com", @config.host assert_equal 1920, @config.port assert @config.https diff --git a/test/helper_test.rb b/test/helper_test.rb index 81ae21a2..0b02039b 100644 --- a/test/helper_test.rb +++ b/test/helper_test.rb @@ -69,6 +69,11 @@ def test_vite_client_tag with_dev_server_running { assert_equal '', vite_client_tag } + + refresh_config(mode: "development", bundled_dev: true) + ViteRuby.instance.stub(:dev_server_running?, true) { + assert_nil vite_client_tag + } end def test_vite_asset_path @@ -138,6 +143,23 @@ def test_vite_javascript_tag assert_equal %(), vite_typescript_tag("main") } + + refresh_config(mode: "development", bundled_dev: true) + backend_tags = { + "scripts" => [{"src" => "/vite-dev/assets/main.js"}], + "styles" => [{"href" => "/vite-dev/assets/main.css"}], + "preloads" => [{"href" => "/vite-dev/assets/vendor.js"}], + } + + ViteRuby.instance.stub(:dev_server_running?, true) { + ViteRuby::Manifest.stub_any_instance(:fetch_backend_entry_tags, backend_tags) { + assert_similar [ + %(), + %(), + link(href: "/vite-dev/assets/main.css", crossorigin: ""), + ].join, vite_typescript_tag("main") + } + } end def test_vite_react_refresh_tag_without_nonce diff --git a/test/manifest_test.rb b/test/manifest_test.rb index ddda3af5..0e4857af 100644 --- a/test/manifest_test.rb +++ b/test/manifest_test.rb @@ -193,6 +193,45 @@ def test_vite_client_src with_dev_server_running { assert_equal "http://example.com/vite-dev/@vite/client", vite_client_src } + + refresh_config(mode: "development", bundled_dev: true) + + with_dev_server_running { + assert_nil vite_client_src + } + end + + def test_resolve_entries_with_backend_tags_when_bundled_dev + refresh_config(mode: "development", bundled_dev: true) + + tags = { + "scripts" => [{"src" => "/vite-dev/assets/main.js"}], + "styles" => [{"href" => "/vite-dev/assets/main.css"}], + "preloads" => [{"href" => "/vite-dev/assets/vendor.js"}], + } + + with_dev_server_running { + ViteRuby::Manifest.stub_any_instance(:fetch_backend_entry_tags, tags) { + entries = ViteRuby.instance.manifest.resolve_entries("main", type: :typescript) + + assert_equal ["/vite-dev/assets/main.js"], entries.fetch(:scripts) + assert_equal ["/vite-dev/assets/main.css"], entries.fetch(:stylesheets) + assert_equal ["/vite-dev/assets/vendor.js"], entries.fetch(:imports) + } + } + end + + def test_resolve_entries_falls_back_when_backend_tags_missing + refresh_config(mode: "development", bundled_dev: true) + + with_dev_server_running { + ViteRuby::Manifest.stub_any_instance(:fetch_backend_entry_tags, nil) { + entries = ViteRuby.instance.manifest.resolve_entries("main", type: :typescript) + assert_equal ["/vite-dev/entrypoints/main.ts"], entries.fetch(:scripts) + assert_empty entries.fetch(:stylesheets) + assert_empty entries.fetch(:imports) + } + } end def test_lookup_nil diff --git a/vite-plugin-ruby/default.vite.json b/vite-plugin-ruby/default.vite.json index 947206f2..c8015b92 100644 --- a/vite-plugin-ruby/default.vite.json +++ b/vite-plugin-ruby/default.vite.json @@ -20,6 +20,7 @@ "viteBinPath": null, "watchAdditionalPaths": [], "base": "", + "bundledDev": false, "ssrBuildEnabled": false, "ssrEntrypoint": "~/ssr/ssr.{js,ts,jsx,tsx}", "ssrOutputDir": "public/vite-ssr" diff --git a/vite-plugin-ruby/src/index.ts b/vite-plugin-ruby/src/index.ts index 92fa4e36..21c18aec 100644 --- a/vite-plugin-ruby/src/index.ts +++ b/vite-plugin-ruby/src/index.ts @@ -33,7 +33,7 @@ const debug = createDebug('vite-plugin-ruby:config') // config file, and configures the entrypoints and manifest generation. function config (userConfig: UserConfig, env: ConfigEnv): UserConfig { const config = loadConfiguration(env.mode, projectRoot, userConfig) - const { assetsDir, base, outDir, server, root, entrypoints, ssrBuild } = config + const { assetsDir, base, outDir, server, root, entrypoints, ssrBuild, bundledDev } = config const isLocal = config.mode === 'development' || config.mode === 'test' @@ -86,6 +86,10 @@ function config (userConfig: UserConfig, env: ConfigEnv): UserConfig { root, server, build, + experimental: { + ...userConfig.experimental, + bundledDev, + }, viteRuby: config, }) } diff --git a/vite-plugin-ruby/src/types.ts b/vite-plugin-ruby/src/types.ts index d6c21877..4dc0fc02 100644 --- a/vite-plugin-ruby/src/types.ts +++ b/vite-plugin-ruby/src/types.ts @@ -17,6 +17,7 @@ export interface ResolvedConfig { publicOutputDir: string watchAdditionalPaths: string[] base: string + bundledDev: boolean skipProxy: boolean /** * @private diff --git a/vite_ruby/default.vite.json b/vite_ruby/default.vite.json index 947206f2..c8015b92 100644 --- a/vite_ruby/default.vite.json +++ b/vite_ruby/default.vite.json @@ -20,6 +20,7 @@ "viteBinPath": null, "watchAdditionalPaths": [], "base": "", + "bundledDev": false, "ssrBuildEnabled": false, "ssrEntrypoint": "~/ssr/ssr.{js,ts,jsx,tsx}", "ssrOutputDir": "public/vite-ssr" diff --git a/vite_ruby/lib/vite_ruby/config.rb b/vite_ruby/lib/vite_ruby/config.rb index 54cf940a..92318843 100644 --- a/vite_ruby/lib/vite_ruby/config.rb +++ b/vite_ruby/lib/vite_ruby/config.rb @@ -96,7 +96,7 @@ def coerce_values(config) config["build_cache_dir"] = root.join(config["build_cache_dir"]) config["ssr_output_dir"] = root.join(config["ssr_output_dir"]) config["dev_server_connect_timeout"] = config["dev_server_connect_timeout"].to_f - coerce_booleans(config, "auto_build", "hide_build_console_output", "https", "skip_compatibility_check", "skip_proxy") + coerce_booleans(config, "auto_build", "bundled_dev", "hide_build_console_output", "https", "skip_compatibility_check", "skip_proxy") config["package_manager"] ||= detect_package_manager(root) end diff --git a/vite_ruby/lib/vite_ruby/manifest.rb b/vite_ruby/lib/vite_ruby/manifest.rb index 1a151323..6dc1dcb5 100644 --- a/vite_ruby/lib/vite_ruby/manifest.rb +++ b/vite_ruby/lib/vite_ruby/manifest.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +require "cgi" +require "json" +require "net/http" + # Public: Registry for accessing resources managed by Vite, using a generated # manifest file which maps entrypoint names to file paths. # @@ -42,6 +46,10 @@ def import_chunks_for(entry, seen_filenames: Set.new) # Public: Returns scripts, imported modules, and stylesheets for the specified # entrypoint files. def resolve_entries(*names, **options) + if dev_server_running? && config.bundled_dev + return resolve_entries_with_backend(*names, **options) + end + entries = names.map { |name| lookup!(name, **options) } script_paths = entries.map { |entry| entry.fetch("file") } @@ -60,12 +68,12 @@ def refresh # Public: The path from where the browser can download the Vite HMR client. def vite_client_src - prefix_asset_with_host("@vite/client") if dev_server_running? + prefix_asset_with_host("@vite/client") if dev_server_running? && !config.bundled_dev end # Public: The content of the preamble needed by the React Refresh plugin. def react_refresh_preamble - if dev_server_running? + if dev_server_running? && !config.bundled_dev <<~REACT_REFRESH