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.
@@ -0,0 +1,62 @@
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 ViteRuby::DevServerProxy < Rack::Proxy
7
+ HOST_WITH_PORT_REGEX = %r{^(.+?)(:\d+)/}
8
+ VITE_DEPENDENCY_PREFIX = '/@'
9
+
10
+ def initialize(app = nil, options = {})
11
+ @vite_ruby = options.delete(:vite_ruby) || ViteRuby.instance
12
+ options[:streaming] = false if ViteRuby.mode == 'test' && !options.key?(:streaming)
13
+ super
14
+ end
15
+
16
+ # Rack: Intercept asset requests and send them to the Vite server.
17
+ def perform_request(env)
18
+ if vite_should_handle?(env) && dev_server_running?
19
+ forward_to_vite_dev_server(env)
20
+ super(env)
21
+ else
22
+ @app.call(env)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ extend Forwardable
29
+
30
+ def_delegators :@vite_ruby, :config, :dev_server_running?
31
+
32
+ def rewrite_uri_for_vite(env)
33
+ uri = env.fetch('REQUEST_URI') { [env['PATH_INFO'], env['QUERY_STRING']].reject { |str| str.to_s.strip.empty? }.join('?') }
34
+ .sub(vite_asset_url_prefix, '/')
35
+ .sub(HOST_WITH_PORT_REGEX, '/') # Hanami adds the host and port.
36
+ .sub('.ts.js', '.ts') # Hanami's javascript helper always adds the extension.
37
+ env['PATH_INFO'], env['QUERY_STRING'] = (env['REQUEST_URI'] = uri).split('?')
38
+ end
39
+
40
+ def forward_to_vite_dev_server(env)
41
+ rewrite_uri_for_vite(env)
42
+ env['HTTP_HOST'] = env['HTTP_X_FORWARDED_HOST'] = config.host
43
+ env['HTTP_X_FORWARDED_SERVER'] = config.host_with_port
44
+ env['HTTP_PORT'] = env['HTTP_X_FORWARDED_PORT'] = config.port.to_s
45
+ env['HTTP_X_FORWARDED_PROTO'] = env['HTTP_X_FORWARDED_SCHEME'] = config.protocol
46
+ env['HTTPS'] = env['HTTP_X_FORWARDED_SSL'] = 'off' unless config.https
47
+ env['SCRIPT_NAME'] = ''
48
+ end
49
+
50
+ def vite_should_handle?(env)
51
+ path, query, referer = env['PATH_INFO'], env['QUERY_STRING'], env['HTTP_REFERER']
52
+ return true if path.start_with?(vite_asset_url_prefix) # Vite asset
53
+ return true if path.start_with?(VITE_DEPENDENCY_PREFIX) # Packages and imports
54
+ return true if query&.start_with?('t=') # Hot Reload for a stylesheet
55
+ return true if query&.start_with?('import&') # Hot Reload for an imported entrypoint
56
+ return true if referer && URI.parse(referer).path.start_with?(vite_asset_url_prefix) # Entry imported from another entry.
57
+ end
58
+
59
+ def vite_asset_url_prefix
60
+ @vite_asset_url_prefix ||= "/#{ config.public_output_dir }/"
61
+ end
62
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Public: Registry for accessing resources managed by Vite, using a generated
4
+ # manifest file which maps entrypoint names to file paths.
5
+ #
6
+ # Example:
7
+ # lookup_entrypoint('calendar', type: :javascript)
8
+ # => { "file" => "/vite/assets/calendar-1016838bab065ae1e314.js", "imports" => [] }
9
+ #
10
+ # NOTE: Using "autoBuild": true` in `config/vite.json` file will trigger a build
11
+ # on demand as needed, before performing any lookup.
12
+ class ViteRuby::Manifest
13
+ class MissingEntryError < StandardError
14
+ end
15
+
16
+ def initialize(vite_ruby)
17
+ @vite_ruby = vite_ruby
18
+ end
19
+
20
+ # Public: Returns the path for the specified Vite entrypoint file.
21
+ #
22
+ # Raises an error if the resource could not be found in the manifest.
23
+ def path_for(name, **options)
24
+ lookup!(name, **options).fetch('file')
25
+ end
26
+
27
+ # Public: Returns scripts, imported modules, and stylesheets for the specified
28
+ # entrypoint files.
29
+ def resolve_entries(*names, **options)
30
+ entries = names.map { |name| lookup!(name, **options) }
31
+ script_paths = entries.map { |entry| entry.fetch('file') }
32
+
33
+ imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact.uniq
34
+ {
35
+ scripts: script_paths,
36
+ imports: imports.map { |entry| entry.fetch('file') }.uniq,
37
+ stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq,
38
+ }
39
+ end
40
+
41
+ # Public: Refreshes the cached mappings by reading the updated manifest files.
42
+ def refresh
43
+ @manifest = load_manifest
44
+ end
45
+
46
+ # Public: The path from where the browser can download the Vite HMR client.
47
+ def vite_client_src
48
+ prefix_vite_asset('@vite/client') if dev_server_running?
49
+ end
50
+
51
+ protected
52
+
53
+ # Internal: Strict version of lookup.
54
+ #
55
+ # Returns a relative path for the asset, or raises an error if not found.
56
+ def lookup!(*args, **options)
57
+ lookup(*args, **options) || missing_entry_error(*args, **options)
58
+ end
59
+
60
+ # Internal: Computes the path for a given Vite asset using manifest.json.
61
+ #
62
+ # Returns a relative path, or nil if the asset is not found.
63
+ #
64
+ # Example:
65
+ # manifest.lookup('calendar.js')
66
+ # => { "file" => "/vite/assets/calendar-1016838bab065ae1e122.js", "imports" => [] }
67
+ def lookup(name, type: nil)
68
+ build if should_build?
69
+
70
+ find_manifest_entry(with_file_extension(name, type))
71
+ end
72
+
73
+ private
74
+
75
+ extend Forwardable
76
+
77
+ def_delegators :@vite_ruby, :config, :build, :dev_server_running?
78
+
79
+ # NOTE: Auto compilation is convenient when running tests, when the developer
80
+ # won't focus on the frontend, or when running the Vite server is not desired.
81
+ def should_build?
82
+ config.auto_build && !dev_server_running?
83
+ end
84
+
85
+ # Internal: Finds the specified entry in the manifest.
86
+ def find_manifest_entry(name)
87
+ if dev_server_running?
88
+ { 'file' => prefix_vite_asset(name.to_s) }
89
+ else
90
+ manifest[name.to_s]
91
+ end
92
+ end
93
+
94
+ # Internal: The parsed data from manifest.json.
95
+ #
96
+ # NOTE: When using build-on-demand in development and testing, the manifest
97
+ # is reloaded automatically before each lookup, to ensure it's always fresh.
98
+ def manifest
99
+ return refresh if config.auto_build
100
+
101
+ @manifest ||= load_manifest
102
+ end
103
+
104
+ # Internal: Loads and merges the manifest files, resolving the asset paths.
105
+ def load_manifest
106
+ files = [config.manifest_path, config.assets_manifest_path].select(&:exist?)
107
+ files.map { |path| JSON.parse(path.read) }.inject({}, &:merge).tap(&method(:resolve_references))
108
+ end
109
+
110
+ # Internal: Scopes an asset to the output folder in public, as a path.
111
+ def prefix_vite_asset(path)
112
+ File.join("/#{ config.public_output_dir }", path)
113
+ end
114
+
115
+ # Internal: Resolves the paths that reference a manifest entry.
116
+ def resolve_references(manifest)
117
+ manifest.each_value do |entry|
118
+ entry['file'] = prefix_vite_asset(entry['file'])
119
+ entry['css'] = entry['css'].map { |path| prefix_vite_asset(path) } if entry['css']
120
+ entry['imports']&.map! { |name| manifest.fetch(name) }
121
+ end
122
+ end
123
+
124
+ # Internal: Adds a file extension to the file name, unless it already has one.
125
+ def with_file_extension(name, entry_type)
126
+ return name unless File.extname(name.to_s).empty?
127
+
128
+ extension = extension_for_type(entry_type)
129
+ extension ? "#{ name }.#{ extension }" : name
130
+ end
131
+
132
+ # Internal: Allows to receive :javascript and :stylesheet as :type in helpers.
133
+ def extension_for_type(entry_type)
134
+ case entry_type
135
+ when :javascript then 'js'
136
+ when :stylesheet then 'css'
137
+ when :typescript then 'ts'
138
+ else entry_type
139
+ end
140
+ end
141
+
142
+ # Internal: Raises a detailed message when an entry is missing in the manifest.
143
+ def missing_entry_error(name, type: nil, **_options)
144
+ file_name = with_file_extension(name, type)
145
+ raise ViteRuby::Manifest::MissingEntryError, <<~MSG
146
+ Vite Ruby can't find #{ file_name } in #{ config.manifest_path } or #{ config.assets_manifest_path }.
147
+
148
+ Possible causes:
149
+ #{ missing_entry_causes.map { |cause| "\t- #{ cause }" }.join("\n") }
150
+
151
+ Content in your manifests:
152
+ #{ JSON.pretty_generate(@manifest) }
153
+ MSG
154
+ end
155
+
156
+ def missing_entry_causes
157
+ local = config.auto_build
158
+ [
159
+ (dev_server_running? && 'Vite has not yet re-built your latest changes.'),
160
+ (local && !dev_server_running? && "\"autoBuild\": false in your #{ config.mode } configuration."),
161
+ (local && !dev_server_running? && 'The Vite development server has crashed or is no longer available.'),
162
+ 'You have misconfigured config/vite.json file.',
163
+ (!local && 'Assets have not been precompiled'),
164
+ ].select(&:itself)
165
+ rescue StandardError
166
+ []
167
+ end
168
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ # Public: Executes Vite commands, providing conveniences for debugging.
6
+ class ViteRuby::Runner
7
+ # Public: Executes Vite with the specified arguments.
8
+ def run(argv, **options)
9
+ $stdout.sync = true
10
+ detect_unsupported_switches!(argv)
11
+ execute_command(argv.clone, **options)
12
+ end
13
+
14
+ private
15
+
16
+ UNSUPPORTED_SWITCHES = %w[--host --port --https --root -r --config -c].freeze
17
+
18
+ # Internal: Allows to prevent configuration mistakes by ensuring the Ruby app
19
+ # and vite-plugin-ruby are using the same configuration for the dev server.
20
+ def detect_unsupported_switches!(args)
21
+ return unless (unsupported = UNSUPPORTED_SWITCHES & args).any?
22
+
23
+ $stdout.puts "Please set the following switches in your vite.json instead: #{ unsupported }."
24
+ exit!
25
+ end
26
+
27
+ # Internal: Executes the command with the specified arguments.
28
+ def execute_command(args, capture: false)
29
+ if capture
30
+ Open3.capture3(*command_for(args), chdir: ViteRuby.config.root)
31
+ else
32
+ Dir.chdir(ViteRuby.config.root) { Kernel.exec(*command_for(args)) }
33
+ end
34
+ end
35
+
36
+ # Internal: Returns an Array with the command to run.
37
+ def command_for(args)
38
+ [vite_env].tap do |cmd|
39
+ cmd.append('node', '--inspect-brk') if args.include?('--debug')
40
+ cmd.append('node', '--trace-deprecation') if args.delete('--trace-deprecation')
41
+ cmd.append(*vite_executable(cmd))
42
+ cmd.append(*args)
43
+ cmd.append('--mode', ViteRuby.mode) unless args.include?('--mode') || args.include?('-m')
44
+ end
45
+ end
46
+
47
+ # Internal: The environment variables to pass to the command.
48
+ def vite_env
49
+ ViteRuby.config.to_env
50
+ end
51
+
52
+ # Internal: Resolves to an executable for Vite.
53
+ def vite_executable(cmd)
54
+ npx = cmd.include?('node') ? `which npx`.chomp("\n") : 'npx' rescue 'npx'
55
+ [npx, 'vite']
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ViteRuby
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vite'
2
+ import RubyPlugin from 'vite-plugin-ruby'
3
+
4
+ export default defineConfig({
5
+ plugins: [
6
+ RubyPlugin(),
7
+ ],
8
+ })
@@ -0,0 +1,15 @@
1
+ {
2
+ "all": {
3
+ "sourceCodeDir": "frontend",
4
+ "watchAdditionalPaths": []
5
+ },
6
+ "development": {
7
+ "autoBuild": true,
8
+ "publicOutputDir": "vite-dev",
9
+ "port": 3036
10
+ },
11
+ "test": {
12
+ "autoBuild": true,
13
+ "publicOutputDir": "vite-test"
14
+ }
15
+ }
@@ -0,0 +1,8 @@
1
+ // To see this message, follow the instructions for your Ruby framework.
2
+ //
3
+ // When using a plain API, perhaps it's better to generate an HTML entrypoint
4
+ // and link to the scripts and stylesheets, and let Vite transform it.
5
+ console.log('Vite ⚡️ Ruby')
6
+
7
+ // Example: Import a stylesheet in <sourceCodeDir>/index.css
8
+ // import '~/index.css'
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vite_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Máximo Mussini
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-cli
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack-proxy
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: zeitwerk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.3.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - maximomussini@gmail.com
100
+ executables:
101
+ - vite
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGELOG.md
106
+ - CONTRIBUTING.md
107
+ - LICENSE.txt
108
+ - README.md
109
+ - default.vite.json
110
+ - exe/vite
111
+ - lib/tasks/vite.rake
112
+ - lib/vite_ruby.rb
113
+ - lib/vite_ruby/builder.rb
114
+ - lib/vite_ruby/cli.rb
115
+ - lib/vite_ruby/cli/build.rb
116
+ - lib/vite_ruby/cli/dev.rb
117
+ - lib/vite_ruby/cli/install.rb
118
+ - lib/vite_ruby/cli/version.rb
119
+ - lib/vite_ruby/commands.rb
120
+ - lib/vite_ruby/config.rb
121
+ - lib/vite_ruby/dev_server_proxy.rb
122
+ - lib/vite_ruby/manifest.rb
123
+ - lib/vite_ruby/runner.rb
124
+ - lib/vite_ruby/version.rb
125
+ - templates/config/vite.config.ts
126
+ - templates/config/vite.json
127
+ - templates/entrypoints/application.js
128
+ homepage: https://github.com/ElMassimo/vite_rails
129
+ licenses:
130
+ - MIT
131
+ metadata:
132
+ source_code_uri: https://github.com/ElMassimo/vite_rails/tree/vite_ruby@1.0.0/vite_ruby
133
+ changelog_uri: https://github.com/ElMassimo/vite_rails/blob/vite_ruby@1.0.0/vite_ruby/CHANGELOG.md
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '2.5'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.1.4
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Use Vite in Ruby and bring joy to your JavaScript experience
153
+ test_files: []