vite_ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []