vite_ruby 3.3.4 → 3.10.2

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.
@@ -1,35 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require "json"
4
4
 
5
5
  # Public: Allows to resolve configuration sourced from `config/vite.json` and
6
6
  # environment variables, combining them with the default options.
7
7
  class ViteRuby::Config
8
8
  def origin
9
- "#{ protocol }://#{ host_with_port }"
9
+ "#{protocol}://#{host_with_port}"
10
10
  end
11
11
 
12
12
  def protocol
13
- https ? 'https' : 'http'
13
+ https ? "https" : "http"
14
14
  end
15
15
 
16
16
  def host_with_port
17
- "#{ host }:#{ port }"
17
+ "#{host}:#{port}"
18
18
  end
19
19
 
20
- # Internal: Path where Vite outputs the manifest file.
21
- def manifest_path
22
- build_output_dir.join('manifest.json')
23
- end
20
+ # Internal: Path to the manifest files generated by Vite and vite-plugin-ruby.
21
+ def known_manifest_paths
22
+ [
23
+ # NOTE: Generated by Vite when `manifest: true`, which vite-plugin-ruby enables.
24
+ build_output_dir.join(".vite/manifest.json"),
24
25
 
25
- # Internal: Path where vite-plugin-ruby outputs the assets manifest file.
26
- def assets_manifest_path
27
- build_output_dir.join('manifest-assets.json')
26
+ # NOTE: Path where vite-plugin-ruby outputs the assets manifest file.
27
+ build_output_dir.join(".vite/manifest-assets.json"),
28
+ ]
28
29
  end
29
30
 
30
31
  # Internal: Path to the manifest files generated by Vite and vite-plugin-ruby.
31
32
  def manifest_paths
32
- [manifest_path, assets_manifest_path].select(&:exist?)
33
+ known_manifest_paths.select(&:exist?)
33
34
  end
34
35
 
35
36
  # Public: The directory where Vite will store the built assets.
@@ -44,7 +45,7 @@ class ViteRuby::Config
44
45
 
45
46
  # Internal: The directory where Vite stores its processing cache.
46
47
  def vite_cache_dir
47
- root.join('node_modules/.vite')
48
+ root.join("node_modules/.vite")
48
49
  end
49
50
 
50
51
  # Public: The directory that Vite uses as root.
@@ -54,7 +55,7 @@ class ViteRuby::Config
54
55
 
55
56
  # Public: Loads an optional config/vite.rb file that can modify ViteRuby.env
56
57
  def load_ruby_config
57
- rb_config_path = File.expand_path(config_path.sub(/.json$/, '.rb'), root)
58
+ rb_config_path = File.expand_path(config_path.sub(/.json$/, ".rb"), root)
58
59
  load rb_config_path if File.exist?(rb_config_path)
59
60
  end
60
61
 
@@ -62,7 +63,7 @@ class ViteRuby::Config
62
63
  def to_env(env_vars = ViteRuby.env)
63
64
  CONFIGURABLE_WITH_ENV.each_with_object({}) do |option, env|
64
65
  unless (value = @config[option]).nil?
65
- env["#{ ViteRuby::ENV_PREFIX }_#{ option.upcase }"] = value.to_s
66
+ env["#{ViteRuby::ENV_PREFIX}_#{option.upcase}"] = value.to_s
66
67
  end
67
68
  end.merge(env_vars)
68
69
  end
@@ -71,10 +72,10 @@ class ViteRuby::Config
71
72
  def watched_paths
72
73
  [
73
74
  *(watch_additional_paths + additional_entrypoints).reject { |dir|
74
- dir.start_with?('~/') || dir.start_with?(source_code_dir)
75
+ dir.start_with?("~/") || dir.start_with?(source_code_dir)
75
76
  },
76
- "#{ source_code_dir }/**/*",
77
- config_path.sub(/.json$/, '.{rb,json}'),
77
+ "#{source_code_dir}/**/*",
78
+ config_path.sub(/.json$/, ".{rb,json}"),
78
79
  *DEFAULT_WATCHED_PATHS,
79
80
  ].freeze
