vite_ruby 3.9.0 → 4.0.0.alpha1

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,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require 'digest/sha1'
4
5
 
5
6
  # Public: Keeps track of watched files and triggers builds as needed.
@@ -11,13 +12,9 @@ class ViteRuby::Builder
11
12
  # Public: Checks if the watched files have changed since the last compilation,
12
13
  # and triggers a Vite build if any files have changed.
13
14
  def build(*args)
14
- last_build = last_build_metadata(ssr: args.include?('--ssr'))
15
-
16
- if args.delete('--force') || last_build.stale? || config.manifest_paths.empty?
17
- stdout, stderr, status = build_with_vite(*args)
18
- log_build_result(stdout, stderr, status)
19
- record_build_metadata(last_build, errors: stderr, success: status.success?)
20
- status.success?
15
+ last_build = last_build_metadata
16
+ if args.delete('--force') || last_build.stale?
17
+ build_with_vite(*args).tap { |success| record_build_metadata(success, last_build) }
21
18
  elsif last_build.success
22
19
  logger.debug "Skipping vite build. Watched files have not changed since the last build at #{ last_build.timestamp }"
23
20
  true
@@ -28,8 +25,8 @@ class ViteRuby::Builder
28
25
  end
29
26
 
30
27
  # Internal: Reads the result of the last compilation from disk.
31
- def last_build_metadata(ssr: false)
32
- ViteRuby::Build.from_previous(last_build_path(ssr: ssr), watched_files_digest)
28
+ def last_build_metadata
29
+ ViteRuby::Build.from_previous(last_build_attrs, watched_files_digest)
33
30
  end
34
31
 
35
32
  private
@@ -38,35 +35,44 @@ private
38
35
 
39
36
  def_delegators :@vite_ruby, :config, :logger, :run
40
37
 
38
+ # Internal: Reads metadata recorded on the last build, if it exists.
39
+ def last_build_attrs
40
+ last_build_path.exist? ? JSON.parse(last_build_path.read.to_s) : {}
41
+ rescue JSON::JSONError, Errno::ENOENT, Errno::ENOTDIR
42
+ {}
43
+ end
44
+
41
45
  # Internal: Writes a digest of the watched files to disk for future checks.
42
- def record_build_metadata(build, **attrs)
46
+ def record_build_metadata(success, build)
43
47
  config.build_cache_dir.mkpath
44
- build.with_result(**attrs).write_to_cache
48
+ last_build_path.write build.with_result(success).to_json
45
49
  end
46
50
 
47
51
  # Internal: The file path where metadata of the last build is stored.
48
- def last_build_path(ssr:)
49
- config.build_cache_dir.join("last#{ '-ssr' if ssr }-build-#{ config.mode }.json")
52
+ def last_build_path
53
+ config.build_cache_dir.join("last-build-#{ config.mode }.json")
50
54
  end
51
55
 
52
56
  # Internal: Returns a digest of all the watched files, allowing to detect
53
57
  # changes, and skip Vite builds if no files have changed.
54
58
  def watched_files_digest
55
- return @last_digest if @last_digest_at && Time.now - @last_digest_at < 1
56
-
57
- config.within_root do
59
+ Dir.chdir File.expand_path(config.root) do
58
60
  files = Dir[*config.watched_paths].reject { |f| File.directory?(f) }
59
61
  file_ids = files.sort.map { |f| "#{ File.basename(f) }/#{ Digest::SHA1.file(f).hexdigest }" }
60
- @last_digest_at = Time.now
61
- @last_digest = Digest::SHA1.hexdigest(file_ids.join('/'))
62
+ Digest::SHA1.hexdigest(file_ids.join('/'))
62
63
  end
63
64
  end
64
65
 
65
66
  # Public: Initiates a Vite build command to generate assets.
67
+ #
68
+ # Returns true if the build is successful, or false if it failed.
66
69
  def build_with_vite(*args)
67
70
  logger.info 'Building with Vite ⚡️'
68
71
 
69
- run(['build', *args])
72
+ stdout, stderr, status = run(['build', *args])
73
+ log_build_result(stdout, stderr.to_s, status)
74
+
75
+ status.success?
70
76
  end
71
77
 
72
78
  # Internal: Outputs the build results.
@@ -75,10 +81,9 @@ private
75
81
  def log_build_result(_stdout, stderr, status)
76
82
  if status.success?
77
83
  logger.info "Build with Vite complete: #{ config.build_output_dir }"
78
- logger.error stderr unless stderr.empty?
84
+ logger.error stderr.to_s unless stderr.empty?
79
85
  else
80
86
  logger.error stderr
81
- logger.error status
82
87
  logger.error 'Build with Vite failed! ❌'
83
88
  logger.error '❌ Check that vite and vite-plugin-ruby are in devDependencies and have been installed. ' if stderr.include?('ERR! canceled')
84
89
  end
@@ -5,10 +5,8 @@ class ViteRuby::CLI::Build < ViteRuby::CLI::Vite
5
5
 
6
6
  desc 'Bundle all entrypoints using Vite.'
7
7
  shared_options
8
- option(:ssr, desc: 'Build the SSR entrypoint instead', type: :boolean)
9
8
  option(:force, desc: 'Force the build even if assets have not changed', type: :boolean)
10
9
  option(:watch, desc: 'Start the Rollup watcher and rebuild on files changes', type: :boolean)
11
- option(:profile, desc: 'Gather performance metrics from the build ', type: :boolean)
12
10
 
13
11
  def call(**options)
14
12
  super { |args| ViteRuby.commands.build_from_task(*args) }
@@ -1,16 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'stringio'
4
- require 'json'
5
4
 
6
5
  class ViteRuby::CLI::Install < Dry::CLI::Command
7
6
  desc 'Performs the initial configuration setup to get started with Vite Ruby.'
8
7
 
9
- option(:package_manager, values: %w[npm pnpm yarn bun], aliases: %w[package-manager with], desc: 'The package manager to use when installing JS dependencies.')
10
-
11
- def call(package_manager: nil, **)
12
- ENV['VITE_RUBY_PACKAGE_MANAGER'] ||= package_manager if package_manager
13
-
8
+ def call(**)
14
9
  $stdout.sync = true
15
10
 
16
11
  say 'Creating binstub'
@@ -84,20 +79,9 @@ private
84
79
  # Internal: Installs vite and vite-plugin-ruby at the project level.
85
80
  def install_js_dependencies
86
81
  package_json = root.join('package.json')
87
- unless package_json.exist?
88
- write package_json, <<~JSON
89
- {
90
- "private": true,
91
- "type": "module"
92
- }
93
- JSON
94
- end
95
-
96
- if (JSON.parse(package_json.read)['type'] != 'module' rescue nil)
97
- FileUtils.mv root.join('vite.config.ts'), root.join('vite.config.mts'), force: true, verbose: true
98
- end
99
-
100
- install_js_packages js_dependencies.join(' ')
82
+ write(package_json, '{}') unless package_json.exist?
83
+ deps = js_dependencies.join(' ')
84
+ run_with_capture("#{ npm_install } -D #{ deps }", stdin_data: "\n")
101
85
  end
102
86
 
103
87
  # Internal: Adds compilation output dirs to git ignore.
@@ -107,7 +91,9 @@ private
107
91
  append(gitignore_file, <<~GITIGNORE)
108
92
 
109
93
  # Vite Ruby
110
- /public/vite*
94
+ /public/vite
95
+ /public/vite-dev
96
+ /public/vite-test
111
97
  node_modules
112
98
  # Vite uses dotenv and suggests to ignore local-only env files. See
113
99
  # https://vitejs.dev/guide/env-and-mode.html#env-files
@@ -127,12 +113,16 @@ private
127
113
  def run_with_capture(*args, **options)
128
114
  Dir.chdir(root) do
129
115
  _, stderr, status = ViteRuby::IO.capture(*args, **options)
130
- say(stderr) unless status.success? || stderr.empty?
116
+ say(stderr) unless status.success? || stderr.to_s.empty?
131
117
  end
132
118
  end
133
119
 
134
- def install_js_packages(deps)
135
- run_with_capture("#{ config.package_manager } add -D #{ deps }", stdin_data: "\n")
120
+ # Internal: Support all popular package managers.
121
+ def npm_install
122
+ return 'yarn add' if root.join('yarn.lock').exist?
123
+ return 'pnpm install' if root.join('pnpm-lock.yaml').exist?
124
+
125
+ 'npm install'
136
126
  end
137
127
 
138
128
  # Internal: Avoid printing warning about missing vite.json, we will create one.
@@ -144,3 +134,8 @@ private
144
134
  $stderr = old_stderr
145
135
  end
146
136
  end
137
+
138
+ # NOTE: This allows framework-specific variants to extend the installation.
139
+ ViteRuby.framework_libraries.each do |_framework, library|
140
+ require "#{ library.name.tr('-', '/') }/installation"
141
+ end
@@ -5,6 +5,7 @@ class ViteRuby::CLI::UpgradePackages < ViteRuby::CLI::Install
5
5
 
6
6
  def call(**)
7
7
  say 'Upgrading npm packages'
8
- install_js_packages js_dependencies.join(' ')
8
+ deps = js_dependencies.join(' ')
9
+ run_with_capture("#{ npm_install } -D #{ deps }")
9
10
  end
10
11
  end
@@ -3,33 +3,18 @@
3
3
  class ViteRuby::CLI::Vite < Dry::CLI::Command
4
4
  CURRENT_ENV = ENV['RACK_ENV'] || ENV['RAILS_ENV']
5
5
 
6
- def self.executable_options
6
+ def self.shared_options
7
7
  option(:mode, default: self::DEFAULT_ENV, values: %w[development production test], aliases: ['m'], desc: 'The build mode for Vite')
8
- option(:node_options, desc: 'Node options for the Vite executable', aliases: ['node-options'])
8
+ option(:clobber, desc: 'Clear cache and previous builds', type: :boolean, aliases: %w[clean clear])
9
+ option(:debug, desc: 'Run Vite in verbose mode, printing all debugging output', aliases: ['verbose'], type: :boolean)
9
10
  option(:inspect, desc: 'Run Vite in a debugging session with node --inspect-brk', aliases: ['inspect-brk'], type: :boolean)
10
11
  option(:trace_deprecation, desc: 'Run Vite in debugging mode with node --trace-deprecation', aliases: ['trace-deprecation'], type: :boolean)
11
12
  end
12
13
 
13
- def self.shared_options
14
- executable_options
15
- option(:debug, desc: 'Run Vite in verbose mode, printing all debugging output', aliases: ['verbose'], type: :boolean)
16
- option(:clobber, desc: 'Clear cache and previous builds', type: :boolean, aliases: %w[clean clear])
17
- end
18
-
19
- def call(mode:, args: [], clobber: false, node_options: nil, inspect: nil, trace_deprecation: nil, **boolean_opts)
14
+ def call(mode:, args: [], clobber: false, **boolean_opts)
20
15
  ViteRuby.env['VITE_RUBY_MODE'] = mode
21
16
  ViteRuby.commands.clobber if clobber
22
-
23
- node_options = [
24
- node_options,
25
- ('--inspect-brk' if inspect),
26
- ('--trace-deprecation' if trace_deprecation),
27
- ].compact.join(' ')
28
-
29
- args << %(--node-options="#{ node_options }") unless node_options.empty?
30
-
31
17
  boolean_opts.map { |name, value| args << "--#{ name }" if value }
32
-
33
18
  yield(args)
34
19
  end
35
20
  end
data/lib/vite_ruby/cli.rb CHANGED
@@ -11,20 +11,7 @@ class ViteRuby::CLI
11
11
  register 'clobber', Clobber, aliases: %w[clean clear]
