vite_rails 1.0.12 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +39 -34
  4. data/lib/tasks/vite.rake +17 -0
  5. data/lib/vite_rails.rb +5 -82
  6. data/lib/vite_rails/config.rb +11 -135
  7. data/lib/vite_rails/engine.rb +7 -11
  8. data/lib/vite_rails/installation.rb +47 -0
  9. data/lib/vite_rails/tag_helpers.rb +61 -0
  10. data/lib/vite_rails/version.rb +2 -2
  11. data/{test/mounted_app/test/dummy/config/vite.json → templates/config/rails-vite.json} +1 -0
  12. data/{lib/install/javascript → templates}/entrypoints/application.js +0 -0
  13. metadata +25 -129
  14. data/lib/install/bin/vite +0 -17
  15. data/lib/install/binstubs.rb +0 -6
  16. data/lib/install/config/vite.config.ts +0 -8
  17. data/lib/install/config/vite.json +0 -14
  18. data/lib/install/template.rb +0 -40
  19. data/lib/tasks/vite/binstubs.rake +0 -12
  20. data/lib/tasks/vite/build.rake +0 -39
  21. data/lib/tasks/vite/clean.rake +0 -23
  22. data/lib/tasks/vite/clobber.rake +0 -20
  23. data/lib/tasks/vite/info.rake +0 -20
  24. data/lib/tasks/vite/install.rake +0 -12
  25. data/lib/tasks/vite/install_dependencies.rake +0 -14
  26. data/lib/tasks/vite/verify_install.rake +0 -23
  27. data/lib/vite_rails/builder.rb +0 -111
  28. data/lib/vite_rails/commands.rb +0 -109
  29. data/lib/vite_rails/dev_server.rb +0 -23
  30. data/lib/vite_rails/dev_server_proxy.rb +0 -57
  31. data/lib/vite_rails/helper.rb +0 -71
  32. data/lib/vite_rails/manifest.rb +0 -143
  33. data/lib/vite_rails/runner.rb +0 -53
  34. data/package.json +0 -19
  35. data/package/default.vite.json +0 -16
  36. data/test/builder_test.rb +0 -77
  37. data/test/commands_test.rb +0 -67
  38. data/test/config_test.rb +0 -133
  39. data/test/dev_server_proxy_test.rb +0 -102
  40. data/test/dev_server_test.rb +0 -9
  41. data/test/engine_rake_tasks_test.rb +0 -81
  42. data/test/helper_test.rb +0 -75
  43. data/test/manifest_test.rb +0 -85
  44. data/test/mode_test.rb +0 -16
  45. data/test/mounted_app/Rakefile +0 -6
  46. data/test/mounted_app/test/dummy/Rakefile +0 -5
  47. data/test/mounted_app/test/dummy/bin/rails +0 -5
  48. data/test/mounted_app/test/dummy/bin/rake +0 -5
  49. data/test/mounted_app/test/dummy/config.ru +0 -7
  50. data/test/mounted_app/test/dummy/config/application.rb +0 -12
  51. data/test/mounted_app/test/dummy/config/environment.rb +0 -5
  52. data/test/mounted_app/test/dummy/package.json +0 -8
  53. data/test/mounted_app/test/dummy/yarn.lock +0 -208
  54. data/test/rake_tasks_test.rb +0 -60
  55. data/test/runner_test.rb +0 -31
  56. data/test/test_app/Rakefile +0 -5
  57. data/test/test_app/app/frontend/entrypoints/application.js +0 -2
  58. data/test/test_app/bin/vite +0 -17
  59. data/test/test_app/config.ru +0 -7
  60. data/test/test_app/config/application.rb +0 -13
  61. data/test/test_app/config/environment.rb +0 -6
  62. data/test/test_app/config/vite.json +0 -18
  63. data/test/test_app/config/vite_additional_paths.json +0 -5
  64. data/test/test_app/config/vite_public_dir.json +0 -5
  65. data/test/test_app/package.json +0 -13
  66. data/test/test_app/public/vite-production/manifest-assets.json +0 -10
  67. data/test/test_app/public/vite-production/manifest.json +0 -39
  68. data/test/test_app/some.config.js +0 -0
  69. data/test/test_app/yarn.lock +0 -11
  70. data/test/test_helper.rb +0 -68
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $stdout.sync = true
4
-
5
- namespace :vite do
6
- desc 'Remove old compiled vites'
7
- task :clean, [:keep, :age] => [:'vite:verify_install', :environment] do |_, args|
8
- ViteRails.clean_from_rake(args)
9
- end
10
- end
11
-
12
- skip_vite_clean = %w[no false n f].include?(ENV['VITE_RUBY_PRECOMPILE'])
13
-
14
- unless skip_vite_clean
15
- # Run clean if the assets:clean is run
16
- if Rake::Task.task_defined?('assets:clean')
17
- Rake::Task['assets:clean'].enhance do
18
- Rake::Task['vite:clean'].invoke
19
- end
20
- else
21
- Rake::Task.define_task('assets:clean' => 'vite:clean')
22
- end
23
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :vite do
4
- desc 'Remove the vite build output directory'
5
- task clobber: [:'vite:verify_install', :environment] do
6
- ViteRails.clobber
7
- $stdout.puts "Removed vite build output directory #{ ViteRails.config.build_output_dir }"
8
- end
9
- end
10
-
11
- skip_vite_clobber = %w[no false n f].include?(ENV['VITE_RUBY_PRECOMPILE'])
12
-
13
- unless skip_vite_clobber
14
- # Run clobber if the assets:clobber is run
15
- if Rake::Task.task_defined?('assets:clobber')
16
- Rake::Task['assets:clobber'].enhance do
17
- Rake::Task['vite:clobber'].invoke
18
- end
19
- end
20
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :vite do
4
- desc "Provide information on ViteRails's environment"
5
- task :info do
6
- Dir.chdir(Rails.root) do
7
- $stdout.puts "Ruby: #{ `ruby --version` }"
8
- $stdout.puts "Rails: #{ Rails.version }"
9
- $stdout.puts "ViteRails: #{ ViteRails::VERSION }"
10
- $stdout.puts "Node: #{ `node --version` }"
11
- $stdout.puts "Yarn: #{ `yarn --version` }"
12
-
13
- $stdout.puts "\n"
14
- $stdout.puts "vite-plugin-ruby: \n#{ `npm list vite-plugin-ruby version` }"
15
-
16
- $stdout.puts "Is bin/vite present?: #{ File.exist? 'bin/vite' }"
17
- $stdout.puts "Is bin/yarn present?: #{ File.exist? 'bin/yarn' }"
18
- end
19
- end
20
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- install_template_path = File.expand_path('../../install/template.rb', __dir__).freeze
4
- bin_path = ENV['BUNDLE_BIN'] || Rails.root.join('bin')
5
-
6
- namespace :vite do
7
- desc 'Install ViteRails in this application'
8
- task :install do |task|
9
- prefix = task.name.split(/#|vite:install/).first
10
- exec "#{ RbConfig.ruby } #{ bin_path }/rails #{ prefix }app:template LOCATION=#{ install_template_path }"
11
- end
12
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :vite do
4
- desc 'Install all JavaScript dependencies as specified via Yarn'
5
- task :install_dependencies do
6
- valid_node_envs = %w[test development production]
7
- node_env = ENV.fetch('NODE_ENV') { valid_node_envs.include?(Rails.env) ? Rails.env : 'production' }
8
- Dir.chdir(Rails.root) do
9
- v1 = `yarn --version`.start_with?('1')
10
- install_command = "yarn install #{ v1 ? '--no-progress --frozen-lockfile' : '--immutable' } --production=false"
11
- system({ 'NODE_ENV' => node_env }, install_command)
12
- end
13
- end
14
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :vite do
4
- desc 'Verifies if Vite Rails is installed'
5
- task :verify_install do
6
- unless File.exist?(Rails.root.join('bin/vite'))
7
- warn <<~WARN
8
- vite binstub not found.
9
- Have you run rails vite:install?
10
- Make sure the bin directory and bin/vite are not included in .gitignore
11
- WARN
12
- exit!
13
- end
14
- config_path = Rails.root.join(ViteRails.config.config_path)
15
- unless config_path.exist?
16
- warn <<~WARN
17
- Configuration #{ config_path } file for vite-plugin-ruby not found.
18
- Make sure vite:install has run successfully before running dependent tasks.
19
- WARN
20
- exit!
21
- end
22
- end
23
- end
@@ -1,111 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
- require 'digest/sha1'
5
-
6
- # Public: Keeps track of watched files and triggers builds as needed.
7
- class ViteRails::Builder
8
- def initialize(vite_rails)
9
- @vite_rails = vite_rails
10
- end
11
-
12
- # Public: Checks if the watched files have changed since the last compilation,
13
- # and triggers a Vite build if any files have changed.
14
- def build
15
- if stale?
16
- build_with_vite.tap { record_files_digest }
17
- else
18
- logger.debug 'Skipping build. Vite assets are already up-to-date ⚡️'
19
- true
20
- end
21
- end
22
-
23
- # Public: Returns true if all the assets built by Vite are up to date.
24
- def fresh?
25
- previous_files_digest&.== watched_files_digest
26
- end
27
-
28
- # Public: Returns true if any of the assets built by Vite is out of date.
29
- def stale?
30
- !fresh?
31
- end
32
-
33
- private
34
-
35
- delegate :config, :logger, to: :@vite_rails
36
-
37
- # Internal: Writes a digest of the watched files to disk for future checks.
38
- def record_files_digest
39
- config.build_cache_dir.mkpath
40
- files_digest_path.write(watched_files_digest)
41
- end
42
-
43
- # Internal: The path of where a digest of the watched files is stored.
44
- def files_digest_path
45
- config.build_cache_dir.join("last-compilation-digest-#{ config.mode }")
46
- end
47
-
48
- # Internal: Reads a digest of watched files from disk.
49
- def previous_files_digest
50
- files_digest_path.read if files_digest_path.exist? && config.manifest_path.exist?
51
- rescue Errno::ENOENT, Errno::ENOTDIR
52
- end
53
-
54
- # Internal: Returns a digest of all the watched files, allowing to detect
55
- # changes, and skip Vite builds if no files have changed.
56
- def watched_files_digest
57
- Dir.chdir File.expand_path(config.root) do
58
- files = Dir[*watched_paths].reject { |f| File.directory?(f) }
59
- file_ids = files.sort.map { |f| "#{ File.basename(f) }/#{ Digest::SHA1.file(f).hexdigest }" }
60
- Digest::SHA1.hexdigest(file_ids.join('/'))
61
- end
62
- end
63
-
64
- # Public: Initiates a Vite build command to generate assets.
65
- #
66
- # Returns true if the build is successful, or false if it failed.
67
- def build_with_vite
68
- logger.info 'Building with Vite ⚡️'
69
-
70
- command = "#{ which_ruby } ./bin/vite build --mode #{ config.mode }"
71
- stdout, stderr, status = Open3.capture3(ViteRails.config.to_env, command, chdir: File.expand_path(config.root))
72
-
73
- log_build_result(stdout, stderr, status)
74
-
75
- status.success?
76
- end
77
-
78
- # Internal: Outputs the build results.
79
- #
80
- # NOTE: By default it also outputs the manifest entries.
81
- def log_build_result(stdout, stderr, status)
82
- if status.success?
83
- logger.info "Build with Vite complete: #{ config.build_output_dir }"
84
- logger.error(stderr.to_s) unless stderr.empty?
85
- logger.info(stdout) unless config.hide_build_console_output
86
- else
87
- non_empty_streams = [stdout, stderr].delete_if(&:empty?)
88
- logger.error "Build with Vite failed:\n#{ non_empty_streams.join("\n\n") }"
89
- end
90
- end
91
-
92
- # Internal: Used to prefix the bin/vite executable file.
93
- def which_ruby
94
- bin_vite_path = config.root.join('bin/vite')
95
- first_line = File.readlines(bin_vite_path).first.chomp
96
- /ruby/.match?(first_line) ? RbConfig.ruby : ''
97
- end
98
-
99
- # Internal: Files and directories that should be watched for changes.
100
- #
101
- # NOTE: You can specify additional ones in vite.json using "watchAdditionalPaths": [...]
102
- def watched_paths
103
- [
104
- *config.watch_additional_paths,
105
- "#{ config.source_code_dir }/**/*",
106
- 'yarn.lock',
107
- 'package.json',
108
- config.config_path,
109
- ].freeze
110
- end
111
- end
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Public: Encapsulates common tasks, available both programatically and in Rake.
4
- class ViteRails::Commands
5
- def initialize(vite_rails)
6
- @vite_rails = vite_rails
7
- end
8
-
9
- # Public: Loads the manifest with all the entries compiled by Vite.
10
- def bootstrap
11
- manifest.refresh
12
- end
13
-
14
- # Public: Defaults to production, and exits if the build fails.
15
- def build_from_rake
16
- with_node_env(ENV.fetch('NODE_ENV', 'production')) {
17
- ensure_log_goes_to_stdout {
18
- build || exit!
19
- }
20
- }
21
- end
22
-
23
- # Public: Builds all assets that are managed by Vite, from the entrypoints.
24
- def build
25
- builder.build.tap { manifest.refresh }
26
- end
27
-
28
- # Public: Removes all build cache and previously compiled assets.
29
- def clobber
30
- config.build_output_dir.rmtree if config.build_output_dir.exist?
31
- config.build_cache_dir.rmtree if config.build_cache_dir.exist?
32
- config.vite_cache_dir.rmtree if config.vite_cache_dir.exist?
33
- end
34
-
35
- # Public: Receives arguments from a rake task.
36
- def clean_from_rake(args)
37
- ensure_log_goes_to_stdout {
38
- clean(keep_up_to: Integer(args.keep || 2), age_in_seconds: Integer(args.age || 3600))
39
- }
40
- end
41
-
42
- # Public: Cleanup old assets in the output directory.
43
- #
44
- # keep_up_to - Max amount of backups to preserve.
45
- # age_in_seconds - Amount of time to look back in order to preserve them.
46
- #
47
- # NOTE: By default keeps the last version, or 2 if created in the past hour.
48
- #
49
- # Examples:
50
- # To force only 1 backup to be kept: clean(1, 0)
51
- # To only keep files created within the last 10 minutes: clean(0, 600)
52
- def clean(keep_up_to: 2, age_in_seconds: 3600)
53
- return false unless may_clean?
54
-
55
- versions
56
- .each_with_index
57
- .drop_while { |(mtime, _), index|
58
- max_age = [0, Time.now - Time.at(mtime)].max
59
- max_age < age_in_seconds || index < keep_up_to
60
- }
61
- .each do |(_, files), _|
62
- clean_files(files)
63
- end
64
- true
65
- end
66
-
67
- private
68
-
69
- delegate :config, :builder, :manifest, :logger, to: :@vite_rails
70
-
71
- def may_clean?
72
- config.build_output_dir.exist? && config.manifest_path.exist?
73
- end
74
-
75
- def clean_files(files)
76
- files.select { |file| File.file?(file) }.each do |file|
77
- File.delete(file)
78
- logger.info("Removed #{ file }")
79
- end
80
- end
81
-
82
- def versions
83
- all_files = Dir.glob("#{ config.build_output_dir }/**/*")
84
- entries = all_files - [config.manifest_path] - current_version_files
85
- entries.reject { |file| File.directory?(file) }
86
- .group_by { |file| File.mtime(file).utc.to_i }
87
- .sort.reverse
88
- end
89
-
90
- def current_version_files
91
- Dir.glob(manifest.refresh.values.map { |value| config.build_output_dir.join("#{ value['file'] }*") })
92
- end
93
-
94
- def with_node_env(env)
95
- original = ENV['NODE_ENV']
96
- ENV['NODE_ENV'] = env
97
- yield
98
- ensure
99
- ENV['NODE_ENV'] = original
100
- end
101
-
102
- def ensure_log_goes_to_stdout
103
- old_logger = ViteRails.logger
104
- ViteRails.logger = ActiveSupport::Logger.new(STDOUT)
105
- yield
106
- ensure
107
- ViteRails.logger = old_logger
108
- end
109
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Public: Allows to verify if a Vite development server is already running.
4
- class ViteRails::DevServer
5
- # Public: Configure dev server connection timeout (in seconds).
6
- # Example:
7
- # ViteRails.dev_server.connect_timeout = 1
8
- cattr_accessor(:connect_timeout) { 0.01 }
9
-
10
- def initialize(config)
11
- @config = config
12
- end
13
-
14
- # Public: Returns true if the Vite development server is reachable.
15
- def running?
16
- Socket.tcp(host, port, connect_timeout: connect_timeout).close
17
- true
18
- rescue StandardError
19
- false
20
- end
21
-
22
- delegate :host, :port, to: :@config
23
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack/proxy'
4
-
5
- # Public: Allows to relay asset requests to the Vite development server.
6
- class ViteRails::DevServerProxy < Rack::Proxy
7
- VITE_DEPENDENCY_PREFIX = '/@'
8
-
9
- def initialize(app = nil, options = {})
10
- @vite_rails = options.delete(:vite_rails) || ViteRails.instance
11
- options[:streaming] = false if Rails.env.test? && !options.key?(:streaming)
12
- super
13
- end
14
-
15
- # Rack: Intercept asset requests and send them to the Vite server.
16
- def perform_request(env)
17
- if vite_should_handle?(env) && dev_server_running?
18
- forward_to_vite_dev_server(env)
19
- super(env)
20
- else
21
- @app.call(env)
22
- end
23
- end
24
-
25
- private
26
-
27
- delegate :config, :dev_server_running?, to: :@vite_rails
28
-
29
- def rewrite_uri_for_vite(env)
30
- uri = env.fetch('REQUEST_URI') { [env['PATH_INFO'], env['QUERY_STRING']].reject(&:blank?).join('?') }
31
- .sub(vite_asset_url_prefix, '/')
32
- env['PATH_INFO'], env['QUERY_STRING'] = (env['REQUEST_URI'] = uri).split('?')
33
- end
34
-
35
- def forward_to_vite_dev_server(env)
36
- rewrite_uri_for_vite(env)
37
- env['HTTP_HOST'] = env['HTTP_X_FORWARDED_HOST'] = config.host
38
- env['HTTP_X_FORWARDED_SERVER'] = config.host_with_port
39
- env['HTTP_PORT'] = env['HTTP_X_FORWARDED_PORT'] = config.port.to_s
40
- env['HTTP_X_FORWARDED_PROTO'] = env['HTTP_X_FORWARDED_SCHEME'] = config.protocol
41
- env['HTTPS'] = env['HTTP_X_FORWARDED_SSL'] = 'off' unless config.https
42
- env['SCRIPT_NAME'] = ''
43
- end
44
-
45
- def vite_should_handle?(env)
46
- path, query, referer = env['PATH_INFO'], env['QUERY_STRING'], env['HTTP_REFERER']
47
- return true if path.start_with?(vite_asset_url_prefix) # Vite asset
48
- return true if path.start_with?(VITE_DEPENDENCY_PREFIX) # Packages and imports
49
- return true if query&.start_with?('t=') # Hot Reload for a stylesheet
50
- return true if query&.start_with?('import&') # Hot Reload for an imported entrypoint
51
- return true if referer && URI.parse(referer).path.start_with?(vite_asset_url_prefix) # Entry imported from another entry.
52
- end
53
-
54
- def vite_asset_url_prefix
55
- @vite_asset_url_prefix ||= "/#{ config.public_output_dir }/"
56
- end
57
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Public: Allows to render HTML tags for scripts and styles processed by Vite.
4
- module ViteRails::Helper
5
- DEFAULT_VITE_SKIP_PRELOAD_TAGS = Rails.gem_version < Gem::Version.new('5.2.0')
6
-
7
- # Public: Returns the current Vite Rails instance.
8
- def current_vite_instance
9
- ViteRails.instance
10
- end
11
-
12
- # Public: Renders a script tag for vite/client to enable HMR in development.
13
- def vite_client_tag
14
- return unless current_vite_instance.dev_server_running?
15
-
16
- content_tag('script', '', src: current_vite_instance.manifest.prefix_vite_asset('@vite/client'), type: 'module')
17
- end
18
-
19
- # Public: Resolves the path for the specified Vite asset.
20
- #
21
- # Example:
22
- # <%= vite_asset_path 'calendar.css' %> # => "/vite/assets/calendar-1016838bab065ae1e122.css"
23
- def vite_asset_path(name, **options)
24
- path_to_asset current_vite_instance.manifest.lookup!(name, **options).fetch('file')
25
- end
26
-
27
- # Public: Renders a <script> tag for the specified Vite entrypoints.
28
- def vite_javascript_tag(*names,
29
- type: 'module',
30
- asset_type: :javascript,
31
- skip_preload_tags: DEFAULT_VITE_SKIP_PRELOAD_TAGS,
32
- skip_style_tags: false,
33
- crossorigin: 'anonymous',
34
- **options)
35
- js_entries = names.map { |name| current_vite_instance.manifest.lookup!(name, type: asset_type) }
36
- js_tags = javascript_include_tag(*js_entries.map { |entry| entry['file'] }, crossorigin: crossorigin, type: type, **options)
37
-
38
- preload_entries = js_entries.flat_map { |entry| entry['imports'] }.compact.uniq
39
-
40
- unless skip_preload_tags || current_vite_instance.dev_server_running?
41
- preload_paths = preload_entries.map { |entry| entry['file'] }.compact.uniq
42
- preload_tags = preload_paths.map { |path| preload_link_tag(path, crossorigin: crossorigin) }
43
- end
44
-
45
- unless skip_style_tags || current_vite_instance.dev_server_running?
46
- style_paths = (js_entries + preload_entries).flat_map { |entry| entry['css'] }.compact.uniq
47
- style_tags = stylesheet_link_tag(*style_paths)
48
- end
49
-
50
- safe_join [js_tags, preload_tags, style_tags]
51
- end
52
-
53
- # Public: Renders a <script> tag for the specified Vite entrypoints.
54
- #
55
- # NOTE: Because TypeScript is not a valid target in browsers, we only specify
56
- # the ts file when running the Vite development server.
57
- def vite_typescript_tag(*names, **options)
58
- vite_javascript_tag(*names, asset_type: :typescript, extname: false, **options)
59
- end
60
-
61
- # Public: Renders a <link> tag for the specified Vite entrypoints.
62
- def vite_stylesheet_tag(*names, **options)
63
- stylesheet_link_tag(*sources_from_vite_manifest(names, type: :stylesheet), **options)
64
- end
65
-
66
- private
67
-
68
- def sources_from_vite_manifest(names, type:)
69
- names.map { |name| vite_asset_path(name, type: type) }
70
- end
71
- end