80
81
  end
@@ -89,17 +90,29 @@ private
89
90
  # Internal: Coerces all the configuration values, in case they were passed
90
91
  # as environment variables which are always strings.
91
92
  def coerce_values(config)
92
- config['mode'] = config['mode'].to_s
93
- config['port'] = config['port'].to_i
94
- config['root'] = Pathname.new(config['root'])
95
- config['build_cache_dir'] = config['root'].join(config['build_cache_dir'])
96
- config['ssr_output_dir'] = config['root'].join(config['ssr_output_dir'])
97
- coerce_booleans(config, 'auto_build', 'hide_build_console_output', 'https', 'skip_compatibility_check', 'skip_proxy')
93
+ config["mode"] = config["mode"].to_s
94
+ config["port"] = config["port"].to_i
95
+ config["root"] = root = Pathname.new(config["root"])
96
+ config["build_cache_dir"] = root.join(config["build_cache_dir"])
97
+ config["ssr_output_dir"] = root.join(config["ssr_output_dir"])
98
+ config["dev_server_connect_timeout"] = config["dev_server_connect_timeout"].to_f
99
+ coerce_booleans(config, "auto_build", "hide_build_console_output", "https", "skip_compatibility_check", "skip_proxy")
100
+ config["package_manager"] ||= detect_package_manager(root)
98
101
  end
99
102
 
100
103
  # Internal: Coerces configuration options to boolean.
101
104
  def coerce_booleans(config, *names)
102
- names.each { |name| config[name] = [true, 'true'].include?(config[name]) }
105
+ truthy = [true, "true"]
106
+ names.each { |name| config[name] = truthy.include?(config[name]) }
107
+ end
108
+
109
+ def detect_package_manager(root)
110
+ return "npm" if root.join("package-lock.json").exist?
111
+ return "pnpm" if root.join("pnpm-lock.yaml").exist?
112
+ return "bun" if root.join("bun.lockb").exist? || root.join("bun.lock").exist?
113
+ return "yarn" if root.join("yarn.lock").exist?
114
+
115
+ "npm"
103
116
  end
104
117
 
105
118
  def initialize(attrs)
@@ -113,8 +126,8 @@ private
113
126
  # Public: Returns the project configuration for Vite.
114
127
  def resolve_config(**attrs)
115
128
  config = config_defaults.merge(attrs.transform_keys(&:to_s))
116
- file_path = File.join(config['root'], config['config_path'])
117
- file_config = config_from_file(file_path, mode: config['mode'])
129
+ file_path = File.join(config["root"], config["config_path"])
130
+ file_config = config_from_file(file_path, mode: config["mode"])
118
131
  new DEFAULT_CONFIG.merge(file_config).merge(config_from_env).merge(config)
119
132
  end
120
133
 
@@ -122,20 +135,20 @@ private
122
135
 
123
136
  # Internal: Converts camelCase to snake_case.
124
137
  SNAKE_CASE = ->(camel_cased_word) {
125
- camel_cased_word.to_s.gsub(/::/, '/')
138
+ camel_cased_word.to_s.gsub("::", "/")
126
139
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
127
140
  .gsub(/([a-z\d])([A-Z])/, '\1_\2')
128
- .tr('-', '_')
141
+ .tr("-", "_")
129
142
  .downcase
130
143
  }
131
144
 
132
145
  # Internal: Default values for a Ruby application.
133
- def config_defaults(asset_host: nil, mode: ENV.fetch('RACK_ENV', 'development'), root: Dir.pwd)
146
+ def config_defaults(asset_host: nil, mode: ENV.fetch("RACK_ENV", "development"), root: Dir.pwd)
134
147
  {
135
- 'asset_host' => option_from_env('asset_host') || asset_host,
136
- 'config_path' => option_from_env('config_path') || DEFAULT_CONFIG.fetch('config_path'),
137
- 'mode' => option_from_env('mode') || mode,
138
- 'root' => option_from_env('root') || root,
148
+ "asset_host" => option_from_env("asset_host") || asset_host,
149
+ "config_path" => option_from_env("config_path") || DEFAULT_CONFIG.fetch("config_path"),
150
+ "mode" => option_from_env("mode") || mode,
151
+ "root" => option_from_env("root") || root,
139
152
  }.select { |_, value| value }
140
153
  end
141
154
 
@@ -150,7 +163,7 @@ private
150
163
 
151
164
  # Internal: Retrieves a configuration option from environment variables.
152
165
  def option_from_env(name)
153
- ViteRuby.env["#{ ViteRuby::ENV_PREFIX }_#{ name.upcase }"]
166
+ ViteRuby.env["#{ViteRuby::ENV_PREFIX}_#{name.upcase}"]
154
167
  end
155
168
 
156
169
  # Internal: Extracts the configuration options provided as env vars.
@@ -165,16 +178,16 @@ private
165
178
  # Internal: Loads the configuration options provided in a JSON file.
166
179
  def config_from_file(path, mode:)
167
180
  multi_env_config = load_json(path)
168
- multi_env_config.fetch('all', {})
181
+ multi_env_config.fetch("all", {})
169
182
  .merge(multi_env_config.fetch(mode, {}))
170
183
  rescue Errno::ENOENT => error
171
- $stderr << "Check that your vite.json configuration file is available in the load path:\n\n\t#{ error.message }\n\n"
184
+ $stderr << "Check that your vite.json configuration file is available in the load path:\n\n\t#{error.message}\n\n"
172
185
  {}
173
186
  end
174
187
  end
175
188
 
176
189
  # Internal: Shared configuration with the Vite plugin for Ruby.
177
- DEFAULT_CONFIG = load_json("#{ __dir__ }/../../default.vite.json").freeze
190
+ DEFAULT_CONFIG = load_json("#{__dir__}/../../default.vite.json").freeze
178
191
 
179
192
  # Internal: Configuration options that can not be provided as env vars.
180
193
  NOT_CONFIGURABLE_WITH_ENV = %w[additional_entrypoints watch_additional_paths].freeze
@@ -184,12 +197,15 @@ private
184
197
 
185
198
  # Internal: If any of these files is modified the build won't be skipped.
186
199
  DEFAULT_WATCHED_PATHS = %w[
200
+ bun.lockb
187
201
  package-lock.json
188
202
  package.json
189
203
  pnpm-lock.yaml
190
204
  postcss.config.js
191
205
  tailwind.config.js
192
206
  vite.config.js
207
+ vite.config.mjs
208
+ vite.config.mts
193
209
  vite.config.ts
194
210
  windi.config.ts
195
211
  yarn.lock
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/proxy'
3
+ require "rack/proxy"
4
4
 
5
5
  # Public: Allows to relay asset requests to the Vite development server.
6
6
  class ViteRuby::DevServerProxy < Rack::Proxy
7
7
  HOST_WITH_PORT_REGEX = %r{^(.+?)(:\d+)/}
8
- VITE_DEPENDENCY_PREFIX = '/@'
8
+ VITE_DEPENDENCY_PREFIX = "/@"
9
9
 
10
10
  def initialize(app = nil, options = {})
11
11
  @vite_ruby = options.delete(:vite_ruby) || ViteRuby.instance
12
- options[:streaming] = false if config.mode == 'test' && !options.key?(:streaming)
12
+ options[:streaming] = false if config.mode == "test" && !options.key?(:streaming)
13
13
  super
14
14
  end
15
15
 
@@ -17,7 +17,7 @@ class ViteRuby::DevServerProxy < Rack::Proxy
17
17
  def perform_request(env)
18
18
  if vite_should_handle?(env) && dev_server_running?
19
19
  forward_to_vite_dev_server(env)
20
- super(env)
20
+ super
21
21
  else
22
22
  @app.call(env)
23
23
  end
@@ -30,39 +30,40 @@ private
30
30
  def_delegators :@vite_ruby, :config, :dev_server_running?
31
31
 
32
32
  def rewrite_uri_for_vite(env)
33
- uri = env.fetch('REQUEST_URI') { [env['PATH_INFO'], env['QUERY_STRING']].reject { |str| str.to_s.strip.empty? }.join('?') }
34
- env['PATH_INFO'], env['QUERY_STRING'] = (env['REQUEST_URI'] = normalize_uri(uri)).split('?')
33
+ uri = env.fetch("REQUEST_URI") { [env["PATH_INFO"], env["QUERY_STRING"]].reject { |str| str.to_s.strip.empty? }.join("?") }
34
+ env["PATH_INFO"], env["QUERY_STRING"] = (env["REQUEST_URI"] = normalize_uri(uri)).split("?")
35
35
  end
36
36
 
37
37
  def normalize_uri(uri)
38
38
  uri
39
- .sub(HOST_WITH_PORT_REGEX, '/') # Hanami adds the host and port.
40
- .sub('.ts.js', '.ts') # Hanami's javascript helper always adds the extension.
39
+ .sub(HOST_WITH_PORT_REGEX, "/") # Hanami adds the host and port.
40
+ .sub(".ts.js", ".ts") # Hanami's javascript helper always adds the extension.
41
41
  .sub(/\.(sass|scss|styl|stylus|less|pcss|postcss)\.css$/, '.\1') # Rails' stylesheet_link_tag always adds the extension.
42
42
  end
43
43
 
44
44
  def forward_to_vite_dev_server(env)
45
45
  rewrite_uri_for_vite(env)
46
- env['QUERY_STRING'] ||= ''
47
- env['HTTP_HOST'] = env['HTTP_X_FORWARDED_HOST'] = config.host
48
- env['HTTP_X_FORWARDED_SERVER'] = config.host_with_port
49
- env['HTTP_PORT'] = env['HTTP_X_FORWARDED_PORT'] = config.port.to_s
50
- env['HTTP_X_FORWARDED_PROTO'] = env['HTTP_X_FORWARDED_SCHEME'] = config.protocol
51
- env['HTTPS'] = env['HTTP_X_FORWARDED_SSL'] = 'off' unless config.https
52
- env['SCRIPT_NAME'] = ''
46
+ env["QUERY_STRING"] ||= ""
47
+ env["HTTP_HOST"] = env["HTTP_X_FORWARDED_HOST"] = config.host
48
+ env["HTTP_X_FORWARDED_SERVER"] = config.host_with_port
49
+ env["HTTP_PORT"] = env["HTTP_X_FORWARDED_PORT"] = config.port.to_s
50
+ env["HTTP_X_FORWARDED_PROTO"] = env["HTTP_X_FORWARDED_SCHEME"] = config.protocol
51
+ env["HTTPS"] = env["HTTP_X_FORWARDED_SSL"] = "off" unless config.https
52
+ env["SCRIPT_NAME"] = ""
53
53
  end
54
54
 
55
55
  def vite_should_handle?(env)
56
- path = normalize_uri(env['PATH_INFO'])
56
+ path = normalize_uri(env["PATH_INFO"])
57
57
  return true if path.start_with?(vite_url_prefix) # Vite asset
58
- return true if file_in_vite_root?(path) # Fallback if Vite can serve the file
58
+
59
+ true if file_in_vite_root?(path) # Fallback if Vite can serve the file
59
60
  end
60
61
 
61
62
  # NOTE: When using an empty 'public_output_dir', we need to rely on a
62
63
  # filesystem check to check whether Vite should serve the request.
63
64
  def file_in_vite_root?(path)
64
- path.include?('.') && # Check for extension, avoid filesystem if possible.
65
- config.vite_root_dir.join(path.start_with?('/') ? path[1..-1] : path).file?
65
+ path.include?(".") && # Check for extension, avoid filesystem if possible.
66
+ config.vite_root_dir.join(path.start_with?("/") ? path[1..] : path).file?
66
67
  end
67
68
 
68
69
  # NOTE: Vite is configured to use 'public_output_dir' as the base, which can
@@ -70,6 +71,6 @@ private
70
71
  #
71
72
  # If the path starts with that prefix, it will be redirected to Vite.
72
73
  def vite_url_prefix
73
- @vite_url_prefix ||= config.public_output_dir.empty? ? VITE_DEPENDENCY_PREFIX : "/#{ config.public_output_dir }/"
74
+ @vite_url_prefix ||= config.public_output_dir.empty? ? VITE_DEPENDENCY_PREFIX : "/#{config.public_output_dir}/"
74
75
  end
75
76
  end
@@ -3,7 +3,7 @@
3
3
  # Internal: Provides common functionality for errors.
4
4
  class ViteRuby::Error < StandardError
5
5
  def message
6
- super.sub(':troubleshooting:', <<~MSG)
6
+ super.sub(":troubleshooting:", <<~MSG)
7
7
  Visit the Troubleshooting guide for more information:
8
8
  https://vite-ruby.netlify.app/guide/troubleshooting.html#troubleshooting
9
9
  MSG
data/lib/vite_ruby/io.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'open3'
3
+ require "open3"
4
4
 
5
5
  # Public: Builds on top of Ruby I/O open3 providing a friendlier experience.
6
6
  module ViteRuby::IO
7
7
  class << self
8
8
  # Internal: A modified version of capture3 that can continuosly print stdout.
9
9
  # NOTE: Streaming output provides a better UX when running bin/vite build.
10
- def capture(*cmd, with_output: $stdout.method(:puts), stdin_data: '', **opts)
10
+ def capture(*cmd, with_output: $stdout.method(:puts), stdin_data: "", **opts)
11
11
  return Open3.capture3(*cmd, **opts) unless with_output
12
12
 
13
13
  Open3.popen3(*cmd, **opts) { |stdin, stdout, stderr, wait_threads|
@@ -21,7 +21,7 @@ module ViteRuby::IO
21
21
 
22
22
  # Internal: Reads and yield every line in the stream. Returns the full content.
23
23
  def read_lines(io)
24
- buffer = +''
24
+ buffer = +""
25
25
  while line = io.gets
26
26
  buffer << line
27
27
  yield line
@@ -19,20 +19,37 @@ class ViteRuby::Manifest
19
19
  #
20
20
  # Raises an error if the resource could not be found in the manifest.
21
21
  def path_for(name, **options)
22
- lookup!(name, **options).fetch('file')
22
+ lookup!(name, **options).fetch("file")
23
+ end
24
+
25
+ # Internal: Recursively collects all imported chunks for a given entry.
26
+ # Returns chunks in dependency-first order (deepest imports first), deduped.
27
+ def import_chunks_for(entry, seen_filenames: Set.new)
28
+ chunks = []
29
+
30
+ entry["imports"]&.each do |chunk|
31
+ filename = chunk["file"]
32
+ next if seen_filenames.include?(filename)
33
+ seen_filenames.add(filename)
34
+
35
+ chunks.concat(import_chunks_for(chunk, seen_filenames: seen_filenames))
36
+ chunks << chunk
37
+ end
38
+
39
+ chunks
23
40
  end
24
41
 
25
42
  # Public: Returns scripts, imported modules, and stylesheets for the specified
26
43
  # entrypoint files.
27
44
  def resolve_entries(*names, **options)
28
45
  entries = names.map { |name| lookup!(name, **options) }
29
- script_paths = entries.map { |entry| entry.fetch('file') }
46
+ script_paths = entries.map { |entry| entry.fetch("file") }
30
47
 
31
- imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact.uniq
48
+ imports = dev_server_running? ? [] : entries.flat_map { |entry| import_chunks_for(entry) }
32
49
  {
33
50
  scripts: script_paths,
34
- imports: imports.map { |entry| entry.fetch('file') }.uniq,
35
- stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq,
51
+ imports: imports.filter_map { |entry| entry.fetch("file") }.uniq,
52
+ stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry["css"] }.compact.uniq,
36
53
  }
37
54
  end
38
55
 
@@ -43,7 +60,7 @@ class ViteRuby::Manifest
43
60
 
44
61
  # Public: The path from where the browser can download the Vite HMR client.
45
62
  def vite_client_src
46
- prefix_asset_with_host('@vite/client') if dev_server_running?
63
+ prefix_asset_with_host("@vite/client") if dev_server_running?
47
64
  end
48
65
 
49
66
  # Public: The content of the preamble needed by the React Refresh plugin.
@@ -51,7 +68,7 @@ class ViteRuby::Manifest
51
68
  if dev_server_running?
52
69
  <<~REACT_REFRESH
53
70
  <script type="module">
54
- #{ react_preamble_code }
71
+ #{react_preamble_code}
55
72
  </script>
56
73
  REACT_REFRESH
57
74
  end
@@ -61,7 +78,7 @@ class ViteRuby::Manifest
61
78
  def react_preamble_code
62
79
  if dev_server_running?
63
80
  <<~REACT_PREAMBLE_CODE
64
- import RefreshRuntime from '#{ prefix_asset_with_host('@react-refresh') }'
81
+ import RefreshRuntime from '#{prefix_asset_with_host("@react-refresh")}'
65
82
  RefreshRuntime.injectIntoGlobalHook(window)
66
83
  window.$RefreshReg$ = () => {}
67
84
  window.$RefreshSig$ = () => (type) => type
@@ -95,7 +112,7 @@ protected
95
112
  private
96
113
 
97
114
  # Internal: The prefix used by Vite.js to request files with an absolute path.
98
- FS_PREFIX = '/@fs/'
115
+ FS_PREFIX = "/@fs/"
99
116
 
100
117
  extend Forwardable
101
118
 
@@ -110,7 +127,7 @@ private
110
127
  # Internal: Finds the specified entry in the manifest.
111
128
  def find_manifest_entry(name)
112
129
  if dev_server_running?
113
- { 'file' => prefix_vite_asset(name) }
130
+ {"file" => prefix_vite_asset(name)}
114
131
  else
115
132
  manifest[name]
116
133
  end
@@ -128,19 +145,21 @@ private
128
145
 
129
146
  # Internal: Loads and merges the manifest files, resolving the asset paths.
130
147
  def load_manifest
131
- files = config.manifest_paths
132
- files.map { |path| JSON.parse(path.read) }.inject({}, &:merge).tap(&method(:resolve_references))
148
+ config.manifest_paths
149
+ .map { |path| JSON.parse(path.read) }
150
+ .inject({}, &:merge)
151
+ .tap { |manifest| resolve_references(manifest) }
133
152
  end
134
153
 
135
154
  # Internal: Scopes an asset to the output folder in public, as a path.
136
155
  def prefix_vite_asset(path)
137
- File.join(vite_asset_origin || '/', config.public_output_dir, path)
156
+ File.join(vite_asset_origin || "/", config.public_output_dir, path)
138
157
  end
139
158
 
140
159
  # Internal: Prefixes an asset with the `asset_host` for tags that do not use
141
160
  # the framework tag helpers.
142
161
  def prefix_asset_with_host(path)
143
- File.join(vite_asset_origin || config.asset_host || '/', config.public_output_dir, path)
162
+ File.join(vite_asset_origin || config.asset_host || "/", config.public_output_dir, path)
144
163
  end
145
164
 
146
165
  # Internal: The origin of assets managed by Vite.
@@ -151,11 +170,11 @@ private
151
170
  # Internal: Resolves the paths that reference a manifest entry.
152
171
  def resolve_references(manifest)
153
172
  manifest.each_value do |entry|
154
- entry['file'] = prefix_vite_asset(entry['file'])
173
+ entry["file"] = prefix_vite_asset(entry["file"])
155
174
  %w[css assets].each do |key|
156
175
  entry[key] = entry[key].map { |path| prefix_vite_asset(path) } if entry[key]
157
176
  end
158
- entry['imports']&.map! { |name| manifest.fetch(name) }
177
+ entry["imports"]&.map! { |name| manifest.fetch(name) }
159
178
  end
160
179
  end
161
180
 
@@ -164,7 +183,7 @@ private
164
183
  return resolve_virtual_entry(name) if type == :virtual
165
184
 
166
185
  name = with_file_extension(name.to_s, type)
167
- raise ArgumentError, "Asset names can not be relative. Found: #{ name }" if name.start_with?('.')
186
+ raise ArgumentError, "Asset names can not be relative. Found: #{name}" if name.start_with?(".")
168
187
 
169
188
  # Explicit path, relative to the source_code_dir.
170
189
  name.sub(%r{^~/(.+)$}) { return Regexp.last_match(1) }
@@ -173,7 +192,7 @@ private
173
192
  name.sub(%r{^/(.+)$}) { return resolve_absolute_entry(Regexp.last_match(1)) }
174
193
 
175
194
  # Sugar: Prefix with the entrypoints dir if the path is not nested.
176
- name.include?('/') ? name : File.join(config.entrypoints_dir, name)
195
+ name.include?("/") ? name : File.join(config.entrypoints_dir, name)
177
196
  end
178
197
 
179
198
  # Internal: Entry names in the manifest are relative to the Vite.js.
@@ -194,7 +213,7 @@ private
194
213
  # Internal: Adds a file extension to the file name, unless it already has one.
195
214
  def with_file_extension(name, entry_type)
196
215
  if File.extname(name).empty? && (ext = extension_for_type(entry_type))
197
- "#{ name }.#{ ext }"
216
+ "#{name}.#{ext}"
198
217
  else
199
218
  name
200
219
  end
@@ -203,16 +222,16 @@ private
203
222
  # Internal: Allows to receive :javascript and :stylesheet as :type in helpers.
204
223
  def extension_for_type(entry_type)
205
224
  case entry_type
206
- when :javascript then 'js'
207
- when :stylesheet then 'css'
208
- when :typescript then 'ts'
225
+ when :javascript then "js"
226
+ when :stylesheet then "css"
227
+ when :typescript then "ts"
209
228
  else entry_type
210
229
  end
211
230
  end
212
231
 
213
232
  # Internal: Raises a detailed message when an entry is missing in the manifest.
214
233
  def missing_entry_error(name, **options)
215
- raise ViteRuby::MissingEntrypointError, OpenStruct.new(
234
+ raise ViteRuby::MissingEntrypointError.new(
216
235
  file_name: resolve_entry_name(name, **options),
217
236
  last_build: builder.last_build_metadata,
218
237
  manifest: @manifest,
@@ -5,27 +5,26 @@
5
5
  # NOTE: The complexity here is justified by the improved usability of providing
6
6
  # a more specific error message depending on the situation.
7
7
  class ViteRuby::MissingEntrypointError < ViteRuby::Error
8
- extend Forwardable
9
- def_delegators :@info, :file_name, :last_build, :manifest, :config
8
+ attr_reader :file_name, :last_build, :manifest, :config
10
9
 
11
- def initialize(info)
12
- @info = info
10
+ def initialize(file_name:, last_build:, manifest:, config:)
11
+ @file_name, @last_build, @manifest, @config = file_name, last_build, manifest, config
13
12
  super <<~MSG
14
- Vite Ruby can't find #{ file_name } in #{ config.manifest_path.relative_path_from(config.root) } or #{ config.assets_manifest_path.relative_path_from(config.root) }.
13
+ Vite Ruby can't find #{file_name} in the manifests.
15
14
 
16
15
  Possible causes:
17
- #{ possible_causes(last_build) }
16
+ #{possible_causes(last_build)}
18
17
  :troubleshooting:
19
- #{ "\nContent in your manifests:\n#{ JSON.pretty_generate(manifest) }\n" if last_build.success }
20
- #{ "Last build in #{ config.mode } mode:\n#{ last_build.to_json }\n" if last_build.success }
18
+ #{"Manifest files found:\n#{config.manifest_paths.map { |path| " #{path.relative_path_from(config.root)}" }.join("\n")}\n" if last_build.success}
19
+ #{"Last build in #{config.mode} mode:\n#{last_build.to_json}\n" if last_build.success}
21
20
  MSG
22
21
  end
23
22
 
24
23
  def possible_causes(last_build)
25
24
  if last_build.success == false
26
25
  FAILED_BUILD_CAUSES
27
- .sub(':mode:', config.mode)
28
- .sub(':errors:', last_build.errors.to_s.gsub(/^(?!$)/, ' '))
26
+ .sub(":mode:", config.mode)
27
+ .sub(":errors:", last_build.errors.to_s.gsub(/^(?!$)/, " "))
29
28
  elsif config.auto_build
30
29
  DEFAULT_CAUSES
31
30
  else
@@ -7,7 +7,7 @@ class ViteRuby::MissingExecutableError < ViteRuby::Error
7
7
  ❌ The vite binary is not available. Have you installed the npm packages?
8
8
 
9
9
  :troubleshooting:
10
- #{ error }
10
+ #{error}
11
11
  MSG
12
12
  end
13
13
  end
@@ -12,7 +12,7 @@ class ViteRuby::Runner
12
12
  cmd = command_for(argv)
13
13
  return Kernel.exec(*cmd) if exec
14
14
 
15
- log_or_noop = ->(line) { logger.info('vite') { line } } unless config.hide_build_console_output
15
+ log_or_noop = ->(line) { logger.info("vite") { line } } unless config.hide_build_console_output
16
16
  ViteRuby::IO.capture(*cmd, chdir: config.root, with_output: log_or_noop)
17
17
  }
18
18
  rescue Errno::ENOENT => error
@@ -28,24 +28,26 @@ private
28
28
  # Internal: Returns an Array with the command to run.
29
29
  def command_for(args)
30
30
  [config.to_env(env)].tap do |cmd|
31
- args = args.clone
32
- cmd.push('node', '--inspect-brk') if args.delete('--inspect')
33
- cmd.push('node', '--trace-deprecation') if args.delete('--trace_deprecation')
34
- cmd.push(*vite_executable)
35
- cmd.push(*args)
36
- cmd.push('--mode', config.mode) unless args.include?('--mode') || args.include?('-m')
31
+ exec_args, vite_args = args.partition { |arg| arg.start_with?("--node-options") }
32
+ cmd.push(*vite_executable(*exec_args))
33
+ cmd.push(*vite_args)
34
+ cmd.push("--mode", config.mode) unless args.include?("--mode") || args.include?("-m")
37
35
  end
38
36
  end
39
37
 
40
38
  # Internal: Resolves to an executable for Vite.
41
- def vite_executable
39
+ def vite_executable(*exec_args)
42
40
  bin_path = config.vite_bin_path
43
- return [bin_path] if File.exist?(bin_path)
44
-
45
- if config.root.join('yarn.lock').exist?
46
- %w[yarn vite]
47
- else
48
- ["#{ `npm bin`.chomp }/vite"]
41
+ return [bin_path] if bin_path && File.exist?(bin_path)
42
+
43
+ x = case config.package_manager
44
+ when "npm" then %w[npx]
45
+ when "pnpm" then %w[pnpm exec]
46
+ when "bun" then %w[bun x --bun]
47
+ when "yarn" then %w[yarn]
48
+ else raise ArgumentError, "Unknown package manager #{config.package_manager.inspect}"
49
49
  end
50
+
51
+ [*x, *exec_args, "vite"]
50
52
  end
51
53
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ViteRuby
4
- VERSION = '3.3.4'
4
+ VERSION = "3.10.2"
5
5
 
6
6
  # Internal: Versions used by default when running `vite install`.
7
- DEFAULT_VITE_VERSION = '^4.3.0'
8
- DEFAULT_PLUGIN_VERSION = '^3.2.0'
7
+ DEFAULT_VITE_VERSION = "^8.0.0"
8
+ DEFAULT_PLUGIN_VERSION = "^5.2.0"
9
9
  end