12
12
  register 'dev', Dev, aliases: %w[d serve]
13
13
  register 'install', Install
14
- register 'ssr', SSR
15
14
  register 'version', Version, aliases: ['v', '-v', '--version', 'info']
16
15
  register 'upgrade', Upgrade, aliases: ['update']
17
16
  register 'upgrade_packages', UpgradePackages, aliases: ['update_packages']
18
-
19
- # Internal: Allows framework-specific variants to extend the CLI.
20
- def self.require_framework_libraries(path = 'cli')
21
- ViteRuby.framework_libraries.each do |_framework, library|
22
- require [library.name.tr('-', '/').to_s, path].compact.join('/')
23
- end
24
- rescue LoadError
25
- require_framework_libraries 'installation' unless path == 'installation'
26
- end
27
17
  end
28
-
29
- # NOTE: This allows framework-specific variants to extend the CLI.
30
- ViteRuby::CLI.require_framework_libraries('cli')
@@ -23,25 +23,47 @@ class ViteRuby::Commands
23
23
 
24
24
  # Public: Removes all build cache and previously compiled assets.
25
25
  def clobber
26
- dirs = [config.build_output_dir, config.ssr_output_dir, config.build_cache_dir, config.vite_cache_dir]
26
+ dirs = [config.build_output_dir, config.build_cache_dir, config.vite_cache_dir]
27
27
  dirs.each { |dir| dir.rmtree if dir.exist? }
28
28
  $stdout.puts "Removed vite cache and output dirs:\n\t#{ dirs.join("\n\t") }"
29
29
  end
30
30
 
31
- # Internal: Installs the binstub for the CLI in the appropriate path.
32
- def install_binstubs
33
- `bundle binstub vite_ruby --path #{ config.root.join('bin') }`
34
- `bundle config --delete bin`
31
+ # Public: Receives arguments from a rake task.
32
+ def clean_from_task(args)
33
+ ensure_log_goes_to_stdout {
34
+ clean(keep_up_to: Integer(args.keep || 2), age_in_seconds: Integer(args.age || 3600))
35
+ }
35
36
  end
36
37
 
37
- # Internal: Checks if the npm version is 6 or lower.
38
- def legacy_npm_version?
39
- `npm --version`.to_i < 7 rescue false
38
+ # Public: Cleanup old assets in the output directory.
39
+ #
40
+ # keep_up_to - Max amount of backups to preserve.
41
+ # age_in_seconds - Amount of time to look back in order to preserve them.
42
+ #
43
+ # NOTE: By default keeps the last version, or 2 if created in the past hour.
44
+ #
45
+ # Examples:
46
+ # To force only 1 backup to be kept: clean(1, 0)
47
+ # To only keep files created within the last 10 minutes: clean(0, 600)
48
+ def clean(keep_up_to: 2, age_in_seconds: 3600)
49
+ return false unless may_clean?
50
+
51
+ versions
52
+ .each_with_index
53
+ .drop_while { |(mtime, _), index|
54
+ max_age = [0, Time.now - Time.at(mtime)].max
55
+ max_age < age_in_seconds || index < keep_up_to
56
+ }
57
+ .each do |(_, files), _|
58
+ clean_files(files)
59
+ end
60
+ true
40
61
  end
41
62
 
42
- # Internal: Checks if the yarn version is 1.x.
43
- def legacy_yarn_version?
44
- `yarn --version`.to_i < 2 rescue false
63
+ # Internal: Installs the binstub for the CLI in the appropriate path.
64
+ def install_binstubs
65
+ `bundle binstub vite_ruby --path #{ config.root.join('bin') }`
66
+ `bundle config --delete bin`
45
67
  end
46
68
 
47
69
  # Internal: Verifies if ViteRuby is properly installed.
@@ -68,7 +90,7 @@ class ViteRuby::Commands
68
90
 
69
91
  # Internal: Prints information about ViteRuby's environment.
70
92
  def print_info
71
- config.within_root do
93
+ Dir.chdir(config.root) do
72
94
  $stdout.puts "bin/vite present?: #{ File.exist? 'bin/vite' }"
73
95
 
74
96
  $stdout.puts "vite_ruby: #{ ViteRuby::VERSION }"
@@ -77,11 +99,11 @@ class ViteRuby::Commands
77
99
  $stdout.puts "#{ framework }: #{ Gem.loaded_specs[framework]&.version }"
78
100
  end
79
101
 
80
- $stdout.puts "ruby: #{ `ruby --version` }"
81
102
  $stdout.puts "node: #{ `node --version` }"
82
-
83
- pkg = config.package_manager
84
- $stdout.puts "#{ pkg }: #{ `#{ pkg } --version` rescue nil }"
103
+ $stdout.puts "npm: #{ `npm --version` }"
104
+ $stdout.puts "yarn: #{ `yarn --version` rescue nil }"
105
+ $stdout.puts "pnpm: #{ `pnpm --version` rescue nil }"
106
+ $stdout.puts "ruby: #{ `ruby --version` }"
85
107
 
86
108
  $stdout.puts "\n"
87
109
  packages = `npm ls vite vite-plugin-ruby`
@@ -98,6 +120,29 @@ private
98
120
 
99
121
  def_delegators :@vite_ruby, :config, :builder, :manifest, :logger, :logger=
100
122
 
123
+ def may_clean?
124
+ config.build_output_dir.exist? && config.manifest_path.exist?
125
+ end
126
+
127
+ def clean_files(files)
128
+ files.select { |file| File.file?(file) }.each do |file|
129
+ File.delete(file)
130
+ logger.info("Removed #{ file }")
131
+ end
132
+ end
133
+
134
+ def versions
135
+ all_files = Dir.glob("#{ config.build_output_dir }/**/*")
136
+ entries = all_files - [config.manifest_path] - current_version_files
137
+ entries.reject { |file| File.directory?(file) }
138
+ .group_by { |file| File.mtime(file).utc.to_i }
139
+ .sort.reverse
140
+ end
141
+
142
+ def current_version_files
143
+ Dir.glob(manifest.refresh.values.map { |value| config.build_output_dir.join("#{ value['file'] }*") })
144
+ end
145
+
101
146
  def with_node_env(env)
102
147
  original = ENV['NODE_ENV']
103
148
  ENV['NODE_ENV'] = env
@@ -24,7 +24,7 @@ module ViteRuby::CompatibilityCheck
24
24
  raise ArgumentError, <<~ERROR
25
25
  vite-plugin-ruby@#{ npm_req } might not be compatible with vite_ruby-#{ ViteRuby::VERSION }
26
26
 
27
- You may disable this check if needed: https://vite-ruby.netlify.app/config/#skipcompatibilitycheck
27
+ You may disable this check if needed: https://vite-ruby.netlify.app/config/#skipCompatibilityCheck
28
28
 
29
29
  You may upgrade both by running:
30
30
 
@@ -5,10 +5,6 @@ require 'json'
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
- def origin
9
- "#{ protocol }://#{ host_with_port }"
10
- end
11
-
12
8
  def protocol
13
9
  https ? 'https' : 'http'
14
10
  end
@@ -17,20 +13,14 @@ class ViteRuby::Config
17
13
  "#{ host }:#{ port }"
18
14
  end
19
15
 
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'),
25
-
26
- # NOTE: Path where vite-plugin-ruby outputs the assets manifest file.
27
- build_output_dir.join('.vite/manifest-assets.json'),
28
- ]
16
+ # Internal: Path where Vite outputs the manifest file.
17
+ def manifest_path
18
+ build_output_dir.join('manifest.json')
29
19
  end
30
20
 
31
- # Internal: Path to the manifest files generated by Vite and vite-plugin-ruby.
32
- def manifest_paths
33
- known_manifest_paths.select(&:exist?)
21
+ # Internal: Path where vite-plugin-ruby outputs the assets manifest file.
22
+ def assets_manifest_path
23
+ build_output_dir.join('manifest-assets.json')
34
24
  end
35
25
 
36
26
  # Public: The directory where Vite will store the built assets.
@@ -60,12 +50,12 @@ class ViteRuby::Config
60
50
  end
61
51
 
62
52
  # Public: Sets additional environment variables for vite-plugin-ruby.
63
- def to_env(env_vars = ViteRuby.env)
53
+ def to_env
64
54
  CONFIGURABLE_WITH_ENV.each_with_object({}) do |option, env|
65
55
  unless (value = @config[option]).nil?
66
56
  env["#{ ViteRuby::ENV_PREFIX }_#{ option.upcase }"] = value.to_s
67
57
  end
68
- end.merge(env_vars)
58
+ end.merge(ViteRuby.env)
69
59
  end
70
60
 
71
61
  # Internal: Files and directories that should be watched for changes.
@@ -80,11 +70,6 @@ class ViteRuby::Config
80
70
  ].freeze
81
71
  end
82
72
 
83
- # Internal: Changes the current directory to the root dir.
84
- def within_root(&block)
85
- Dir.chdir(File.expand_path(root), &block)
86
- end
87
-
88
73
  private
89
74
 
90
75
  # Internal: Coerces all the configuration values, in case they were passed
@@ -92,11 +77,9 @@ private
92
77
  def coerce_values(config)
93
78
  config['mode'] = config['mode'].to_s
94
79
  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
- coerce_booleans(config, 'auto_build', 'hide_build_console_output', 'https', 'skip_compatibility_check', 'skip_proxy')
99
- config['package_manager'] ||= detect_package_manager(root)
80
+ config['root'] = Pathname.new(config['root'])
81
+ config['build_cache_dir'] = config['root'].join(config['build_cache_dir'])
82
+ coerce_booleans(config, 'auto_build', 'hide_build_console_output', 'https', 'skip_compatibility_check')
100
83
  end
101
84
 
102
85
  # Internal: Coerces configuration options to boolean.
@@ -104,15 +87,6 @@ private
104
87
  names.each { |name| config[name] = [true, 'true'].include?(config[name]) }
105
88
  end
106
89
 
107
- def detect_package_manager(root)
108
- return 'npm' if root.join('package-lock.json').exist?
109
- return 'pnpm' if root.join('pnpm-lock.yaml').exist?
110
- return 'bun' if root.join('bun.lockb').exist?
111
- return 'yarn' if root.join('yarn.lock').exist?
112
-
113
- 'npm'
114
- end
115
-
116
90
  def initialize(attrs)
117
91
  @config = attrs.tap { |config| coerce_values(config) }.freeze
118
92
  ViteRuby::CompatibilityCheck.verify_plugin_version(root) unless skip_compatibility_check
@@ -147,7 +121,7 @@ private
147
121
  'config_path' => option_from_env('config_path') || DEFAULT_CONFIG.fetch('config_path'),
148
122
  'mode' => option_from_env('mode') || mode,
149
123
  'root' => option_from_env('root') || root,
150
- }.select { |_, value| value }
124
+ }
151
125
  end
152
126
 
153
127
  # Internal: Used to load a JSON file from the specified path.
@@ -195,15 +169,12 @@ private
195
169
 
196
170
  # Internal: If any of these files is modified the build won't be skipped.
197
171
  DEFAULT_WATCHED_PATHS = %w[
198
- bun.lockb
199
172
  package-lock.json
200
173
  package.json
201
174
  pnpm-lock.yaml
202
175
  postcss.config.js
203
176
  tailwind.config.js
204
177
  vite.config.js
205
- vite.config.mjs
206
- vite.config.mts
207
178
  vite.config.ts
208
179
  windi.config.ts
209
180
  yarn.lock
@@ -38,7 +38,7 @@ private
38
38
  uri
39
39
  .sub(HOST_WITH_PORT_REGEX, '/') # Hanami adds the host and port.
40
40
  .sub('.ts.js', '.ts') # Hanami's javascript helper always adds the extension.
41
- .sub(/\.(sass|scss|styl|stylus|less|pcss|postcss)\.css$/, '.\1') # Rails' stylesheet_link_tag always adds the extension.
41
+ .sub(/(\.(?!min|module)\w+)\.css$/, '\1') # Rails' stylesheet_link_tag always adds the extension.
42
42
  end
43
43
 
44
44
  def forward_to_vite_dev_server(env)
@@ -54,22 +54,17 @@ private
54
54
 
55
55
  def vite_should_handle?(env)
56
56
  path = normalize_uri(env['PATH_INFO'])
57
- return true if path.start_with?(vite_url_prefix) # Vite asset
57
+ return true if path.start_with?(vite_asset_url_prefix) # Vite asset
58
+ return true if path.start_with?(VITE_DEPENDENCY_PREFIX) # Packages and imports
58
59
  return true if file_in_vite_root?(path) # Fallback if Vite can serve the file
59
60
  end
60
61
 
61
- # NOTE: When using an empty 'public_output_dir', we need to rely on a
62
- # filesystem check to check whether Vite should serve the request.
63
62
  def file_in_vite_root?(path)
64
63
  path.include?('.') && # Check for extension, avoid filesystem if possible.
65
64
  config.vite_root_dir.join(path.start_with?('/') ? path[1..-1] : path).file?
66
65
  end
67
66
 
68
- # NOTE: Vite is configured to use 'public_output_dir' as the base, which can
69
- # be customized by the user in development to not match any of the routes.
70
- #
71
- # If the path starts with that prefix, it will be redirected to Vite.
72
- def vite_url_prefix
73
- @vite_url_prefix ||= config.public_output_dir.empty? ? VITE_DEPENDENCY_PREFIX : "/#{ config.public_output_dir }/"
67
+ def vite_asset_url_prefix
68
+ @vite_asset_url_prefix ||= config.public_output_dir.empty? ? "\0" : "/#{ config.public_output_dir }/"
74
69
  end
75
70
  end
data/lib/vite_ruby/io.rb CHANGED
@@ -15,7 +15,7 @@ module ViteRuby::IO
15
15
  stdin.close
16
16
  out = Thread.new { read_lines(stdout, &with_output) }
17
17
  err = Thread.new { stderr.read }
18
- [out.value, err.value.to_s, wait_threads.value]
18
+ [out.value, err.value, wait_threads.value]
19
19
  }
20
20
  end
21
21
 
@@ -7,7 +7,7 @@
7
7
  # lookup_entrypoint('calendar', type: :javascript)
8
8
  # => { "file" => "/vite/assets/calendar-1016838bab065ae1e314.js", "imports" => [] }
9
9
  #
10
- # NOTE: Using `"autoBuild": true` in `config/vite.json` file will trigger a build
10
+ # NOTE: Using "autoBuild": true` in `config/vite.json` file will trigger a build
11
11
  # on demand as needed, before performing any lookup.
12
12
  class ViteRuby::Manifest
13
13
  def initialize(vite_ruby)
@@ -22,16 +22,15 @@ class ViteRuby::Manifest
22
22
  lookup!(name, **options).fetch('file')
23
23
  end
24
24
 
25
- # Public: Returns scripts, imported modules, and stylesheets for the specified
25
+ # Public: Returns entries, imported modules, and stylesheets for the specified
26
26
  # entrypoint files.
27
27
  def resolve_entries(*names, **options)
28
28
  entries = names.map { |name| lookup!(name, **options) }
29
- script_paths = entries.map { |entry| entry.fetch('file') }
30
29
 
31
30
  imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact.uniq
32
31
  {
33
- scripts: script_paths,
34
- imports: imports.map { |entry| entry.fetch('file') }.uniq,
32
+ main: entries.map(&TO_ENTRY),
33
+ imports: imports.map(&TO_ENTRY).uniq,
35
34
  stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq,
36
35
  }
37
36
  end
@@ -51,27 +50,21 @@ class ViteRuby::Manifest
51
50
  if dev_server_running?
52
51
  <<~REACT_REFRESH
53
52
  <script type="module">
54
- #{ react_preamble_code }
53
+ import RefreshRuntime from '#{ prefix_asset_with_host('@react-refresh') }'
54
+ RefreshRuntime.injectIntoGlobalHook(window)
55
+ window.$RefreshReg$ = () => {}
56
+ window.$RefreshSig$ = () => (type) => type
57
+ window.__vite_plugin_react_preamble_installed__ = true
55
58
  </script>
56
59
  REACT_REFRESH
57
60
  end
58
61
  end
59
62
 
60
- # Public: Source script for the React Refresh plugin.
61
- def react_preamble_code
62
- if dev_server_running?
63
- <<~REACT_PREAMBLE_CODE
64
- import RefreshRuntime from '#{ prefix_asset_with_host('@react-refresh') }'
65
- RefreshRuntime.injectIntoGlobalHook(window)
66
- window.$RefreshReg$ = () => {}
67
- window.$RefreshSig$ = () => (type) => type
68
- window.__vite_plugin_react_preamble_installed__ = true
69
- REACT_PREAMBLE_CODE
70
- end
71
- end
72
-
73
63
  protected
74
64
 
65
+ # Internal: Returns a [src, attrs] entry.
66
+ TO_ENTRY = ->(entry) { [entry.fetch('file'), entry.slice('integrity').symbolize_keys] }
67
+
75
68
  # Internal: Strict version of lookup.
76
69
  #
77
70
  # Returns a relative path for the asset, or raises an error if not found.
@@ -87,7 +80,7 @@ protected
87
80
  # manifest.lookup('calendar.js')
88
81
  # => { "file" => "/vite/assets/calendar-1016838bab065ae1e122.js", "imports" => [] }
89
82
  def lookup(name, **options)
90
- @build_mutex.synchronize { builder.build || (return nil) } if should_build?
83
+ @build_mutex.synchronize { builder.build } if should_build?
91
84
 
92
85
  find_manifest_entry resolve_entry_name(name, **options)
93
86
  end
@@ -128,24 +121,19 @@ private
128
121
 
129
122
  # Internal: Loads and merges the manifest files, resolving the asset paths.
130
123
  def load_manifest
131
- files = config.manifest_paths
124
+ files = [config.manifest_path, config.assets_manifest_path].select(&:exist?)
132
125
  files.map { |path| JSON.parse(path.read) }.inject({}, &:merge).tap(&method(:resolve_references))
133
126
  end
134
127
 
135
128
  # Internal: Scopes an asset to the output folder in public, as a path.
136
129
  def prefix_vite_asset(path)
137
- File.join(vite_asset_origin || '/', config.public_output_dir, path)
130
+ File.join("/#{ config.public_output_dir }", path)
138
131
  end
139
132
 
140
133
  # Internal: Prefixes an asset with the `asset_host` for tags that do not use
141
134
  # the framework tag helpers.
142
135
  def prefix_asset_with_host(path)
143
- File.join(vite_asset_origin || config.asset_host || '/', config.public_output_dir, path)
144
- end
145
-
146
- # Internal: The origin of assets managed by Vite.
147
- def vite_asset_origin
148
- config.origin if dev_server_running? && config.skip_proxy
136
+ File.join(config.asset_host || '/', config.public_output_dir, path)
149
137
  end
150
138
 
151
139
  # Internal: Resolves the paths that reference a manifest entry.
@@ -212,7 +200,7 @@ private
212
200
 
213
201
  # Internal: Raises a detailed message when an entry is missing in the manifest.
214
202
  def missing_entry_error(name, **options)
215
- raise ViteRuby::MissingEntrypointError.new(
203
+ raise ViteRuby::MissingEntrypointError, OpenStruct.new(
216
204
  file_name: resolve_entry_name(name, **options),
217
205
  last_build: builder.last_build_metadata,
218
206
  manifest: @manifest,