Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error messages when the Vite executable is missing #41

Merged
merged 5 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ You can customize this behavior using the following options.

Specify the project root.

### viteBinPath

- **Default:** `node_modules/.bin/vite`
- **Env Var:** `VITE_RUBY_VITE_BIN_PATH`

The path where the Vite.js binary is installed. It will be used to execute the `dev` and `build` commands.

### watchAdditionalPaths

- **Default:** `[]`
Expand Down
26 changes: 19 additions & 7 deletions docs/guide/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@
[devServerConnectTimeout]: /config/#devserverconnecttimeout
[host]: /config/#host
[port]: /config/#port
[vite]: https://vitejs.dev/
[vite-plugin-ruby]: https://github.com/ElMassimo/vite_ruby/tree/main/vite-plugin-ruby
[viteBinPath]: /config/#vitebinpath
[docker example]: https://github.com/ElMassimo/vite_rails_docker_example
[Using Heroku]: /guide/deployment#using-heroku
[example app]: https://github.com/ElMassimo/vite_ruby/tree/main/examples/rails/vite.config.ts

# Troubleshooting

This section lists a few common gotchas, and bugs introduced in the past.

Please skim through __before__ opening an [issue][GitHub Issues].

### Missing executable error

Verify that both <kbd>[vite]</kbd> and <kbd>[vite-plugin-ruby]</kbd> are in `devDependencies` in your `package.json` and have been installed by running <kbd>bin/vite info</kbd>.

If you are using a non-standard setup, try configuring <kbd>[viteBinPath]</kbd>.

### Making HMR work in Docker Compose

Using Vite.js with Docker Compose requires configuring [`VITE_RUBY_HOST`][host] in the services.
Expand Down Expand Up @@ -50,12 +60,6 @@ Usually happens when importing code outside the <kbd>[sourceCodeDir]</kbd>.

Add a file path or dir glob in <kbd>[watchAdditionalPaths]</kbd> to ensure changes to those files trigger a new build.

### `bin/vite dev` does not start the server

Make sure that both `vite` and `vite-plugin-ruby` are in `devDependencies` in your `package.json`, and have been installed.

You can verify that they are installed by running <kbd>bin/vite info</kbd>.

### `vite` and `vite-plugin-ruby` were not installed

If you have run <kbd>bundle exec vite install</kbd>, check the output for errors.
Expand All @@ -72,10 +76,18 @@ Make sure you are using [`vite@2.0.5`](https://github.com/vitejs/vite/pull/2309)

A project called [Windi CSS](https://github.com/windicss/windicss) addresses this pain point − I've created a [documentation website](http://windicss.netlify.app/).

A [plugin for Vite.js](https://github.com/windicss/vite-plugin-windicss) is available, and should allow you to get [insanely faster](https://twitter.com/antfu7/status/1361398324587163648) load times in comparison.
A [plugin for Vite.js](https://github.com/windicss/vite-plugin-windicss) is available, and should allow you to get [insanely faster](https://twitter.com/antfu7/status/1361398324587163648) load times in comparison. Check the [example app] for a sample setup.

The creator of Tailwind CSS was impressed by the performance achieved by Windi CSS, and is currently implementing [something similar](https://twitter.com/adamwathan/status/1363638620209446921).

### esbuild: cannot execute binary file

This can happen when using mounted volumes in Docker, and attempting to run Vite from the host.

Since `esbuild` relies on a `postinstall` script, and the architecture of the host usually does not match the architecture of the image, this means the binaries are not compatible.

Try reinstalling `esbuild` in the host.

## Contact ✉️

Please visit [GitHub Issues] to report bugs you find, and [GitHub Discussions] to make feature requests, or to get help.
Expand Down
11 changes: 0 additions & 11 deletions examples/rails/tailwind.config.js

This file was deleted.

13 changes: 13 additions & 0 deletions test/builder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ def test_external_env_variables
refresh_config
end

def test_missing_executable
refresh_config('VITE_RUBY_VITE_BIN_PATH' => 'none/vite')

# It fails because we stub the File.exist? check, so the binary is missing.
error = assert_raises(ViteRuby::MissingExecutableError) {
File.stub(:exist?, true) { builder.build }
}
assert_match 'The vite binary is not available.', error.message

# The provided binary does not exist, so it uses the default strategy.
stub_runner(success: true) { assert builder.build }
end

private

def stub_runner(success:, &block)
Expand Down
2 changes: 1 addition & 1 deletion test/manifest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_lookup_success_with_dev_server_running
def assert_raises_manifest_missing_entry_error(auto_build: false, &block)
error = nil
ViteRuby.config.stub :auto_build, auto_build do
error = assert_raises(ViteRuby::Manifest::MissingEntryError, &block)
error = assert_raises(ViteRuby::MissingEntrypointError, &block)
end
error
end
Expand Down
2 changes: 1 addition & 1 deletion test/runner_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_build_command_with_argument
end

def test_command_capture
ViteRuby::Runner.stub_any_instance(:vite_executable, ['echo']) {
ViteRuby::Runner.stub_any_instance(:vite_executable, 'echo') {
stdout, stderr, status = ViteRuby.run(['"Hello"'], capture: true)
assert_equal %("Hello" --mode production\n), stdout
assert_equal '', stderr
Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def stub_builder(build_successful:, stale: false, &block)
def assert_run_command(*argv, flags: [])
Dir.chdir(test_app_path) {
mock = Minitest::Mock.new
mock.expect(:call, nil, [ViteRuby.config.to_env, 'npx', '--no-install', '--', 'vite', *argv, *flags])
mock.expect(:call, nil, [ViteRuby.config.to_env, %r{node_modules/.bin/vite}, *argv, *flags])
Kernel.stub(:exec, mock) { ViteRuby.run(argv) }
mock.verify
}
Expand Down
1 change: 1 addition & 0 deletions vite-plugin-ruby/default.vite.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
"https": null,
"port": 3036,
"hideBuildConsoleOutput": false,
"viteBinPath": "node_modules/.bin/vite",
"watchAdditionalPaths": []
}
1 change: 1 addition & 0 deletions vite_ruby/default.vite.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
"https": null,
"port": 3036,
"hideBuildConsoleOutput": false,
"viteBinPath": "node_modules/.bin/vite",
"watchAdditionalPaths": []
}
1 change: 0 additions & 1 deletion vite_ruby/lib/tasks/vite.rake
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ namespace :vite do
desc 'Remove the vite build output directory'
task clobber: :'vite:verify_install' do
ViteRuby.commands.clobber
$stdout.puts "Removed vite build output directory #{ ViteRuby.config.build_output_dir }"
end

desc 'Verifies if ViteRuby is properly installed in this application'
Expand Down
6 changes: 3 additions & 3 deletions vite_ruby/lib/vite_ruby/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ def build(*args)

# Public: Removes all build cache and previously compiled assets.
def clobber
config.build_output_dir.rmtree if config.build_output_dir.exist?
config.build_cache_dir.rmtree if config.build_cache_dir.exist?
config.vite_cache_dir.rmtree if config.vite_cache_dir.exist?
dirs = [config.build_output_dir, config.build_cache_dir, config.vite_cache_dir]
dirs.each { |dir| dir.rmtree if dir.exist? }
$stdout.puts "Removed vite cache and output dirs:\n\t#{ dirs.join("\n\t") }"
end

# Public: Receives arguments from a rake task.
Expand Down
2 changes: 1 addition & 1 deletion vite_ruby/lib/vite_ruby/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def resolve_config(**attrs)
}

# Internal: Default values for a Ruby application.
def config_defaults(asset_host: nil, mode: ENV.fetch('RACK_ENV', 'production'), root: Dir.pwd)
def config_defaults(asset_host: nil, mode: ENV.fetch('RACK_ENV', 'development'), root: Dir.pwd)
{
'asset_host' => option_from_env('asset_host') || asset_host,
'config_path' => option_from_env('config_path') || DEFAULT_CONFIG.fetch('config_path'),
Expand Down
11 changes: 11 additions & 0 deletions vite_ruby/lib/vite_ruby/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

# Internal: Provides common functionality for errors.
class ViteRuby::Error < StandardError
def message
super.sub(':troubleshooting:', <<~MSG)
Visit the Troubleshooting guide for more information:
https://vite-ruby.netlify.app/guide/troubleshooting.html#troubleshooting
MSG
end
end
43 changes: 6 additions & 37 deletions vite_ruby/lib/vite_ruby/manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
# NOTE: Using "autoBuild": true` in `config/vite.json` file will trigger a build
# on demand as needed, before performing any lookup.
class ViteRuby::Manifest
class MissingEntryError < StandardError
end

def initialize(vite_ruby)
@vite_ruby = vite_ruby
@build_mutex = Mutex.new if config.auto_build
Expand Down Expand Up @@ -142,39 +139,11 @@ def extension_for_type(entry_type)

# Internal: Raises a detailed message when an entry is missing in the manifest.
def missing_entry_error(name, type: nil, **_options)
file_name = with_file_extension(name, type)
last_build = builder.last_build_metadata
raise ViteRuby::Manifest::MissingEntryError, <<~MSG
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) }.

Possible causes:
#{ possible_causes(last_build) }
Visit the Troubleshooting guide for more information:
https://vite-ruby.netlify.app/guide/troubleshooting.html#troubleshooting
#{ "\nContent in your manifests:\n#{ JSON.pretty_generate(@manifest) }\n" if last_build.success }
#{ "\nLast build in #{ config.mode } mode:\n#{ last_build.to_json }\n" unless last_build.success.nil? }
MSG
raise ViteRuby::MissingEntrypointError, OpenStruct.new(
file_name: with_file_extension(name, type),
last_build: builder.last_build_metadata,
manifest: @manifest,
config: config,
)
end

def possible_causes(last_build)
return FAILED_BUILD_CAUSES.sub(':mode', config.mode) if last_build.success == false
return DEFAULT_CAUSES if config.auto_build

DEFAULT_CAUSES + NO_AUTO_BUILD_CAUSES
end

FAILED_BUILD_CAUSES = <<-MSG
- The last build failed. Try running `RACK_ENV=:mode bin/vite build --force` manually and check for errors.
MSG

DEFAULT_CAUSES = <<-MSG
- The file path is incorrect.
- The file is not in the entrypoints directory.
- Some files are outside the sourceCodeDir, and have not been added to watchAdditionalPaths.
MSG

NO_AUTO_BUILD_CAUSES = <<-MSG
- You have not run `bin/vite dev` to start Vite, or the dev server is not reachable.
- "autoBuild" is set to `false` in your config/vite.json for this environment.
MSG
end
45 changes: 45 additions & 0 deletions vite_ruby/lib/vite_ruby/missing_entrypoint_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

# Internal: Raised when an entry is not found in the build manifest.
#
# NOTE: The complexity here is justified by the improved usability of providing
# a more specific error message depending on the situation.
class ViteRuby::MissingEntrypointError < ViteRuby::Error
extend Forwardable
def_delegators :@info, :file_name, :last_build, :manifest, :config

def initialize(info)
@info = info
super <<~MSG
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) }.

Possible causes:
#{ possible_causes(last_build) }
:troubleshooting:
#{ "\nContent in your manifests:\n#{ JSON.pretty_generate(manifest) }\n" if last_build.success }
#{ "Last build in #{ config.mode } mode:\n#{ last_build.to_json }\n" unless last_build.success.nil? }
MSG
end

def possible_causes(last_build)
return FAILED_BUILD_CAUSES.sub(':mode:', config.mode) if last_build.success == false
return DEFAULT_CAUSES if config.auto_build

DEFAULT_CAUSES + NO_AUTO_BUILD_CAUSES
end

FAILED_BUILD_CAUSES = <<-MSG
- The last build failed. Try running `bin/vite build --force --mode=:mode:` manually and check for errors.
MSG

DEFAULT_CAUSES = <<-MSG
- The file path is incorrect.
- The file is not in the entrypoints directory.
- Some files are outside the sourceCodeDir, and have not been added to watchAdditionalPaths.
MSG

NO_AUTO_BUILD_CAUSES = <<-MSG
- You have not run `bin/vite dev` to start Vite, or the dev server is not reachable.
- "autoBuild" is set to `false` in your config/vite.json for this environment.
MSG
end
13 changes: 13 additions & 0 deletions vite_ruby/lib/vite_ruby/missing_executable_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

# Internal: Raised when the Vite executable can not be found.
class ViteRuby::MissingExecutableError < ViteRuby::Error
def initialize(error = nil)
super <<~MSG
❌ The vite binary is not available. Have you installed Vite?

:troubleshooting:
#{ error }
MSG
end
end
18 changes: 10 additions & 8 deletions vite_ruby/lib/vite_ruby/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ def initialize(vite_ruby)

# Public: Executes Vite with the specified arguments.
def run(argv, capture: false)
if capture
capture3_with_output(*command_for(argv), chdir: config.root)
else
Dir.chdir(config.root) { Kernel.exec(*command_for(argv)) }
end
Dir.chdir(config.root) {
cmd = command_for(argv)
capture ? capture3_with_output(*cmd, chdir: config.root) : Kernel.exec(*cmd)
}
rescue Errno::ENOENT => error
raise ViteRuby::MissingExecutableError, error
end

private
Expand All @@ -29,15 +30,16 @@ def command_for(args)
args = args.clone
cmd.append('node', '--inspect-brk') if args.delete('--inspect')
cmd.append('node', '--trace-deprecation') if args.delete('--trace_deprecation')
cmd.append(*vite_executable(cmd))
cmd.append(vite_executable)
cmd.append(*args)
cmd.append('--mode', config.mode) unless args.include?('--mode') || args.include?('-m')
end
end

# Internal: Resolves to an executable for Vite.
def vite_executable(cmd)
cmd.include?('node') ? ['./node_modules/.bin/vite'] : ['npx', '--no-install', '--', 'vite']
def vite_executable
bin_path = config.vite_bin_path
File.exist?(bin_path) ? bin_path : "#{ `npm bin`.chomp }/vite"
end

# Internal: A modified version of capture3 that continuosly prints stdout.
Expand Down