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

feat: add package_manager config option, experimental support for bun #481

Merged
merged 5 commits into from
Jul 17, 2024
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
12 changes: 12 additions & 0 deletions docs/src/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,18 @@ You can customize this behavior using the following options.

Allows to skip Vite build output from logs, to keep the noise down.

### packageManager

- **Default:** auto-detected based on existing lockfiles, otherwise `"npm"`
- **Env Var:** `VITE_RUBY_PACKAGE_MANAGER`

Allows to specify which package manager to use, such as:

- `npm`
- `pnpm`
- `yarn`
- `bun` (experimental)

### root

- **Default:** `Rails.root`
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 @@ -7,6 +7,7 @@
"publicOutputDir": "vite",
"configPath": "config/vite.json",
"devServerConnectTimeout": 0.01,
"packageManager": null,
"publicDir": "public",
"entrypointsDir": "entrypoints",
"sourceCodeDir": "app/frontend",
Expand Down
1 change: 1 addition & 0 deletions vite_ruby/default.vite.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"publicOutputDir": "vite",
"configPath": "config/vite.json",
"devServerConnectTimeout": 0.01,
"packageManager": null,
"publicDir": "public",
"entrypointsDir": "entrypoints",
"sourceCodeDir": "app/frontend",
Expand Down
11 changes: 7 additions & 4 deletions vite_ruby/lib/tasks/vite.rake
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ namespace :vite do
desc 'Ensure build dependencies like Vite are installed before bundling'
task :install_dependencies do
install_env_args = ENV['VITE_RUBY_SKIP_INSTALL_DEV_DEPENDENCIES'] == 'true' ? {} : { 'NODE_ENV' => 'development' }
cmd = ViteRuby.commands.legacy_npm_version? ? 'npx ci --yes' : 'npx --yes ci'
result = system(install_env_args, cmd)
# Fallback to `yarn` if `npx` is not available.
system(install_env_args, 'yarn install --frozen-lockfile') if result.nil?

install_cmd = case (pkg = ViteRuby.config.package_manager)
when 'npm' then 'npm ci'
else "#{ pkg } install --frozen-lockfile"
end

system(install_env_args, install_cmd)
end

desc "Provide information on ViteRuby's environment"
Expand Down
17 changes: 8 additions & 9 deletions vite_ruby/lib/vite_ruby/cli/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
class ViteRuby::CLI::Install < Dry::CLI::Command
desc 'Performs the initial configuration setup to get started with Vite Ruby.'

def call(**)
option(:package_manager, values: %w[npm pnpm yarn bun], aliases: %w[package-manager with], desc: 'The package manager to use when installing JS dependencies.')

def call(package_manager: nil, **)
ENV['VITE_RUBY_PACKAGE_MANAGER'] ||= package_manager if package_manager

$stdout.sync = true

say 'Creating binstub'
Expand Down Expand Up @@ -93,8 +97,7 @@ def install_js_dependencies
FileUtils.mv root.join('vite.config.ts'), root.join('vite.config.mts'), force: true, verbose: true
end

deps = js_dependencies.join(' ')
run_with_capture("#{ npm_install } -D #{ deps }", stdin_data: "\n")
install_js_packages js_dependencies.join(' ')
end

# Internal: Adds compilation output dirs to git ignore.
Expand Down Expand Up @@ -128,12 +131,8 @@ def run_with_capture(*args, **options)
end
end

# Internal: Support all popular package managers.
def npm_install
return 'yarn add' if root.join('yarn.lock').exist?
return 'pnpm install' if root.join('pnpm-lock.yaml').exist?

'npm install'
def install_js_packages(deps)
run_with_capture("#{ config.package_manager } add -D #{ deps }", stdin_data: "\n")
end

# Internal: Avoid printing warning about missing vite.json, we will create one.
Expand Down
3 changes: 1 addition & 2 deletions vite_ruby/lib/vite_ruby/cli/upgrade_packages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ class ViteRuby::CLI::UpgradePackages < ViteRuby::CLI::Install

def call(**)
say 'Upgrading npm packages'
deps = js_dependencies.join(' ')
run_with_capture("#{ npm_install } -D #{ deps }")
install_js_packages js_dependencies.join(' ')
end
end
8 changes: 4 additions & 4 deletions vite_ruby/lib/vite_ruby/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ def print_info
$stdout.puts "#{ framework }: #{ Gem.loaded_specs[framework]&.version }"
end

$stdout.puts "node: #{ `node --version` }"
$stdout.puts "npm: #{ `npm --version` }"
$stdout.puts "yarn: #{ `yarn --version` rescue nil }"
$stdout.puts "pnpm: #{ `pnpm --version` rescue nil }"
$stdout.puts "ruby: #{ `ruby --version` }"
$stdout.puts "node: #{ `node --version` }"

pkg = config.package_manager
$stdout.puts "#{ pkg }: #{ `#{ pkg } --version` rescue nil }"

$stdout.puts "\n"
packages = `npm ls vite vite-plugin-ruby`
Expand Down
19 changes: 15 additions & 4 deletions vite_ruby/lib/vite_ruby/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,27 @@ def within_root(&block)
def coerce_values(config)
config['mode'] = config['mode'].to_s
config['port'] = config['port'].to_i
config['root'] = Pathname.new(config['root'])
config['build_cache_dir'] = config['root'].join(config['build_cache_dir'])
config['ssr_output_dir'] = config['root'].join(config['ssr_output_dir'])
config['root'] = root = Pathname.new(config['root'])
config['build_cache_dir'] = root.join(config['build_cache_dir'])
config['ssr_output_dir'] = root.join(config['ssr_output_dir'])
coerce_booleans(config, 'auto_build', 'hide_build_console_output', 'https', 'skip_compatibility_check', 'skip_proxy')
config['package_manager'] ||= detect_package_manager(root)
end

# Internal: Coerces configuration options to boolean.
def coerce_booleans(config, *names)
names.each { |name| config[name] = [true, 'true'].include?(config[name]) }
end

def detect_package_manager(root)
return 'npm' if root.join('package-lock.json').exist?
return 'pnpm' if root.join('pnpm-lock.yaml').exist?
return 'bun' if root.join('bun.lockb').exist?
return 'yarn' if root.join('yarn.lock').exist?

'npm'
end

def initialize(attrs)
@config = attrs.tap { |config| coerce_values(config) }.freeze
ViteRuby::CompatibilityCheck.verify_plugin_version(root) unless skip_compatibility_check
Expand Down Expand Up @@ -189,15 +199,16 @@ def config_from_file(path, mode:)

# Internal: If any of these files is modified the build won't be skipped.
DEFAULT_WATCHED_PATHS = %w[
bun.lockb
package-lock.json
package.json
pnpm-lock.yaml
postcss.config.js
tailwind.config.js
vite.config.js
vite.config.mjs
vite.config.ts
vite.config.mts
vite.config.ts
windi.config.ts
yarn.lock
].freeze
Expand Down
23 changes: 11 additions & 12 deletions vite_ruby/lib/vite_ruby/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,26 @@ def run(argv, exec: false)
# Internal: Returns an Array with the command to run.
def command_for(args)
[config.to_env(env)].tap do |cmd|
npx_options, vite_args = args.partition { |arg| arg.start_with?('--node-options') }
cmd.push(*vite_executable)

# NOTE: Vite will parse args, so we need to disambiguate and pass them to
# `npx` before specifying the `vite` executable.
cmd.insert(-2, *npx_options) unless npx_options.empty?

exec_args, vite_args = args.partition { |arg| arg.start_with?('--node-options') }
cmd.push(*vite_executable(*exec_args))
cmd.push(*vite_args)
cmd.push('--mode', config.mode) unless args.include?('--mode') || args.include?('-m')
end
end

# Internal: Resolves to an executable for Vite.
def vite_executable
def vite_executable(*exec_args)
bin_path = config.vite_bin_path
return [bin_path] if bin_path && File.exist?(bin_path)

if config.root.join('yarn.lock').exist?
%w[yarn vite]
else
%w[npx vite]
x = case config.package_manager
when 'npm' then %w[npx]
when 'pnpm' then %w[pnpm exec]
when 'bun' then %w[bun x]
when 'yarn' then %w[yarn]
else raise ArgumentError, "Unknown package manager #{ config.package_manager.inspect }"
end

[*x, *exec_args, 'vite']
end
end