rails_vite 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e21a7726c621ab685293e27628370999aa8f4eac432f7bba906385f61e07e9d7
4
- data.tar.gz: 1b960c132edf8437be44cf32237c2b13e87fdf0611c009a90a3c5807e6f117d9
3
+ metadata.gz: '0857c5ff94e3b683d91dc9514d94f03669cbc470a83690ab1952f152ec339f11'
4
+ data.tar.gz: c8c67fc040f4050365e8a16feb3aa18bf5ed35bf5c75e2174a7b1235830991c1
5
5
  SHA512:
6
- metadata.gz: 4e4c557a47e858aece8cfaa5d80e1f39b1858327107ff92142b44d329a37713bbba32a84075eb821a5ef89c25150be8a374fbdec756bfef24dfbb5d88266878c
7
- data.tar.gz: 6fbadd4536700b156f151250abb377665d099ae0ace3c2b97f9e60bcc6a2df510efb6f3d7c39c53d85030e5842807d6aa9d4b21b87eb86e2716c24b324618417
6
+ metadata.gz: 3dbd780374483c13a7847076fe22cd64bbfe5852cdcbd003340ed1de8be7d883da3baef5f8ffeab4f46be18ee39f25c21a22a5b2977250b96b8078a18955635b
7
+ data.tar.gz: c5550e0498ddfbea0f37c562874123efb319a2db047164a6cf4809397ff17ebfb4a5a0628ee53457f1739fad01122048526354f37a0ae40a472467028f2aab4b
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.2] - 2026-03-08
11
+
12
+ ### Fixed
13
+
14
+ - Short entry names now resolve correctly when using `entrypoints/` directory ([@skryukov])
15
+
16
+ ## [0.1.1] - 2026-03-08
17
+
18
+ ### Added
19
+
20
+ - Support custom HTML attributes in `vite_tags` (e.g., `data-turbo-track`, `media`) ([@skryukov])
21
+ - Add `vite_javascript_tag`, `vite_stylesheet_tag`, and `vite_typescript_tag` compat helpers for easier migration from vite_rails ([@skryukov])
22
+ - Auto-discover entrypoints from `sourceDir/entrypoints/` directory ([@skryukov])
23
+ - Support extensionless entry names in `vite_tags` (e.g., `vite_tags("application")`) ([@skryukov])
24
+ - Subresource Integrity (SRI) support — automatically adds `integrity` and `crossorigin` attributes when `vite-plugin-manifest-sri` is used ([@skryukov])
25
+
26
+ ### Fixed
27
+
28
+ - `refresh: false` option now correctly disables file watching ([@skryukov])
29
+
10
30
  ## [0.1.0] - 2026-03-07
11
31
 
12
32
  ### Added
@@ -15,7 +35,9 @@ and this project adheres to [Semantic Versioning].
15
35
 
16
36
  [@skryukov]: https://github.com/skryukov
17
37
 
18
- [Unreleased]: https://github.com/skryukov/rails_vite/compare/v0.1.0...HEAD
38
+ [Unreleased]: https://github.com/skryukov/rails_vite/compare/v0.1.2...HEAD
39
+ [0.1.2]: https://github.com/skryukov/rails_vite/compare/v0.1.1...v0.1.2
40
+ [0.1.1]: https://github.com/skryukov/rails_vite/compare/v0.1.0...v0.1.1
19
41
  [0.1.0]: https://github.com/skryukov/rails_vite/commits/v0.1.0
20
42
 
21
43
  [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
data/README.md CHANGED
@@ -1,14 +1,36 @@
1
1
  # RailsVite
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/rails_vite.svg)](https://rubygems.org/gems/rails_vite)
4
+
3
5
  Vite integration for Rails, inspired by [Laravel's Vite plugin](https://laravel.com/docs/12.x/vite). No proxy, no config duplication, no magic.
4
6
 
7
+ ## Table of Contents
8
+
9
+ - [How It Works](#how-it-works)
10
+ - [Quick Start](#quick-start)
11
+ - [Usage](#usage)
12
+ - [Vite Config](#vite-config)
13
+ - [Adding Frameworks](#adding-frameworks)
14
+ - [SSR](#ssr)
15
+ - [Auto Build](#auto-build)
16
+ - [Testing the Build](#testing-the-build)
17
+ - [Custom Paths](#custom-paths)
18
+ - [Rake Tasks](#rake-tasks)
19
+ - [Migrating from vite_rails](#migrating-from-vite_rails)
20
+ - [Contributing](#contributing)
21
+ - [License](#license)
22
+
23
+ <a href="https://evilmartians.com/?utm_source=rails_vite&utm_campaign=project_page">
24
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Built by Evil Martians" width="236" height="54">
25
+ </a>
26
+
5
27
  ## How It Works
6
28
 
7
29
  **Development:** The Vite plugin writes `tmp/rails-vite.json` with the dev server URL. The Rails helper reads it and emits `<script>` tags pointing directly at Vite. The browser talks to Vite — Puma never touches your assets.
8
30
 
9
31
  **Production:** `vite build` outputs fingerprinted assets to `public/vite/` with a standard Vite manifest. The Rails helper reads the manifest and emits the correct tags.
10
32
 
11
- No Rack proxy. No `config/vite.json`. No version-locked packages.
33
+ No Rack proxy. No `config/vite.json`. No extra binstubs.
12
34
 
13
35
  ## Quick Start
14
36
 
@@ -62,10 +84,20 @@ Short names are automatically prefixed with `sourceDir` (default: `app/javascrip
62
84
 
63
85
  | Helper | Purpose |
64
86
  |--------|---------|
65
- | `vite_tags(*entries, nonce: nil)` | Emits script, stylesheet, and modulepreload tags |
87
+ | `vite_tags(*entries, **options)` | Emits script, stylesheet, and modulepreload tags |
88
+ | `vite_javascript_tag(*entries, **options)` | Same as `vite_tags`, appends `.js` to extensionless names |
89
+ | `vite_stylesheet_tag(*entries, **options)` | Same as `vite_tags`, appends `.css` to extensionless names |
90
+ | `vite_typescript_tag(*entries, **options)` | Same as `vite_tags`, appends `.ts` to extensionless names |
66
91
  | `vite_asset_path(name)` | Returns the fingerprinted path from the manifest |
67
92
  | `vite_image_tag(name, **options)` | Image tag with manifest-resolved src |
68
93
 
94
+ All tag helpers accept arbitrary HTML attributes:
95
+
96
+ ```erb
97
+ <%= vite_tags "application.js", "application.css",
98
+ "data-turbo-track": "reload", nonce: content_security_policy_nonce %>
99
+ ```
100
+
69
101
  ### CSS Entry Points
70
102
 
71
103
  CSS files are detected by extension and emit `<link rel="stylesheet">`:
@@ -80,6 +112,29 @@ CSS files are detected by extension and emit `<link rel="stylesheet">`:
80
112
  <%= vite_tags "application.js", nonce: content_security_policy_nonce %>
81
113
  ```
82
114
 
115
+ ### Subresource Integrity (SRI)
116
+
117
+ [SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) lets browsers verify that fetched assets haven't been tampered with by checking cryptographic hashes. Install the [`vite-plugin-manifest-sri`](https://github.com/nicolo-ribaudo/vite-plugin-manifest-sri) plugin:
118
+
119
+ ```bash
120
+ npm install -D vite-plugin-manifest-sri
121
+ ```
122
+
123
+ ```typescript
124
+ import { defineConfig } from 'vite';
125
+ import rails from 'rails-vite-plugin';
126
+ import manifestSRI from 'vite-plugin-manifest-sri';
127
+
128
+ export default defineConfig({
129
+ plugins: [
130
+ rails(),
131
+ manifestSRI(),
132
+ ],
133
+ });
134
+ ```
135
+
136
+ That's it — `integrity` and `crossorigin="anonymous"` attributes are automatically added to all script, stylesheet, and modulepreload tags when the manifest includes integrity hashes.
137
+
83
138
  ### Asset Discovery (Images, Fonts)
84
139
 
85
140
  Use `import.meta.glob` in your entry point to include assets in the Vite manifest:
@@ -114,7 +169,7 @@ export default defineConfig({
114
169
 
115
170
  | Option | Default | Description |
116
171
  |--------|---------|-------------|
117
- | `input` | auto-detected | Entry point(s). Auto-detects `application.{js,ts,jsx,tsx}` in `sourceDir` |
172
+ | `input` | auto-detected | Entry point(s). If `sourceDir/entrypoints/` exists, all files in it are used. Otherwise, detects `application.{js,ts,jsx,tsx}` in `sourceDir` |
118
173
  | `sourceDir` | `'app/javascript'` | Source directory. Short names are prefixed with this. Also sets the `@` import alias |
119
174
  | `ssr` | — | SSR entry point |
120
175
  | `ssrOutputDirectory` | `'ssr'` | SSR output directory |
@@ -262,6 +317,50 @@ Defaults match the plugin defaults — no config needed if you follow convention
262
317
  `vite:build` hooks into `assets:precompile` and `test:prepare` automatically. Skip with `SKIP_VITE_BUILD=1`.
263
318
 
264
319
 
320
+ ## Migrating from vite_rails
321
+
322
+ ### 1. Swap dependencies
323
+
324
+ ```ruby
325
+ # Gemfile
326
+ - gem "vite_rails"
327
+ + gem "rails_vite"
328
+ ```
329
+
330
+ ```json
331
+ // package.json — replace vite-plugin-ruby with rails-vite-plugin
332
+ - "vite-plugin-ruby": "^5.1.1"
333
+ + "rails-vite-plugin": "^0.1.0"
334
+ ```
335
+
336
+ ### 2. Replace `vite.config.ts`
337
+
338
+ ```typescript
339
+ import { defineConfig } from 'vite';
340
+ import rails from 'rails-vite-plugin';
341
+
342
+ export default defineConfig({
343
+ plugins: [
344
+ rails({
345
+ sourceDir: 'app/frontend',
346
+ }),
347
+ ],
348
+ });
349
+ ```
350
+
351
+ If you have an `entrypoints/` directory inside `sourceDir`, all files in it are auto-discovered — no need to list them. Otherwise, set `input` explicitly.
352
+
353
+ ### 3. Delete files
354
+
355
+ - `config/vite.json` — settings now live in `vite.config.ts`
356
+ - `bin/vite` — no longer needed, `Procfile.dev` runs `npx vite` directly
357
+
358
+ ### 4. Update layouts
359
+
360
+ Remove `vite_client_tag` and `vite_react_refresh_tag` — both are automatic now.
361
+
362
+ The `vite_javascript_tag`, `vite_stylesheet_tag`, and `vite_typescript_tag` helpers work as drop-in replacements:
363
+
265
364
  ## Contributing
266
365
 
267
366
  Bug reports and pull requests are welcome on GitHub at https://github.com/skryukov/rails_vite.
@@ -20,6 +20,10 @@ module RailsVite
20
20
  plugin_meta["sourceDir"] || "app/javascript"
21
21
  end
22
22
 
23
+ def entrypoints_dir
24
+ plugin_meta["entrypointsDir"]
25
+ end
26
+
23
27
  def auto_build?
24
28
  return @auto_build if defined?(@auto_build)
25
29
  Rails.env.local?
@@ -6,13 +6,18 @@ module RailsVite
6
6
  @path = path
7
7
  end
8
8
 
9
+ # Vite's default resolve.extensions order, plus CSS extensions
10
+ RESOLVE_EXTENSIONS = %w[.mjs .js .mts .ts .jsx .tsx .json .css .scss .sass .less .styl .pcss].freeze
11
+
9
12
  def lookup(name)
10
13
  manifest = data
11
- entry = manifest[name] || raise(MissingEntryError.new(name, @path))
14
+ entry = manifest[name] || resolve_with_extension(name, manifest) ||
15
+ raise(MissingEntryError.new(name, @path))
12
16
 
13
17
  {
14
18
  file: entry["file"],
15
- css: entry.fetch("css", []),
19
+ integrity: entry["integrity"],
20
+ css: resolve_css(entry, manifest),
16
21
  imports: resolve_imports(entry, Set.new, manifest)
17
22
  }
18
23
  end
@@ -43,6 +48,23 @@ module RailsVite
43
48
  raise MissingManifestError.new(@path)
44
49
  end
45
50
 
51
+ def resolve_with_extension(name, manifest)
52
+ return if File.extname(name).present?
53
+
54
+ RESOLVE_EXTENSIONS.each do |ext|
55
+ entry = manifest["#{name}#{ext}"]
56
+ return entry if entry
57
+ end
58
+ nil
59
+ end
60
+
61
+ def resolve_css(entry, manifest)
62
+ entry.fetch("css", []).map do |css_file|
63
+ integrity = manifest.values.find { |e| e["file"] == css_file }&.dig("integrity")
64
+ {file: css_file, integrity: integrity}
65
+ end
66
+ end
67
+
46
68
  def resolve_imports(entry, seen, manifest)
47
69
  entry.fetch("imports", []).flat_map do |import_key|
48
70
  next [] if seen.include?(import_key)
@@ -51,7 +73,8 @@ module RailsVite
51
73
  imported = manifest[import_key]
52
74
  next [] unless imported
53
75
 
54
- [imported["file"]] + resolve_imports(imported, seen, manifest)
76
+ [{file: imported["file"], integrity: imported["integrity"]}] +
77
+ resolve_imports(imported, seen, manifest)
55
78
  end
56
79
  end
57
80
  end
@@ -4,14 +4,27 @@ module RailsVite
4
4
 
5
5
  def vite_tags(*entries, nonce: nil, **options)
6
6
  config = RailsVite.config
7
- resolved = entries.map { |e| resolve_vite_entry(e, config.source_dir) }
7
+ resolved = entries.map { |e| resolve_vite_entry(e, config.source_dir, config.entrypoints_dir) }
8
8
 
9
9
  if config.dev_server_running?
10
- vite_dev_tags(resolved, config.dev_server_url, nonce: nonce)
10
+ vite_dev_tags(resolved, config.dev_server_url, nonce: nonce, **options)
11
11
  else
12
- vite_prod_tags(resolved, nonce: nonce)
12
+ vite_prod_tags(resolved, nonce: nonce, **options)
13
13
  end
14
14
  end
15
+ alias_method :vite_tag, :vite_tags
16
+
17
+ def vite_javascript_tag(*entries, **options)
18
+ vite_tags(*entries.map { |e| with_default_ext(e, ".js") }, **options)
19
+ end
20
+
21
+ def vite_stylesheet_tag(*entries, **options)
22
+ vite_tags(*entries.map { |e| with_default_ext(e, ".css") }, **options)
23
+ end
24
+
25
+ def vite_typescript_tag(*entries, **options)
26
+ vite_tags(*entries.map { |e| with_default_ext(e, ".ts") }, **options)
27
+ end
15
28
 
16
29
  def vite_asset_path(name)
17
30
  vite_asset_url(RailsVite.manifest.path_for(name))
@@ -23,7 +36,7 @@ module RailsVite
23
36
 
24
37
  private
25
38
 
26
- def vite_dev_tags(entries, dev_url, nonce: nil)
39
+ def vite_dev_tags(entries, dev_url, nonce: nil, **options)
27
40
  tags = []
28
41
 
29
42
  unless @_vite_client_emitted
@@ -42,45 +55,59 @@ module RailsVite
42
55
  end
43
56
 
44
57
  entries.each do |entry|
45
- tags << build_asset_tag(entry, "#{dev_url}/#{entry}", nonce: nonce)
58
+ tags << build_asset_tag(entry, "#{dev_url}/#{entry}", nonce: nonce, **options)
46
59
  end
47
60
 
48
61
  safe_join(tags, "\n")
49
62
  end
50
63
 
51
- def vite_prod_tags(entries, nonce: nil)
64
+ def vite_prod_tags(entries, nonce: nil, **options)
52
65
  tags = []
53
66
  preloaded = Set.new
54
67
 
55
68
  entries.each do |entry|
56
69
  result = RailsVite.manifest.lookup(entry)
57
70
 
58
- Array(result[:imports]).each do |import_file|
59
- next if preloaded.include?(import_file)
60
- preloaded.add(import_file)
61
- tags << tag.link(rel: "modulepreload", href: vite_asset_url(import_file), nonce: nonce)
71
+ Array(result[:imports]).each do |import_entry|
72
+ next if preloaded.include?(import_entry[:file])
73
+ preloaded.add(import_entry[:file])
74
+ tags << tag.link(rel: "modulepreload", href: vite_asset_url(import_entry[:file]),
75
+ nonce: nonce, **sri_attrs(import_entry[:integrity]))
62
76
  end
63
77
 
64
- tags << build_asset_tag(entry, vite_asset_url(result[:file]), nonce: nonce)
78
+ tags << build_asset_tag(entry, vite_asset_url(result[:file]),
79
+ nonce: nonce, integrity: result[:integrity], **options)
65
80
 
66
- Array(result[:css]).each do |css_file|
67
- tags << tag.link(rel: "stylesheet", href: vite_asset_url(css_file), nonce: nonce)
81
+ Array(result[:css]).each do |css_entry|
82
+ tags << tag.link(rel: "stylesheet", href: vite_asset_url(css_entry[:file]),
83
+ nonce: nonce, **sri_attrs(css_entry[:integrity]), **options)
68
84
  end
69
85
  end
70
86
 
71
87
  safe_join(tags, "\n")
72
88
  end
73
89
 
74
- def build_asset_tag(entry, url, nonce: nil)
90
+ def build_asset_tag(entry, url, nonce: nil, integrity: nil, **options)
75
91
  if css_entry?(entry)
76
- tag.link(rel: "stylesheet", href: url, nonce: nonce)
92
+ tag.link(rel: "stylesheet", href: url, nonce: nonce, **sri_attrs(integrity), **options)
77
93
  else
78
- tag.script(src: url, type: "module", nonce: nonce)
94
+ tag.script(src: url, type: "module", nonce: nonce, **sri_attrs(integrity), **options)
79
95
  end
80
96
  end
81
97
 
82
- def resolve_vite_entry(entry, source_dir)
83
- entry.start_with?("#{source_dir}/", "/") ? entry : "#{source_dir}/#{entry}"
98
+ def sri_attrs(integrity)
99
+ integrity ? {integrity: integrity, crossorigin: "anonymous"} : {}
100
+ end
101
+
102
+ def with_default_ext(name, ext)
103
+ File.extname(name).empty? ? "#{name}#{ext}" : name
104
+ end
105
+
106
+ def resolve_vite_entry(entry, source_dir, entrypoints_dir)
107
+ return entry if entry.start_with?("#{source_dir}/", "/")
108
+
109
+ prefix = entrypoints_dir ? "#{source_dir}/#{entrypoints_dir}" : source_dir
110
+ "#{prefix}/#{entry}"
84
111
  end
85
112
 
86
113
  def vite_asset_url(file)
@@ -1,3 +1,3 @@
1
1
  module RailsVite
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_vite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov