vite_ruby 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +82 -0
- data/default.vite.json +17 -0
- data/exe/vite +10 -0
- data/lib/tasks/vite.rake +51 -0
- data/lib/vite_ruby.rb +125 -0
- data/lib/vite_ruby/builder.rb +103 -0
- data/lib/vite_ruby/cli.rb +14 -0
- data/lib/vite_ruby/cli/build.rb +18 -0
- data/lib/vite_ruby/cli/dev.rb +12 -0
- data/lib/vite_ruby/cli/install.rb +122 -0
- data/lib/vite_ruby/cli/version.rb +9 -0
- data/lib/vite_ruby/commands.rb +153 -0
- data/lib/vite_ruby/config.rb +152 -0
- data/lib/vite_ruby/dev_server_proxy.rb +62 -0
- data/lib/vite_ruby/manifest.rb +168 -0
- data/lib/vite_ruby/runner.rb +57 -0
- data/lib/vite_ruby/version.rb +5 -0
- data/templates/config/vite.config.ts +8 -0
- data/templates/config/vite.json +15 -0
- data/templates/entrypoints/application.js +8 -0
- metadata +153 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/cli'
|
4
|
+
|
5
|
+
# Public: Command line interface that allows to install the library, and run
|
6
|
+
# simple commands.
|
7
|
+
class ViteRuby::CLI
|
8
|
+
extend Dry::CLI::Registry
|
9
|
+
|
10
|
+
register 'build', Build, aliases: ['b']
|
11
|
+
register 'dev', Dev, aliases: %w[d serve]
|
12
|
+
register 'install', Install, aliases: %w[setup init]
|
13
|
+
register 'version', Version, aliases: ['v', '-v', '--version', 'info']
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViteRuby::CLI::Build < Dry::CLI::Command
|
4
|
+
CURRENT_ENV = ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
5
|
+
DEFAULT_ENV = CURRENT_ENV || 'production'
|
6
|
+
|
7
|
+
def self.shared_options
|
8
|
+
option(:mode, default: self::DEFAULT_ENV, values: %w[development production], aliases: ['m'], desc: 'The build mode for Vite.')
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Bundle all entrypoints using Vite.'
|
12
|
+
shared_options
|
13
|
+
|
14
|
+
def call(mode:)
|
15
|
+
ViteRuby.env['VITE_RUBY_MODE'] = mode
|
16
|
+
block_given? ? yield(mode) : ViteRuby.commands.build_from_task
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ViteRuby::CLI::Dev < ViteRuby::CLI::Build
|
4
|
+
DEFAULT_ENV = CURRENT_ENV || 'development'
|
5
|
+
|
6
|
+
desc 'Start the Vite development server.'
|
7
|
+
shared_options
|
8
|
+
|
9
|
+
def call(mode:, args: [])
|
10
|
+
super(mode: mode) { ViteRuby.run(args) }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/cli/utils/files'
|
4
|
+
require 'stringio'
|
5
|
+
require 'open3'
|
6
|
+
|
7
|
+
class ViteRuby::CLI::Install < Dry::CLI::Command
|
8
|
+
desc 'Performs the initial configuration setup to get started with Vite Ruby.'
|
9
|
+
|
10
|
+
def call(**)
|
11
|
+
$stdout.sync = true
|
12
|
+
|
13
|
+
say 'Creating binstub'
|
14
|
+
ViteRuby.commands.install_binstubs
|
15
|
+
|
16
|
+
say 'Creating configuration files'
|
17
|
+
create_configuration_files
|
18
|
+
|
19
|
+
say 'Installing sample files'
|
20
|
+
install_sample_files
|
21
|
+
|
22
|
+
say 'Installing js dependencies'
|
23
|
+
install_js_dependencies
|
24
|
+
|
25
|
+
say 'Adding files to .gitignore'
|
26
|
+
install_gitignore
|
27
|
+
|
28
|
+
say "\nVite ⚡️ Ruby successfully installed! 🎉"
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# Internal: Setup for a plain Rack application.
|
34
|
+
def setup_app_files
|
35
|
+
copy_template 'config/vite.json', to: config.config_path
|
36
|
+
|
37
|
+
if (rackup_file = root.join('config.ru')).exist?
|
38
|
+
inject_line_after_last rackup_file, 'require', 'use(ViteRuby::DevServerProxy, ssl_verify_none: true) if ViteRuby.run_proxy?'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Create a sample JS file and attempt to inject it in an HTML template.
|
43
|
+
def install_sample_files
|
44
|
+
copy_template 'entrypoints/application.js', to: config.resolved_entrypoints_dir.join('application.js')
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
extend Forwardable
|
50
|
+
|
51
|
+
def_delegators 'ViteRuby', :config
|
52
|
+
|
53
|
+
%i[append cp inject_line_after inject_line_after_last inject_line_before write].each do |util|
|
54
|
+
define_method(util) { |*args, **opts, &block|
|
55
|
+
Dry::CLI::Utils::Files.send(util, *args, **opts, &block) rescue nil
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
TEMPLATES_PATH = Pathname.new(File.expand_path('../../../templates', __dir__))
|
60
|
+
|
61
|
+
def copy_template(path, to:)
|
62
|
+
cp TEMPLATES_PATH.join(path), to
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: Creates the Vite and vite-plugin-ruby configuration files.
|
66
|
+
def create_configuration_files
|
67
|
+
copy_template 'config/vite.config.ts', to: root.join('vite.config.ts')
|
68
|
+
setup_app_files
|
69
|
+
ViteRuby.reload_with('VITE_RUBY_CONFIG_PATH' => config.config_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Internal: Installs vite and vite-plugin-ruby at the project level.
|
73
|
+
def install_js_dependencies
|
74
|
+
package_json = root.join('package.json')
|
75
|
+
write(package_json, '{}') unless package_json.exist?
|
76
|
+
Dir.chdir(root) do
|
77
|
+
deps = "vite@#{ ViteRuby::DEFAULT_VITE_VERSION } vite-plugin-ruby@#{ ViteRuby::DEFAULT_PLUGIN_VERSION }"
|
78
|
+
stdout, stderr, status = Open3.capture3({ 'CI' => 'true' }, "npx ni -D #{ deps }")
|
79
|
+
stdout, stderr, = Open3.capture3({}, "yarn add -D #{ deps }") unless status.success?
|
80
|
+
say(stdout, "\n", stderr)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Internal: Adds compilation output dirs to git ignore.
|
85
|
+
def install_gitignore
|
86
|
+
return unless (gitignore_file = root.join('.gitignore')).exist?
|
87
|
+
|
88
|
+
append(gitignore_file, <<~GITIGNORE)
|
89
|
+
|
90
|
+
# Vite Ruby
|
91
|
+
/public/vite
|
92
|
+
/public/vite-dev
|
93
|
+
/public/vite-test
|
94
|
+
node_modules
|
95
|
+
*.local
|
96
|
+
.DS_Store
|
97
|
+
GITIGNORE
|
98
|
+
end
|
99
|
+
|
100
|
+
# Internal: The root path for the Ruby application.
|
101
|
+
def root
|
102
|
+
@root ||= silent_warnings { config.root }
|
103
|
+
end
|
104
|
+
|
105
|
+
def say(*args)
|
106
|
+
$stdout.puts(*args)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Internal: Avoid printing warning about missing vite.json, we will create one.
|
110
|
+
def silent_warnings
|
111
|
+
old_stderr = $stderr
|
112
|
+
$stderr = StringIO.new
|
113
|
+
yield
|
114
|
+
ensure
|
115
|
+
$stderr = old_stderr
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# NOTE: This allows framework-specific variants to extend the installation.
|
120
|
+
ViteRuby.framework_libraries.each do |_framework, library|
|
121
|
+
require "#{ library.name }/installation"
|
122
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Public: Encapsulates common tasks, available both programatically and from the
|
4
|
+
# CLI and Rake tasks.
|
5
|
+
class ViteRuby::Commands
|
6
|
+
def initialize(vite_ruby)
|
7
|
+
@vite_ruby = vite_ruby
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Defaults to production, and exits if the build fails.
|
11
|
+
def build_from_task
|
12
|
+
with_node_env(ENV.fetch('NODE_ENV', 'production')) {
|
13
|
+
ensure_log_goes_to_stdout {
|
14
|
+
build || exit!
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Builds all assets that are managed by Vite, from the entrypoints.
|
20
|
+
def build
|
21
|
+
builder.build.tap { manifest.refresh }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Removes all build cache and previously compiled assets.
|
25
|
+
def clobber
|
26
|
+
config.build_output_dir.rmtree if config.build_output_dir.exist?
|
27
|
+
config.build_cache_dir.rmtree if config.build_cache_dir.exist?
|
28
|
+
config.vite_cache_dir.rmtree if config.vite_cache_dir.exist?
|
29
|
+
end
|
30
|
+
|
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
|
+
}
|
36
|
+
end
|
37
|
+
|
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
|
61
|
+
end
|
62
|
+
|
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
|
+
end
|
67
|
+
|
68
|
+
# Internal: Verifies if ViteRuby is properly installed.
|
69
|
+
def verify_install
|
70
|
+
unless File.exist?(config.root.join('bin/vite'))
|
71
|
+
warn <<~WARN
|
72
|
+
vite binstub not found.
|
73
|
+
Have you run `bundle binstub vite`?
|
74
|
+
Make sure the bin directory and bin/vite are not included in .gitignore
|
75
|
+
WARN
|
76
|
+
end
|
77
|
+
|
78
|
+
config_path = config.root.join(config.config_path)
|
79
|
+
unless config_path.exist?
|
80
|
+
warn <<~WARN
|
81
|
+
Configuration #{ config_path } file for vite-plugin-ruby not found.
|
82
|
+
Make sure `bundle exec vite install` has run successfully before running dependent tasks.
|
83
|
+
WARN
|
84
|
+
exit!
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Internal: Prints information about ViteRuby's environment.
|
89
|
+
def print_info
|
90
|
+
Dir.chdir(config.root) do
|
91
|
+
$stdout.puts "Is bin/vite present?: #{ File.exist? 'bin/vite' }"
|
92
|
+
|
93
|
+
$stdout.puts "vite_ruby: #{ ViteRuby::VERSION }"
|
94
|
+
ViteRuby.framework_libraries.each do |framework, library|
|
95
|
+
$stdout.puts "#{ library.name }: #{ library.version }"
|
96
|
+
$stdout.puts "#{ framework }: #{ Gem.loaded_specs[framework]&.version }"
|
97
|
+
end
|
98
|
+
|
99
|
+
$stdout.puts "node: #{ `node --version` }"
|
100
|
+
$stdout.puts "npm: #{ `npm --version` }"
|
101
|
+
$stdout.puts "yarn: #{ `yarn --version` }"
|
102
|
+
$stdout.puts "ruby: #{ `ruby --version` }"
|
103
|
+
|
104
|
+
$stdout.puts "\n"
|
105
|
+
$stdout.puts "vite-plugin-ruby: \n#{ `npm list vite-plugin-ruby version` }"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
extend Forwardable
|
112
|
+
|
113
|
+
def_delegators :@vite_ruby, :config, :builder, :manifest, :logger, :logger=
|
114
|
+
|
115
|
+
def may_clean?
|
116
|
+
config.build_output_dir.exist? && config.manifest_path.exist?
|
117
|
+
end
|
118
|
+
|
119
|
+
def clean_files(files)
|
120
|
+
files.select { |file| File.file?(file) }.each do |file|
|
121
|
+
File.delete(file)
|
122
|
+
logger.info("Removed #{ file }")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def versions
|
127
|
+
all_files = Dir.glob("#{ config.build_output_dir }/**/*")
|
128
|
+
entries = all_files - [config.manifest_path] - current_version_files
|
129
|
+
entries.reject { |file| File.directory?(file) }
|
130
|
+
.group_by { |file| File.mtime(file).utc.to_i }
|
131
|
+
.sort.reverse
|
132
|
+
end
|
133
|
+
|
134
|
+
def current_version_files
|
135
|
+
Dir.glob(manifest.refresh.values.map { |value| config.build_output_dir.join("#{ value['file'] }*") })
|
136
|
+
end
|
137
|
+
|
138
|
+
def with_node_env(env)
|
139
|
+
original = ENV['NODE_ENV']
|
140
|
+
ENV['NODE_ENV'] = env
|
141
|
+
yield
|
142
|
+
ensure
|
143
|
+
ENV['NODE_ENV'] = original
|
144
|
+
end
|
145
|
+
|
146
|
+
def ensure_log_goes_to_stdout
|
147
|
+
old_logger = logger
|
148
|
+
self.logger = Logger.new($stdout)
|
149
|
+
yield
|
150
|
+
ensure
|
151
|
+
self.logger = old_logger
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# Public: Allows to resolve configuration sourced from `config/vite.json` and
|
6
|
+
# environment variables, combining them with the default options.
|
7
|
+
class ViteRuby::Config
|
8
|
+
def protocol
|
9
|
+
https ? 'https' : 'http'
|
10
|
+
end
|
11
|
+
|
12
|
+
def host_with_port
|
13
|
+
"#{ host }:#{ port }"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Internal: Path where Vite outputs the manifest file.
|
17
|
+
def manifest_path
|
18
|
+
build_output_dir.join('manifest.json')
|
19
|
+
end
|
20
|
+
|
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')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: The directory where Vite will store the built assets.
|
27
|
+
def build_output_dir
|
28
|
+
root.join(public_dir, public_output_dir)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: The directory where the entries are located.
|
32
|
+
def resolved_entrypoints_dir
|
33
|
+
root.join(source_code_dir, entrypoints_dir)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Internal: The directory where Vite stores its processing cache.
|
37
|
+
def vite_cache_dir
|
38
|
+
root.join('node_modules/.vite')
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Sets additional environment variables for vite-plugin-ruby.
|
42
|
+
def to_env
|
43
|
+
CONFIGURABLE_WITH_ENV.each_with_object({}) do |option, env|
|
44
|
+
unless (value = @config[option]).nil?
|
45
|
+
env["#{ ViteRuby::ENV_PREFIX }_#{ option.upcase }"] = value.to_s
|
46
|
+
end
|
47
|
+
end.merge(ViteRuby.env)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Internal: Coerces all the configuration values, in case they were passed
|
53
|
+
# as environment variables which are always strings.
|
54
|
+
def coerce_values(config)
|
55
|
+
config['mode'] = config['mode'].to_s
|
56
|
+
config['port'] = config['port'].to_i
|
57
|
+
config['root'] = Pathname.new(config['root'])
|
58
|
+
config['build_cache_dir'] = config['root'].join(config['build_cache_dir'])
|
59
|
+
coerce_booleans(config, 'auto_build', 'hide_build_console_output', 'https')
|
60
|
+
end
|
61
|
+
|
62
|
+
# Internal: Coerces configuration options to boolean.
|
63
|
+
def coerce_booleans(config, *names)
|
64
|
+
names.each { |name| config[name] = [true, 'true'].include?(config[name]) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(attrs)
|
68
|
+
@config = attrs.tap { |config| coerce_values(config) }.freeze
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
private :new
|
73
|
+
|
74
|
+
# Public: Returns the project configuration for Vite.
|
75
|
+
def resolve_config(**attrs)
|
76
|
+
config = config_defaults.merge(attrs.transform_keys(&:to_s))
|
77
|
+
file_path = File.join(config['root'], config['config_path'])
|
78
|
+
file_config = config_from_file(file_path, mode: config['mode'])
|
79
|
+
new DEFAULT_CONFIG.merge(file_config).merge(config_from_env).merge(config)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Internal: Converts camelCase to snake_case.
|
85
|
+
SNAKE_CASE = ->(camel_cased_word) {
|
86
|
+
camel_cased_word.to_s.gsub(/::/, '/')
|
87
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
88
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
89
|
+
.tr('-', '_')
|
90
|
+
.downcase
|
91
|
+
}
|
92
|
+
|
93
|
+
# Internal: Default values for a Ruby application.
|
94
|
+
def config_defaults(asset_host: nil, mode: ENV.fetch('RACK_ENV', 'production'), root: Dir.pwd)
|
95
|
+
{
|
96
|
+
'asset_host' => option_from_env('asset_host') || asset_host,
|
97
|
+
'config_path' => option_from_env('config_path') || DEFAULT_CONFIG.fetch('config_path'),
|
98
|
+
'mode' => option_from_env('mode') || mode,
|
99
|
+
'root' => option_from_env('root') || root,
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Internal: Used to load a JSON file from the specified path.
|
104
|
+
def load_json(path)
|
105
|
+
JSON.parse(File.read(File.expand_path(path))).each do |_env, config|
|
106
|
+
config.transform_keys!(&SNAKE_CASE) if config.is_a?(Hash)
|
107
|
+
end.tap do |config|
|
108
|
+
config.transform_keys!(&SNAKE_CASE)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Internal: Retrieves a configuration option from environment variables.
|
113
|
+
def option_from_env(name)
|
114
|
+
ViteRuby.env["#{ ViteRuby::ENV_PREFIX }_#{ name.upcase }"]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Internal: Extracts the configuration options provided as env vars.
|
118
|
+
def config_from_env
|
119
|
+
CONFIGURABLE_WITH_ENV.each_with_object({}) do |option, env_vars|
|
120
|
+
if value = option_from_env(option)
|
121
|
+
env_vars[option] = value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Internal: Loads the configuration options provided in a JSON file.
|
127
|
+
def config_from_file(path, mode:)
|
128
|
+
multi_env_config = load_json(path)
|
129
|
+
multi_env_config.fetch('all', {})
|
130
|
+
.merge(multi_env_config.fetch(mode, {}))
|
131
|
+
rescue Errno::ENOENT => error
|
132
|
+
warn "Check that your vite.json configuration file is available in the load path:\n\n\t#{ error.message }\n\n"
|
133
|
+
{}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Internal: Shared configuration with the Vite plugin for Ruby.
|
138
|
+
DEFAULT_CONFIG = load_json("#{ __dir__ }/../../default.vite.json").freeze
|
139
|
+
|
140
|
+
# Internal: Configuration options that can not be provided as env vars.
|
141
|
+
NOT_CONFIGURABLE_WITH_ENV = %w[watch_additional_paths].freeze
|
142
|
+
|
143
|
+
# Internal: Configuration options that can be provided as env vars.
|
144
|
+
CONFIGURABLE_WITH_ENV = (DEFAULT_CONFIG.keys + %w[mode root] - NOT_CONFIGURABLE_WITH_ENV).freeze
|
145
|
+
|
146
|
+
public
|
147
|
+
|
148
|
+
# Define getters for the configuration options.
|
149
|
+
(CONFIGURABLE_WITH_ENV + NOT_CONFIGURABLE_WITH_ENV).each do |option|
|
150
|
+
define_method(option) { @config[option] }
|
151
|
+
end
|
152
|
+
end
|