minivite_rails 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 346d0e1dda504de6fe45499ad41cbc3d1561aea6be67b7703c4a4beb67df27c5
4
+ data.tar.gz: f9f7fa0218f21622446b89a095d6d5ec4631ff4df8cd216172673c209d83c92c
5
+ SHA512:
6
+ metadata.gz: 33b98d69cca67c961f6af9efadd61499b71f2e97b0585275fa6c1d2d67092bd1c7ae592f4ee356fead6d9acb5a518c94812ef115a2441b2dffd7e2a72e79adca
7
+ data.tar.gz: 7d7b59b04ed4f778b9fa079eac169af790802d40c89f01d273f5eb85bc1faa0a6b76396da051eb1ac10024fbfe3553b424ea7d07aefe4fe71c257b8d389dce4d
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,7 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.7
5
+
6
+ Layout/LineLength:
7
+ Max: 120
@@ -0,0 +1,6 @@
1
+ {
2
+ "cSpell.words": [
3
+ "Minivite",
4
+ "vite"
5
+ ]
6
+ }
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in minivite_rails.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,126 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ minivite_rails (0.1.0)
5
+ actionview (>= 6.0.0)
6
+ railties (>= 6.0.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actionpack (7.0.4.2)
12
+ actionview (= 7.0.4.2)
13
+ activesupport (= 7.0.4.2)
14
+ rack (~> 2.0, >= 2.2.0)
15
+ rack-test (>= 0.6.3)
16
+ rails-dom-testing (~> 2.0)
17
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
18
+ actionview (7.0.4.2)
19
+ activesupport (= 7.0.4.2)
20
+ builder (~> 3.1)
21
+ erubi (~> 1.4)
22
+ rails-dom-testing (~> 2.0)
23
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
24
+ activesupport (7.0.4.2)
25
+ concurrent-ruby (~> 1.0, >= 1.0.2)
26
+ i18n (>= 1.6, < 2)
27
+ minitest (>= 5.1)
28
+ tzinfo (~> 2.0)
29
+ ast (2.4.2)
30
+ builder (3.2.4)
31
+ concurrent-ruby (1.2.0)
32
+ crass (1.0.6)
33
+ debug (1.7.1)
34
+ irb (>= 1.5.0)
35
+ reline (>= 0.3.1)
36
+ diff-lcs (1.5.0)
37
+ erubi (1.12.0)
38
+ i18n (1.12.0)
39
+ concurrent-ruby (~> 1.0)
40
+ io-console (0.6.0)
41
+ irb (1.6.2)
42
+ reline (>= 0.3.0)
43
+ json (2.6.3)
44
+ loofah (2.19.1)
45
+ crass (~> 1.0.2)
46
+ nokogiri (>= 1.5.9)
47
+ method_source (1.0.0)
48
+ minitest (5.17.0)
49
+ nokogiri (1.14.1-x86_64-darwin)
50
+ racc (~> 1.4)
51
+ parallel (1.22.1)
52
+ parser (3.2.0.0)
53
+ ast (~> 2.4.1)
54
+ racc (1.6.2)
55
+ rack (2.2.6.2)
56
+ rack-test (2.0.2)
57
+ rack (>= 1.3)
58
+ rails-dom-testing (2.0.3)
59
+ activesupport (>= 4.2.0)
60
+ nokogiri (>= 1.6)
61
+ rails-html-sanitizer (1.5.0)
62
+ loofah (~> 2.19, >= 2.19.1)
63
+ railties (7.0.4.2)
64
+ actionpack (= 7.0.4.2)
65
+ activesupport (= 7.0.4.2)
66
+ method_source
67
+ rake (>= 12.2)
68
+ thor (~> 1.0)
69
+ zeitwerk (~> 2.5)
70
+ rainbow (3.1.1)
71
+ rake (13.0.6)
72
+ regexp_parser (2.6.1)
73
+ reline (0.3.2)
74
+ io-console (~> 0.5)
75
+ rexml (3.2.5)
76
+ rspec (3.12.0)
77
+ rspec-core (~> 3.12.0)
78
+ rspec-expectations (~> 3.12.0)
79
+ rspec-mocks (~> 3.12.0)
80
+ rspec-core (3.12.0)
81
+ rspec-support (~> 3.12.0)
82
+ rspec-expectations (3.12.1)
83
+ diff-lcs (>= 1.2.0, < 2.0)
84
+ rspec-support (~> 3.12.0)
85
+ rspec-mocks (3.12.1)
86
+ diff-lcs (>= 1.2.0, < 2.0)
87
+ rspec-support (~> 3.12.0)
88
+ rspec-support (3.12.0)
89
+ rubocop (1.42.0)
90
+ json (~> 2.3)
91
+ parallel (~> 1.10)
92
+ parser (>= 3.1.2.1)
93
+ rainbow (>= 2.2.2, < 4.0)
94
+ regexp_parser (>= 1.8, < 3.0)
95
+ rexml (>= 3.2.5, < 4.0)
96
+ rubocop-ast (>= 1.24.1, < 2.0)
97
+ ruby-progressbar (~> 1.7)
98
+ unicode-display_width (>= 1.4.0, < 3.0)
99
+ rubocop-ast (1.24.1)
100
+ parser (>= 3.1.1.0)
101
+ rubocop-capybara (2.17.1)
102
+ rubocop (~> 1.41)
103
+ rubocop-rspec (2.18.1)
104
+ rubocop (~> 1.33)
105
+ rubocop-capybara (~> 2.17)
106
+ ruby-progressbar (1.11.0)
107
+ thor (1.2.1)
108
+ tzinfo (2.0.6)
109
+ concurrent-ruby (~> 1.0)
110
+ unicode-display_width (2.4.2)
111
+ zeitwerk (2.6.6)
112
+
113
+ PLATFORMS
114
+ x86_64-darwin-21
115
+ x86_64-darwin-22
116
+
117
+ DEPENDENCIES
118
+ debug (~> 1.0)
119
+ minivite_rails!
120
+ rake (~> 13.0)
121
+ rspec (~> 3.0)
122
+ rubocop (~> 1.21)
123
+ rubocop-rspec (~> 2.18)
124
+
125
+ BUNDLED WITH
126
+ 2.4.6
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Linh Tran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # MiniviteRails
2
+
3
+ MiniviteRails provides minimal integration with [Vite](https://vitejs.dev/) for Rails projects.
4
+
5
+ __DISCLAIMER:__ This gem is not thoroughly tested and is not yet ready for production use. For battle-tested and full featured integration with Vite for not only Rails but Ruby projects, please check out [vite_ruby](https://vite-ruby.netlify.app/). In fact, this gem reuses a lot of code from [vite_ruby](https://vite-ruby.netlify.app/) and is intended just for my projects' specific use cases that requires full manual control over Vite's configuration and build process.
6
+
7
+ ## Features
8
+
9
+ * Rails view helpers to resolve paths to assets which are built by Vite (both by Vite's dev server and by Vite's build process).
10
+ * Support multiple configurations for Vite
11
+
12
+ ### Notes
13
+
14
+ * MiniviteRails does not automatically install Vite and manage Vite's configuration. You must install Vite, its dependencies and manage Vite's configuration manually.
15
+ * It also does not provide any integration with Rails' asset pipeline. So you must use Vite to build all your assets.
16
+
17
+ If you need above features, please use [vite_ruby](https://vite-ruby.netlify.app/)
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'minivite_rails'
25
+ ```
26
+
27
+ And then run:
28
+
29
+ ```sh
30
+ $ bundle install
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ After installed, configure your Rails app below as a new file `config/initializers/minivite_rails.rb`.
36
+
37
+ ```rb
38
+ MiniviteRails.configuration do |c|
39
+ # By default c.cache is set to `false`, which means an application always parses a
40
+ # manifest.json. In development, you should set cache false usually.
41
+ # Instead, setting it `true` which caches the manifest in memory is recommended basically.
42
+ c.cache = Rails.env.production?
43
+
44
+ # Register vite dev server address here, if you are using vite dev server.
45
+ # It will only be used in development mode.
46
+ c.vite_dev_server = 'http://127.0.0.1:5173'
47
+
48
+ # Vite base path, default will be `/vite`
49
+ # c.public_base_path = '/vite'
50
+
51
+ # Vite public directory, default will be `public`
52
+ # c.public_dir, default: 'public'
53
+
54
+ # Vite manifest file path, default will be `#{c.public_dir}#{c.public_base_path}/manifest.json`
55
+ # c.manifest_path = "#{c.public_dir}#{c.public_base_path}/manifest.json"
56
+ end
57
+ ```
58
+
59
+ With the above default configuration, you are expected to have a Vite's configuration like below:
60
+
61
+ ```js
62
+ import { fileURLToPath, URL } from 'node:url'
63
+
64
+ import { defineConfig } from 'vite'
65
+ import vue from '@vitejs/plugin-vue'
66
+
67
+ export default defineConfig({
68
+ base: '/vite',
69
+ plugins: [vue()],
70
+ build: {
71
+ manifest: true, // This is required
72
+ rollupOptions: {
73
+ input: fileURLToPath(new URL('./src/main.ts', import.meta.url)) // The entry file of your application, it will depends on your project structure
74
+ },
75
+ // For this example, the frontend code and vite configuration file are directly in a child folder from the root of the Rails project.
76
+ outDir: fileURLToPath(new URL('../public/vite', import.meta.url)),
77
+ emptyOutDir: true,
78
+ },
79
+ server: {
80
+ origin: 'http://127.0.0.1:5173' // For referencing assets from vite dev server
81
+ }
82
+ })
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ### Rails view helpers
88
+
89
+ Use the following helpers in your Rails views to resolve paths to assets which are built by Vite.
90
+
91
+ ### `vite_javascript_tag`
92
+
93
+ Renders a `<script>` tag for the specified Vite js entrypoints. This is the most commonly used helper and will also load needed css entrypoints.
94
+
95
+ ```erb
96
+ <%= vite_javascript_tag 'src/main.js' %>
97
+ ```
98
+
99
+ ### `vite_typescript_tag`
100
+
101
+ Same as `vite_javascript_tag` but for typescript entrypoints
102
+
103
+ ### `vite_stylesheet_tag`
104
+
105
+ Renders a `<link>` tag for the specified Vite css entrypoints
106
+
107
+ ```erb
108
+ <%= vite_stylesheet_tag 'app.css' %>
109
+ ```
110
+
111
+ ### `vite_client_tag`
112
+
113
+ Render a script tag to load vite/client to enable HMR
114
+
115
+ ```erb
116
+ <%= vite_client_tag %>
117
+ ```
118
+
119
+ ### `vite_react_refesth_tag`
120
+
121
+ Render a script tag to enable HMR with React Refresh
122
+
123
+ ```erb
124
+ <%= vite_react_refesh_tag %>
125
+ ```
126
+
127
+ ### `vite_asset_path`
128
+
129
+ Render a path to a specified asset built by Vite
130
+
131
+ ```erb
132
+ <%= vite_asset_path 'calender.js' %>
133
+ ```
134
+
135
+ ### `vite_asset_url`
136
+
137
+ Same as `vite_asset_path` but returns a full URL
138
+
139
+ ### `vite_image_tag`
140
+
141
+ Renders an `<img>` tag for the specified Vite image asset
142
+
143
+ ```erb
144
+ <%= vite_image_tag 'logo.png' %>
145
+ ```
146
+
147
+ ### Multiple configurations
148
+
149
+ Besides the main configuration, you can also register multiple configurations for Vite. For example, you may want to have a separate configuration for your admin panel.
150
+
151
+ ```rb
152
+ MiniviteRails.configuration do |c|
153
+ # Main configuration
154
+ c.cache = Rails.env.production?
155
+ c.vite_dev_server = 'http://127.0.0.1:5173'
156
+
157
+ # Sub configuration for admin panel
158
+ c.add :admin do |sc|
159
+ sc.public_base_path = '/vite_admin'
160
+ sc.vite_dev_server = 'http://127.0.0.1:5174'
161
+ end
162
+ end
163
+ ```
164
+
165
+ And the corresponding Vite configuration for admin panel:
166
+
167
+ ```js
168
+ import { fileURLToPath, URL } from 'node:url'
169
+ import { defineConfig } from 'vite'
170
+ import react from '@vitejs/plugin-react'
171
+
172
+ // https://vitejs.dev/config/
173
+ export default defineConfig({
174
+ base: '/vite_ops/',
175
+ plugins: [react()],
176
+ build: {
177
+ manifest: true, // This is required
178
+ rollupOptions: {
179
+ input: fileURLToPath(new URL('./src/main.jsx', import.meta.url))
180
+ },
181
+ outDir: fileURLToPath(new URL('../public/vite_admin', import.meta.url)),
182
+ emptyOutDir: true,
183
+ },
184
+ server: {
185
+ port: 5174,
186
+ origin: 'http://127.0.0.1:5174'
187
+ }
188
+ })
189
+
190
+ ```
191
+
192
+ ## Special Thanks
193
+
194
+ This project uses ideas and codes from the following projects:
195
+
196
+ * [vite_ruby](https://github.com/ElMassimo/vite_ruby)
197
+ * [minipack](https://github.com/nikushi/minipack)
198
+
199
+ ## License
200
+
201
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniviteRails
4
+ class Configuration
5
+ class Error < StandardError; end
6
+
7
+ ROOT_DEFAULT_ID = :''
8
+
9
+ class << self
10
+ def config_attr(prop, default: nil)
11
+ define_method(prop) do
12
+ # If not overridden, children will use parent's setting
13
+ @config.fetch(prop) do
14
+ @parent ? @parent.public_send(prop) : default
15
+ end
16
+ end
17
+
18
+ define_method("#{prop}=".to_sym) do |v|
19
+ @config[prop] = v
20
+ end
21
+ end
22
+ end
23
+
24
+ # Private
25
+ config_attr :id, default: ROOT_DEFAULT_ID
26
+ config_attr :cache, default: false
27
+ # The base directory of the frontend.
28
+ config_attr :vite_dev_server
29
+ config_attr :manifest_path
30
+ config_attr :public_base_path, default: '/vite'
31
+ config_attr :public_dir, default: 'public'
32
+
33
+ # Initializes a new instance of Configuration class.
34
+ def initialize
35
+ @parent = nil
36
+ @children = {}
37
+ @config = {}
38
+ end
39
+
40
+ def public_asset_dir
41
+ File.expand_path('.', File.join(public_dir, public_base_path))
42
+ end
43
+
44
+ def manifest
45
+ @manifest ||= Manifest.new(self)
46
+ end
47
+
48
+ def reload_manifest
49
+ @manifest&.update_config(self)
50
+ @children.each_value(&:reload_manifest)
51
+ end
52
+
53
+ def add(id)
54
+ raise Error, 'Can only define sub configuration from root config' unless root?
55
+ raise Error, 'Id already used by root configuration' if id == @config[:id]
56
+
57
+ @children[id] ||= self.class.new.tap do |c|
58
+ c.instance_variable_set(:@parent, self)
59
+ c.instance_variable_set(:@children, nil)
60
+ c.id = id
61
+ yield c if block_given?
62
+ end
63
+ end
64
+
65
+ def child_by_id(id)
66
+ raise Error, 'Can only get sub configuration from root config' unless root?
67
+
68
+ return self if id == @config[:id] # return itself if id is root id
69
+
70
+ @children.fetch(id)
71
+ rescue KeyError
72
+ raise Error, "No sub configuration with id #{id}"
73
+ end
74
+
75
+ def root?
76
+ @parent.nil?
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Modified from vite_ruby's source code
4
+ # https://github.com/ElMassimo/vite_ruby/blob/main/vite_ruby/lib/vite_ruby/manifest.rb
5
+
6
+ require 'json'
7
+ require 'active_support/core_ext/object/blank'
8
+ require 'rails'
9
+
10
+ module MiniviteRails
11
+ class Manifest
12
+ class FileNotFoundError < StandardError; end
13
+ class MissingEntryError < StandardError; end
14
+
15
+ attr_reader :config, :manifest_path
16
+
17
+ def initialize(config)
18
+ update_config(config)
19
+ end
20
+
21
+ def update_config(config)
22
+ @config = config
23
+ @manifest_path = config.manifest_path || File.join(config.public_asset_dir, 'manifest.json')
24
+ @data = nil
25
+ end
26
+
27
+ def data
28
+ return load_manifest unless config.cache
29
+
30
+ @data ||= load_manifest
31
+ end
32
+
33
+ def path_for(name, **options)
34
+ lookup!(name, **options).fetch('file')
35
+ end
36
+
37
+ def vite_client_src
38
+ prefix_vite_asset('@vite/client') if dev_server_available?
39
+ end
40
+
41
+ def resolve_entries(*names, **options)
42
+ entries = names.map { |name| lookup!(name, **options) }
43
+ script_paths = entries.map { |entry| entry.fetch('file') }
44
+
45
+ imports = dev_server_available? ? [] : entries.flat_map { |entry| entry['imports'] }.compact.uniq
46
+ {
47
+ scripts: script_paths,
48
+ imports: imports.map { |entry| entry.fetch('file') }.uniq,
49
+ stylesheets: dev_server_available? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq
50
+ }
51
+ end
52
+
53
+ def react_refresh_preamble
54
+ return unless dev_server_available?
55
+
56
+ <<~REACT_REFRESH
57
+ <script type="module">
58
+ import RefreshRuntime from '#{prefix_vite_asset('@react-refresh')}'
59
+ RefreshRuntime.injectIntoGlobalHook(window)
60
+ window.$RefreshReg$ = () => {}
61
+ window.$RefreshSig$ = () => (type) => type
62
+ window.__vite_plugin_react_preamble_installed__ = true
63
+ </script>
64
+ REACT_REFRESH
65
+ end
66
+
67
+ protected
68
+
69
+ def dev_server_available?
70
+ !Rails.env.production? && config.vite_dev_server.present?
71
+ end
72
+
73
+ def load_manifest
74
+ u = URI.parse(manifest_path)
75
+ data = nil
76
+ if u.scheme == 'file' || u.path == manifest_path # file path
77
+ raise(FileNotFoundError, "#{manifest_path}: no such manifest found") unless File.exist?(manifest_path)
78
+
79
+ data = File.read(manifest_path)
80
+ else
81
+ # http url
82
+ data = u.read
83
+ end
84
+ JSON.parse(data).tap(&method(:resolve_references))
85
+ end
86
+
87
+ def prefix_vite_asset(path)
88
+ root_path = dev_server_available? ? config.vite_dev_server : '/'
89
+ File.join(root_path, config.public_base_path, path)
90
+ end
91
+
92
+ # Internal: Resolves the paths that reference a manifest entry.
93
+ def resolve_references(manifest)
94
+ manifest.each_value do |entry|
95
+ entry['file'] = prefix_vite_asset(entry['file'])
96
+ %w[css assets].each do |key|
97
+ entry[key] = entry[key].map { |path| prefix_vite_asset(path) } if entry[key]
98
+ end
99
+ entry['imports']&.map! { |name| manifest.fetch(name) }
100
+ end
101
+ end
102
+
103
+ def lookup!(name, **options)
104
+ lookup(name, **options) || missing_entry_error(name, **options)
105
+ end
106
+
107
+ # Internal: Computes the path for a given Vite asset using manifest.json.
108
+ #
109
+ # Returns a relative path, or nil if the asset is not found.
110
+ #
111
+ # Example:
112
+ # manifest.lookup('calendar.js')
113
+ # => { "file" => "/vite/assets/calendar-1016838bab065ae1e122.js", "imports" => [] }
114
+ def lookup(name, **options)
115
+ find_manifest_entry resolve_entry_name(name, **options)
116
+ end
117
+
118
+ def find_manifest_entry(name)
119
+ if dev_server_available?
120
+ { 'file' => prefix_vite_asset(name) }
121
+ else
122
+ data[name]
123
+ end
124
+ end
125
+
126
+ def resolve_entry_name(name, type: nil)
127
+ return resolve_virtual_entry(name) if type == :virtual
128
+
129
+ name = with_file_extension(name.to_s, type)
130
+ raise ArgumentError, "Asset names can not be relative. Found: #{name}" if name.start_with?('.')
131
+
132
+ # Explicit path, relative to the source_code_dir.
133
+ name.sub(%r{^~/(.+)$}) { return Regexp.last_match(1) }
134
+ name
135
+ end
136
+
137
+ # Internal: Resolves a virtual entry by walking all the manifest keys.
138
+ def resolve_virtual_entry(name)
139
+ data.keys.find { |file| file.include?(name) } || name
140
+ end
141
+
142
+ # Internal: Adds a file extension to the file name, unless it already has one.
143
+ def with_file_extension(name, entry_type)
144
+ if File.extname(name).empty? && (ext = extension_for_type(entry_type))
145
+ "#{name}.#{ext}"
146
+ else
147
+ name
148
+ end
149
+ end
150
+
151
+ # Internal: Allows to receive :javascript and :stylesheet as :type in helpers.
152
+ def extension_for_type(entry_type)
153
+ case entry_type
154
+ when :javascript then 'js'
155
+ when :stylesheet then 'css'
156
+ when :typescript then 'ts'
157
+ else entry_type
158
+ end
159
+ end
160
+
161
+ # Internal: Raises a detailed message when an entry is missing in the manifest.
162
+ def missing_entry_error(name, **_options)
163
+ raise MissingEntryError, <<~MSG
164
+ Can not find #{name} in #{manifest_path}.
165
+ Your manifest contains:
166
+ #{JSON.pretty_generate(data)}
167
+ MSG
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_view'
4
+
5
+ # Use tag helpers from Vite Ruby
6
+ # https://github.com/ElMassimo/vite_ruby/blob/main/vite_rails/lib/vite_rails/tag_helpers.rb
7
+
8
+ # Public: Allows to render HTML tags for scripts and styles processed by Vite.
9
+ module MiniviteRails
10
+ module TagHelpers
11
+ # Public: Renders a script tag for vite/client to enable HMR in development.
12
+ def vite_client_tag(id: nil, **options)
13
+ src = vite_manifest(id: id).vite_client_src
14
+ return unless src
15
+
16
+ javascript_include_tag(src, type: 'module', extname: false, **options)
17
+ end
18
+
19
+ # Public: Renders a script tag to enable HMR with React Refresh.
20
+ def vite_react_refresh_tag(id: nil)
21
+ vite_manifest(id: id).react_refresh_preamble&.html_safe
22
+ end
23
+
24
+ # Public: Resolves the path for the specified Vite asset.
25
+ #
26
+ # Example:
27
+ # <%= vite_asset_path 'calendar.css' %> # => "/vite/assets/calendar-1016838bab065ae1e122.css"
28
+ def vite_asset_path(name, id: nil, **options)
29
+ path_to_asset vite_manifest(id: id).path_for(name, **options)
30
+ end
31
+
32
+ # Public: Resolves the url for the specified Vite asset.
33
+ #
34
+ # Example:
35
+ # <%= vite_asset_url 'calendar.css' %> # => "https://example.com/vite/assets/calendar-1016838bab065ae1e122.css"
36
+ def vite_asset_url(name, id: nil, **options)
37
+ url_to_asset vite_manifest(id: id).path_for(name, **options)
38
+ end
39
+
40
+ # Public: Renders a <script> tag for the specified Vite entrypoints.
41
+ def vite_javascript_tag(*names,
42
+ id: nil,
43
+ type: 'module',
44
+ asset_type: :javascript,
45
+ skip_preload_tags: false,
46
+ skip_style_tags: false,
47
+ crossorigin: 'anonymous',
48
+ media: 'screen',
49
+ **options)
50
+ entries = vite_manifest(id: id).resolve_entries(*names, type: asset_type)
51
+ tags = javascript_include_tag(*entries.fetch(:scripts), crossorigin: crossorigin, type: type, extname: false,
52
+ **options)
53
+ tags << vite_preload_tag(*entries.fetch(:imports), crossorigin: crossorigin, **options) unless skip_preload_tags
54
+
55
+ options[:extname] = false if Rails::VERSION::MAJOR >= 7
56
+
57
+ tags << stylesheet_link_tag(*entries.fetch(:stylesheets), media: media, **options) unless skip_style_tags
58
+
59
+ tags
60
+ end
61
+
62
+ # Public: Renders a <script> tag for the specified Vite entrypoints.
63
+ def vite_typescript_tag(*names, id: nil, **options)
64
+ vite_javascript_tag(*names, id: id, asset_type: :typescript, **options)
65
+ end
66
+
67
+ # Public: Renders a <link> tag for the specified Vite entrypoints.
68
+ def vite_stylesheet_tag(*names, id: nil, **options)
69
+ style_paths = names.map { |name| vite_asset_path(name, id: id, type: :stylesheet) }
70
+
71
+ options[:extname] = false if Rails::VERSION::MAJOR >= 7
72
+
73
+ stylesheet_link_tag(*style_paths, **options)
74
+ end
75
+
76
+ # Public: Renders an <img> tag for the specified Vite asset.
77
+ def vite_image_tag(name, id: nil, **options)
78
+ if options[:srcset] && !options[:srcset].is_a?(String)
79
+ options[:srcset] = options[:srcset].map do |src_name, size|
80
+ "#{vite_asset_path(src_name, id: id)} #{size}"
81
+ end.join(', ')
82
+ end
83
+
84
+ image_tag(vite_asset_path(name, id: id), options)
85
+ end
86
+
87
+ private
88
+
89
+ # Internal: Returns the current manifest loaded by Vite Ruby.
90
+ def vite_manifest(id: nil)
91
+ MiniviteRails.manifest(id: id)
92
+ end
93
+
94
+ # Internal: Renders a modulepreload link tag.
95
+ def vite_preload_tag(*sources, crossorigin:, **options)
96
+ sources.map do |source|
97
+ href = path_to_asset(source)
98
+ try(:request).try(:send_early_hints,
99
+ 'Link' => %(<#{href}>; rel=modulepreload; as=script; crossorigin=#{crossorigin}))
100
+ tag.link(rel: 'modulepreload', href: href, as: 'script', crossorigin: crossorigin, **options)
101
+ end.join("\n").html_safe
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniviteRails
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniviteRails
4
+ require 'minivite_rails/configuration'
5
+ require 'minivite_rails/manifest'
6
+ require 'minivite_rails/tag_helpers'
7
+ require 'minivite_rails/version'
8
+
9
+ class << self
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ yield @configuration if block_given?
13
+ @configuration
14
+ end
15
+
16
+ def manifest(id: nil)
17
+ raise 'MiniviteRails is not configured' if @configuration.nil?
18
+
19
+ return @configuration.manifest if id.nil?
20
+
21
+ @configuration.child_by_id(id).manifest
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'active_support/lazy_load_hooks'
27
+ ActiveSupport.on_load :action_view do
28
+ ::ActionView::Base.include MiniviteRails::TagHelpers
29
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minivite_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Linh Tran
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionview
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: railties
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: debug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.21'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.21'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.18'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.18'
111
+ description: Minivite Ruby Gem
112
+ email:
113
+ - linh.mtran168@live.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".rspec"
119
+ - ".rubocop.yml"
120
+ - ".vscode/settings.json"
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - lib/minivite_rails.rb
127
+ - lib/minivite_rails/configuration.rb
128
+ - lib/minivite_rails/manifest.rb
129
+ - lib/minivite_rails/tag_helpers.rb
130
+ - lib/minivite_rails/version.rb
131
+ homepage: https://github.com/linhmtran168/minivite_rails
132
+ licenses:
133
+ - MIT
134
+ metadata:
135
+ homepage_uri: https://github.com/linhmtran168/minivite_rails
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 2.7.0
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubygems_version: 3.4.6
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: Minivite Ruby Gem
155
+ test_files: []