rails_vite 0.2.2 → 0.3.0.rc1
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 +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +132 -4
- data/lib/generators/rails_vite/install/install_generator.rb +26 -1
- data/lib/rails_vite/auto_build.rb +21 -5
- data/lib/rails_vite/config.rb +53 -7
- data/lib/rails_vite/manifest.rb +20 -14
- data/lib/rails_vite/tag_helper.rb +26 -31
- data/lib/rails_vite/tasks.rb +39 -9
- data/lib/rails_vite/version.rb +1 -1
- data/lib/rails_vite.rb +15 -0
- data/lib/tasks/rails_vite/build.rake +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e7210773488e0deadb365f2fb2ab6782d1b347d77ca2f63007eada5cbe6c59f
|
|
4
|
+
data.tar.gz: 687c142b64c7eb24431696f56c105be88b06c68686689d40005f773f8d70aebf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d4a84d843acb2feb2c9f80d26386b5071ca8c4a8093332d8e71880cddbbf608351709f30fb7cef147b25f7114818eb2b09b8d35ea0808affdf19b67594574446
|
|
7
|
+
data.tar.gz: e483f34f03fed33786db337c84b5cb290e7ce2ad65e3c73b7294f5e75b77aabffe934dac00b2cb55d79517b65181b699b43f7c4ef9feedadf2ea1190ec1781a6
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog],
|
|
6
6
|
and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
|
8
|
+
## rails_vite@0.3.0.rc1 / rails-vite-plugin@0.3.0-rc.1 - 2026-10-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `refreshDelay` plugin option for `rails()` and `jsbundling()`. It waits the given milliseconds after the last `refresh` change before the full-page reload, and sends one reload for a burst of changes. Use it when Rails sees template changes late, e.g. with `EventedFileUpdateChecker`. Default: `0`, which reloads at once as before (#45) ([@olivier-thatch])
|
|
13
|
+
- The install generator sets `"type": "module"` in package.json — Vite and rails-vite-plugin are ESM-only, and without it Node fails to load `vite.config.ts` under npm/pnpm/yarn. When package.json pins another `type`, the generator emits `vite.config.mts` instead (#38) ([@skryukov])
|
|
14
|
+
- [aube](https://github.com/aubepkg/aube) package manager support: an `aube-lock.yaml` makes the rake tasks, auto build and the install generator use aube, even next to another lockfile left over from `aube import` (#41) ([@beauraF])
|
|
15
|
+
- `config.rails_vite.vite_executable` sets the executable for `rake vite:build`, test builds and auto builds, so Vite-compatible CLIs with another name, such as Vite+'s `vp`, work. The default is `vite` (#42) ([@cole-robertson])
|
|
16
|
+
- `auto_build_paths` config option: extra paths and globs, relative to `Rails.root`, that auto build checks for changes besides `sourceDir` (for example `app/views` for Tailwind). It defaults to the root-level Vite, PostCSS, Tailwind and TypeScript configs, `package.json`, and the lockfile, so changes to them now trigger a rebuild (#46) ([@olivier-thatch])
|
|
17
|
+
- `config.rails_vite.build_mode` sets the `--mode` for `vite build` in `rake vite:build` and auto builds. The default is unchanged (`"test"` in the test environment, no `--mode` elsewhere); set it to `nil` to pass no `--mode`. The gem now passes its `build_dir` to the plugin in `RAILS_VITE_BUILD_DIR`, so the build directory no longer depends on the mode. Upgrade `rails-vite-plugin` together with the gem: older plugin versions ignore `RAILS_VITE_BUILD_DIR`. With `build_mode` set, `vite:build` runs `vite build --mode` directly instead of the package.json `build` script (#49) ([@olivier-thatch])
|
|
18
|
+
- `vite_asset_url(name)` returns a full URL for a Vite asset, like Rails' `asset_url`, for mailers and other places that need an absolute URL (#36) ([@skryukov])
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Breaking:** `rails-vite-plugin` requires Vite 7 or newer and Node.js 20.19+ or 22.12+, like Vite 7 itself. It failed to load on Vite 5 and 6 already ([@skryukov])
|
|
23
|
+
- Vite asset URLs are built with Rails' `path_to_asset`, so `asset_host` works as it does for other Rails assets: procs that take the request, `%d` hosts and per-controller hosts. Mailers now use `config.action_mailer.asset_host` (or `config.asset_host`) instead of the controller's. With `relative_url_root`, tags now include the prefix; set Vite's `base` to match (#36) ([@skryukov])
|
|
24
|
+
- `rake vite:build` loads the Rails environment first, so `config.rails_vite` settings from initializers (such as `vite_executable`) also apply to it and to `bin/rails test` (#42) ([@cole-robertson])
|
|
25
|
+
- The install generator writes `vite dev` instead of `vite` to `Procfile.dev` (#42) ([@cole-robertson])
|
|
26
|
+
- **Breaking:** `vite:build` (and therefore `assets:precompile`) runs the package.json `build` script when one exists, so `"build": "vite build && vite build --ssr"` produces both client and SSR bundles on deploy. Apps without a `build` script keep the bare `vite build`. If package.json already has a `build` script (e.g. one left over from jsbundling-rails), make sure it builds with Vite before upgrading. Test builds and auto-builds always use the bare command, since their extra flags (`--mode test`, `--logLevel warn`) would only reach the last command of a compound script (#38) ([@skryukov])
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Vite commands from the rake tasks and auto build run in `Rails.root`, so they work when Rails is started from another directory (#39) ([@janko])
|
|
31
|
+
- The package manager is detected from lockfiles in `Rails.root`, not the current directory ([@skryukov])
|
|
32
|
+
- Chunks loaded at runtime (dynamic `import()` and their preloads) now come from a string `config.action_controller.asset_host`, like the tags: `rake vite:build` and auto builds pass it to the plugin in `RAILS_VITE_ASSET_HOST`, which prepends it to Vite's `base`. Set the variable yourself for a proc or `%d` `asset_host`. Needs both the updated gem and plugin (#35) ([@skryukov])
|
|
33
|
+
- Allow Vitest's internal Vite server to start in CI. `rails()` and `jsbundling()` now skip the dev-server environment guard and dev server setup under Vitest (#43) ([@cole-robertson])
|
|
34
|
+
- Production tags now link the CSS of chunks an entry imports, including nested and shared chunks, not just the entry's own CSS. Chunk CSS comes before the entry's CSS, matching Vite's HTML output, and CSS shared by several entries in one `vite_tags` call is linked once (#40) ([@madogiwa0124])
|
|
35
|
+
- Watch the base directories of the `refresh` globs, so template and helper changes trigger a full reload on Linux. Vite's watcher disables globbing, so the globs were watched as literal paths that don't exist. On Linux this also stopped change events for the nested view directories (#44) ([@olivier-thatch])
|
|
36
|
+
- Ignore a dev metadata file whose Vite process is gone. After a hard kill (SIGKILL, OOM killer), `vite_tags` no longer link to a dead dev server, and Rails started afterwards runs auto builds again. The plugin now records its hostname, and the pid is only checked when Vite runs on the same host, so Vite in another container still counts as running. Needs both the updated gem and plugin (#48) ([@olivier-thatch])
|
|
37
|
+
|
|
38
|
+
## rails_vite@0.2.3 / rails-vite-plugin@0.2.5 - 2026-06-09
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `RailsVite.dev_server_csp_source` returns a Content Security Policy source for the running Vite dev server, resolved per request so it tracks the real (possibly auto-incremented) port and adds nothing when the server is down. Pass `websocket: true` for the HMR socket (#25) ([@skryukov])
|
|
43
|
+
- `prependSourceDirToEntries` plugin option for `rails()` and `jsbundling()`. Set it to `false` to use Vite's `root` as your `sourceDir` — entries are then resolved by their bare, root-relative names instead of being prefixed with `sourceDir` (#22) ([@skryukov])
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Auto builds now run quietly (`vite build --logLevel warn`) to keep system-test output clean; warnings and errors are still shown ([@skryukov])
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- The development `@vite/client` and React Fast Refresh tags now derive their nonce from the request (`content_security_policy_nonce`) instead of inheriting whatever nonce the first `vite_*` call passed. A nonce-less first call (e.g. a stylesheet) no longer ships a nonce-less client script under a `strict-dynamic` CSP (#25) ([@skryukov])
|
|
52
|
+
- Persist auto-build freshness across process restarts so repeated local system-test runs no longer rebuild unchanged assets. Freshness now derives from the build manifest's timestamp instead of in-memory state (#21) ([@skryukov], [@brodienguyen])
|
|
53
|
+
- Don't delete the dev metadata file owned by another Vite process on exit, so concurrent Vite servers no longer clobber each other's dev server info (#27) ([@brodienguyen])
|
|
54
|
+
- Allow Vite config to resolve in CI environments without weakening the dev-server environment guard (#28) ([@brodienguyen])
|
|
55
|
+
|
|
8
56
|
## rails_vite@0.2.2 / rails-vite-plugin@0.2.4 - 2026-04-09
|
|
9
57
|
|
|
10
58
|
### Added
|
|
@@ -74,6 +122,12 @@ and this project adheres to [Semantic Versioning].
|
|
|
74
122
|
- Initial release ([@skryukov])
|
|
75
123
|
|
|
76
124
|
[@skryukov]: https://github.com/skryukov
|
|
125
|
+
[@brodienguyen]: https://github.com/brodienguyen
|
|
126
|
+
[@olivier-thatch]: https://github.com/olivier-thatch
|
|
127
|
+
[@beauraF]: https://github.com/beauraF
|
|
128
|
+
[@cole-robertson]: https://github.com/cole-robertson
|
|
129
|
+
[@madogiwa0124]: https://github.com/madogiwa0124
|
|
130
|
+
[@janko]: https://github.com/janko
|
|
77
131
|
|
|
78
132
|
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
|
|
79
133
|
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
|
data/README.md
CHANGED
|
@@ -89,7 +89,8 @@ Short names are automatically prefixed with `sourceDir` (default: `app/javascrip
|
|
|
89
89
|
| `vite_javascript_tag(*entries, **options)` | Same as `vite_tags`, appends `.js` to extensionless names |
|
|
90
90
|
| `vite_stylesheet_tag(*entries, **options)` | Same as `vite_tags`, appends `.css` to extensionless names |
|
|
91
91
|
| `vite_typescript_tag(*entries, **options)` | Same as `vite_tags`, appends `.ts` to extensionless names |
|
|
92
|
-
| `vite_asset_path(name)` | Returns the fingerprinted path from the manifest |
|
|
92
|
+
| `vite_asset_path(name)` | Returns the fingerprinted path from the manifest, with `asset_host` applied like Rails' `asset_path` |
|
|
93
|
+
| `vite_asset_url(name)` | Same as `vite_asset_path`, as a full URL like Rails' `asset_url`. In mailers, set `asset_host` to get a full URL |
|
|
93
94
|
| `vite_image_tag(name, **options)` | Image tag with manifest-resolved src |
|
|
94
95
|
|
|
95
96
|
All tag helpers accept arbitrary HTML attributes:
|
|
@@ -107,12 +108,27 @@ CSS files are detected by extension and emit `<link rel="stylesheet">`:
|
|
|
107
108
|
<%= vite_tags "application.css" %>
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
###
|
|
111
|
+
### Content Security Policy
|
|
112
|
+
|
|
113
|
+
Pass a `nonce` to any tag helper; it's applied to every tag it emits:
|
|
111
114
|
|
|
112
115
|
```erb
|
|
113
116
|
<%= vite_tags "application.js", nonce: content_security_policy_nonce %>
|
|
114
117
|
```
|
|
115
118
|
|
|
119
|
+
The dev server's `@vite/client` and React Fast Refresh tags pick up the request nonce automatically, so they work under `strict-dynamic` even when your first `vite_*` call is a nonce-less stylesheet.
|
|
120
|
+
|
|
121
|
+
Running a CSP in development? Allow the dev server's origin with `RailsVite.dev_server_csp_source` — it resolves per request, so it tracks Vite's actual port and adds nothing when the server is down:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# config/initializers/content_security_policy.rb — inside your `policy` block
|
|
125
|
+
if Rails.env.development?
|
|
126
|
+
policy.script_src(*policy.script_src, RailsVite.dev_server_csp_source)
|
|
127
|
+
policy.style_src(*policy.style_src, RailsVite.dev_server_csp_source)
|
|
128
|
+
policy.connect_src(*policy.connect_src, RailsVite.dev_server_csp_source(websocket: true)) # HMR
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
116
132
|
### Subresource Integrity (SRI)
|
|
117
133
|
|
|
118
134
|
[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:
|
|
@@ -151,6 +167,19 @@ Then reference them in views:
|
|
|
151
167
|
<%= vite_image_tag "app/assets/images/logo.png", alt: "Logo" %>
|
|
152
168
|
```
|
|
153
169
|
|
|
170
|
+
**Migrating from Propshaft?** Vite resolves asset references itself, so you don't need Propshaft's `RAILS_ASSET_URL(...)` — swap the logical path for a path relative to the referencing file, and Vite fingerprints it:
|
|
171
|
+
|
|
172
|
+
```css
|
|
173
|
+
/* Propshaft */
|
|
174
|
+
background: url(RAILS_ASSET_URL("logo.svg"));
|
|
175
|
+
/* Vite */
|
|
176
|
+
background: url(../assets/images/logo.svg);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- In CSS: works for any stylesheet Vite processes — an entry point, or imported from one.
|
|
180
|
+
- In JS: `import logoUrl from '../assets/images/logo.svg'` (or pull in a whole folder with `import.meta.glob`).
|
|
181
|
+
- In views: `vite_asset_path` / `vite_image_tag`, which read the Vite manifest.
|
|
182
|
+
|
|
154
183
|
## Vite Config
|
|
155
184
|
|
|
156
185
|
The install generator creates a minimal `vite.config.ts`:
|
|
@@ -166,6 +195,23 @@ export default defineConfig({
|
|
|
166
195
|
});
|
|
167
196
|
```
|
|
168
197
|
|
|
198
|
+
### Custom Vite Executable
|
|
199
|
+
|
|
200
|
+
The gem runs Vite through the detected package manager (`npx vite`, `yarn vite`, `pnpm vite`, `bun run vite` or `aube exec vite`). To use a Vite-compatible executable with a different name, set `vite_executable`:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
# config/initializers/rails_vite.rb
|
|
204
|
+
Rails.application.config.rails_vite.vite_executable = "vp"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`rake vite:build`, `assets:precompile`, test builds and auto builds then run `vp build` (for example, `npx vp build`). A package.json `build` script runs as written, so use `vp build` in it too. In your `Procfile.dev`, run the dev server with `vp dev`:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
js: npx vp dev
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
[Vite+](https://viteplus.dev) installs `vp`, not `vite`, so set `vite_executable = "vp"`. Without it, the package manager may still find a plain Vite (installed by another package, or downloaded by `npx`) and build without Vite+. Package managers may also warn that the plugin's `vite` peer dependency is not met, because Vite+ replaces `vite` with its own package and version.
|
|
214
|
+
|
|
169
215
|
### Plugin Options
|
|
170
216
|
|
|
171
217
|
| Option | Default | Description |
|
|
@@ -175,9 +221,11 @@ export default defineConfig({
|
|
|
175
221
|
| `ssr` | — | SSR entry point |
|
|
176
222
|
| `ssrOutDir` | `'ssr'` | SSR output directory |
|
|
177
223
|
| `devMetaFile` | `'tmp/rails-vite.json'` | Dev metadata file path |
|
|
178
|
-
| `buildDir` | `'vite'` | Build output subdirectory inside `public
|
|
224
|
+
| `buildDir` | `$RAILS_VITE_BUILD_DIR`, else `'vite-test'` in `test` mode, else `'vite'` | Build output subdirectory inside `public/`. The gem sets `RAILS_VITE_BUILD_DIR` from `config.rails_vite.build_dir` when it runs a build |
|
|
179
225
|
| `publicDir` | `'public'` | Public directory |
|
|
180
226
|
| `refresh` | `true` | Paths to watch for full-page reload. `true` watches `app/views/**` and `app/helpers/**` |
|
|
227
|
+
| `refreshDelay` | `0` | Milliseconds to wait after the last `refresh` change before the full-page reload. Changes within the delay send one reload. Set it (e.g. `300`) when Rails sees template changes late, as with `config.file_watcher = ActiveSupport::EventedFileUpdateChecker`, so the reload does not get the old HTML |
|
|
228
|
+
| `prependSourceDirToEntries` | `true` | When `false`, entries are resolved without the `sourceDir` prefix. Set this when Vite's `root` is your `sourceDir` (see below) |
|
|
181
229
|
|
|
182
230
|
### Multiple Entry Points
|
|
183
231
|
|
|
@@ -208,6 +256,26 @@ rails({
|
|
|
208
256
|
<%= vite_tags "entrypoints/application.ts" %>
|
|
209
257
|
```
|
|
210
258
|
|
|
259
|
+
### Setting Vite's `root` to your source directory
|
|
260
|
+
|
|
261
|
+
By default, `root` is the Rails project root, so `import.meta.glob` keys and manifest entries are prefixed with `sourceDir` (e.g. `app/frontend/components/Foo.jsx`). If you prefer `vite_ruby`-style root-relative names (`components/Foo.jsx`), set Vite's `root` to your source directory and tell the plugin not to prepend `sourceDir`:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { fileURLToPath } from 'node:url'
|
|
265
|
+
|
|
266
|
+
export default defineConfig({
|
|
267
|
+
root: fileURLToPath(new URL('./app/frontend', import.meta.url)),
|
|
268
|
+
plugins: [
|
|
269
|
+
rails({ sourceDir: 'app/frontend', prependSourceDirToEntries: false }),
|
|
270
|
+
],
|
|
271
|
+
build: {
|
|
272
|
+
outDir: fileURLToPath(new URL('./public/vite', import.meta.url)),
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
With `root` set to the source directory, Vite emits bare manifest keys, and `prependSourceDirToEntries: false` makes the Rails helpers look them up by those bare names. Set both together. (Don't point `root` at a symlinked path — Vite resolves symlinks and emits keys that escape the root.)
|
|
278
|
+
|
|
211
279
|
## Adding Frameworks
|
|
212
280
|
|
|
213
281
|
### React
|
|
@@ -267,10 +335,36 @@ npx vite build && npx vite build --ssr
|
|
|
267
335
|
node ssr/ssr.js
|
|
268
336
|
```
|
|
269
337
|
|
|
338
|
+
To build the SSR bundle during `assets:precompile`, define a `build` script — `vite:build` runs it when present:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
"scripts": {
|
|
342
|
+
"build": "vite build && vite build --ssr"
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
270
346
|
## Auto Build
|
|
271
347
|
|
|
272
348
|
When the Vite dev server is not running, rails_vite automatically rebuilds assets on the first request if sources have changed. This is useful for system tests and quick checks without running `bin/dev`.
|
|
273
349
|
|
|
350
|
+
Freshness is determined by comparing your source files' timestamps against the build manifest's timestamp. Since the manifest lives on disk, unchanged assets are not rebuilt across process restarts — for example, on repeated local system-test runs.
|
|
351
|
+
|
|
352
|
+
Besides `sourceDir`, auto build checks these files at the app root by default: `vite.config.*`, `postcss.config.*`, `tailwind.config.*`, `tsconfig*.json`, `package.json`, and the package manager lockfile. Use `auto_build_paths` to add other build inputs. Each entry is a path or glob relative to `Rails.root`. Directories are checked recursively, and paths that do not exist are ignored. For example, Tailwind generates CSS from class names in your views, so add the directories it scans:
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
# config/initializers/rails_vite.rb
|
|
356
|
+
Rails.application.config.rails_vite.auto_build_paths += %w[
|
|
357
|
+
app/views
|
|
358
|
+
app/components
|
|
359
|
+
app/helpers
|
|
360
|
+
app/assets/stylesheets
|
|
361
|
+
]
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Assign the option (`=`) instead of appending to replace the defaults.
|
|
365
|
+
|
|
366
|
+
Auto builds run quietly (`vite build --logLevel warn`), so they don't clutter your test output; warnings and errors are still shown. Run `rake vite:build` directly for the full build log.
|
|
367
|
+
|
|
274
368
|
Disable it:
|
|
275
369
|
|
|
276
370
|
```ruby
|
|
@@ -282,6 +376,31 @@ By default, auto build is enabled in development and test (`Rails.env.local?`).
|
|
|
282
376
|
|
|
283
377
|
Note: for parallel test runners, disable auto build and use `rake vite:build` before the suite instead.
|
|
284
378
|
|
|
379
|
+
## CDN
|
|
380
|
+
|
|
381
|
+
Tags follow `config.action_controller.asset_host`. `rake vite:build` and auto builds also pass a string `asset_host` to the plugin in `RAILS_VITE_ASSET_HOST`, and the plugin prepends it to Vite's `base`, so chunks Vite loads at runtime (dynamic `import()` and their preloads) come from the same host. With a proc or `%d` `asset_host`, set `RAILS_VITE_ASSET_HOST` in the build's environment yourself (`.env` files are not read for it).
|
|
382
|
+
|
|
383
|
+
The host is fixed at build time, so build with the same `asset_host` you deploy with. jsbundling mode doesn't use this.
|
|
384
|
+
|
|
385
|
+
## Build Mode
|
|
386
|
+
|
|
387
|
+
`rake vite:build` and auto builds pass `--mode test` to `vite build` in the test environment, and no `--mode` elsewhere. This sets `import.meta.env.MODE` and chooses which `.env.[mode]` files Vite loads. If `MODE === 'test'` means "running under Vitest" in your app, a Rails system-test bundle then includes Vitest-only code. Choose another mode, or no mode:
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
# config/initializers/rails_vite.rb
|
|
391
|
+
if Rails.env.test?
|
|
392
|
+
Rails.application.config.rails_vite.build_mode = nil # no --mode, so Vite uses "production"
|
|
393
|
+
# or
|
|
394
|
+
Rails.application.config.rails_vite.build_mode = "e2e" # --mode e2e
|
|
395
|
+
end
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The build mode does not change the build directory: the test environment still builds to `public/vite-test/`. The gem passes its `build_dir` to the plugin in the `RAILS_VITE_BUILD_DIR` environment variable, so the gem and the plugin always use the same directory. If you run `vite build` yourself, set `RAILS_VITE_BUILD_DIR` or the plugin's `buildDir` option to match.
|
|
399
|
+
|
|
400
|
+
Upgrade `rails-vite-plugin` together with the gem: older plugin versions ignore `RAILS_VITE_BUILD_DIR` and build every mode other than `test` into `public/vite/`.
|
|
401
|
+
|
|
402
|
+
With `build_mode` set, `vite:build` runs `vite build --mode` directly instead of your package.json `build` script, because `--mode` would only reach the last command of a compound script. Build other bundles, such as SSR, in a separate step.
|
|
403
|
+
|
|
285
404
|
## Testing the Build
|
|
286
405
|
|
|
287
406
|
To verify your production build works in development:
|
|
@@ -291,7 +410,7 @@ rake vite:build # build assets
|
|
|
291
410
|
bin/rails s # start Rails without Vite dev server
|
|
292
411
|
```
|
|
293
412
|
|
|
294
|
-
Without the Vite dev server running (no `tmp/rails-vite.json
|
|
413
|
+
Without the Vite dev server running (no `tmp/rails-vite.json`, or its Vite process is gone), Rails serves built assets from `public/vite/`. To switch back to dev mode, start Vite again — the dev metadata takes priority.
|
|
295
414
|
|
|
296
415
|
Clean up built assets with `rake vite:clobber`.
|
|
297
416
|
|
|
@@ -317,6 +436,14 @@ Defaults match the plugin defaults — no config needed if you follow convention
|
|
|
317
436
|
|
|
318
437
|
`vite:build` hooks into `assets:precompile` and `test:prepare` automatically. Skip with `SKIP_VITE_BUILD=1`.
|
|
319
438
|
|
|
439
|
+
`vite:build` prefers your package.json `build` script when one exists (like jsbundling-rails), falling back to a bare `vite build`. Test builds, and builds with `build_mode` set, skip the script and run `vite build` directly.
|
|
440
|
+
|
|
441
|
+
### Package Manager
|
|
442
|
+
|
|
443
|
+
The rake tasks, auto build and the install generator run your JS package manager, picked by lockfile in this order: `aube-lock.yaml`, `bun.lockb` or `bun.lock`, `yarn.lock`, `pnpm-lock.yaml`, `package-lock.json`. Without a lockfile, the first of bun, yarn, pnpm, npm and aube found on `PATH` is used.
|
|
444
|
+
|
|
445
|
+
[aube](https://github.com/aubepkg/aube) can also use an existing pnpm, yarn, npm or bun lockfile in place, without writing `aube-lock.yaml`. Such a project is detected as the tool that owns the lockfile, so keep that tool installed, or run `aube import` to create `aube-lock.yaml`.
|
|
446
|
+
|
|
320
447
|
|
|
321
448
|
## jsbundling Mode
|
|
322
449
|
|
|
@@ -350,6 +477,7 @@ export default defineConfig({
|
|
|
350
477
|
| `outputDir` | `'public/assets'` | Public directory for the full Vite build output |
|
|
351
478
|
| `ssr` | — | SSR entry point. String or `{ entry, outDir }` |
|
|
352
479
|
| `refresh` | — | Paths to watch for full-page reload. `true` watches `app/views/**` and `app/helpers/**` |
|
|
480
|
+
| `refreshDelay` | `0` | Milliseconds to wait after the last `refresh` change before the full-page reload. Changes within the delay send one reload. Set it (e.g. `300`) when Rails sees template changes late, as with `config.file_watcher = ActiveSupport::EventedFileUpdateChecker`, so the reload does not get the old HTML |
|
|
353
481
|
| `devMetaFile` | `'tmp/rails-vite.json'` | Dev metadata file path. Set to `false` to disable |
|
|
354
482
|
|
|
355
483
|
### Replacing esbuild
|
|
@@ -10,8 +10,26 @@ module RailsVite
|
|
|
10
10
|
run RailsVite::Tasks.add_command("vite", "rails-vite-plugin")
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def ensure_esm_package
|
|
14
|
+
return unless File.exist?("package.json")
|
|
15
|
+
|
|
16
|
+
package_json = JSON.parse(File.read("package.json"))
|
|
17
|
+
@esm_package = package_json["type"] == "module"
|
|
18
|
+
return if @esm_package
|
|
19
|
+
|
|
20
|
+
if package_json.key?("type")
|
|
21
|
+
say %(package.json sets "type": "#{package_json["type"]}", but Vite and rails-vite-plugin are ESM-only — generating vite.config.mts instead.), :yellow
|
|
22
|
+
else
|
|
23
|
+
package_json["type"] = "module"
|
|
24
|
+
create_file "package.json", JSON.pretty_generate(package_json) + "\n", force: true
|
|
25
|
+
@esm_package = true
|
|
26
|
+
say %(Added "type": "module" to package.json (Vite and rails-vite-plugin are ESM-only).)
|
|
27
|
+
warn_about_commonjs_configs
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
13
31
|
def create_vite_config
|
|
14
|
-
template "vite.config.ts.tt", "vite.config.ts"
|
|
32
|
+
template "vite.config.ts.tt", @esm_package ? "vite.config.ts" : "vite.config.mts"
|
|
15
33
|
end
|
|
16
34
|
|
|
17
35
|
def create_entrypoint
|
|
@@ -65,6 +83,13 @@ module RailsVite
|
|
|
65
83
|
|
|
66
84
|
private
|
|
67
85
|
|
|
86
|
+
def warn_about_commonjs_configs
|
|
87
|
+
commonjs_configs = Dir["*.config.js"].select { |f| File.read(f).match?(/\bmodule\.exports\b|\brequire\(/) }
|
|
88
|
+
return if commonjs_configs.empty?
|
|
89
|
+
|
|
90
|
+
say "These files use CommonJS and will break under \"type\": \"module\": #{commonjs_configs.join(", ")}. Rename them to .cjs.", :yellow
|
|
91
|
+
end
|
|
92
|
+
|
|
68
93
|
def vite_dev_command
|
|
69
94
|
RailsVite::Tasks.dev_command
|
|
70
95
|
end
|
|
@@ -9,7 +9,6 @@ module RailsVite
|
|
|
9
9
|
@app = app
|
|
10
10
|
@config = config
|
|
11
11
|
@mutex = Mutex.new
|
|
12
|
-
@last_build_at = NEVER
|
|
13
12
|
@cached_source_mtime = nil
|
|
14
13
|
@source_mtime_checked_at = nil
|
|
15
14
|
end
|
|
@@ -25,16 +24,20 @@ module RailsVite
|
|
|
25
24
|
@mutex.synchronize do
|
|
26
25
|
return unless stale?
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# Build quietly: vite's per-build summary is noise on every auto-build
|
|
28
|
+
# (e.g. each local system-test run). Warnings and errors still stream.
|
|
29
|
+
Rails.logger.error("rails-vite: build failed") unless system(RailsVite::Tasks.build_env, "#{RailsVite::Tasks.build_command} --logLevel warn", chdir: Rails.root)
|
|
30
30
|
@cached_source_mtime = nil
|
|
31
31
|
@source_mtime_checked_at = nil
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def stale?
|
|
36
|
+
# The manifest file's mtime is our persisted "last build" timestamp: it
|
|
37
|
+
# lives on disk, so freshness survives process restarts (e.g. a new Rails
|
|
38
|
+
# process per local system-test run) without any extra state.
|
|
36
39
|
!File.exist?(@config.manifest_path) ||
|
|
37
|
-
latest_source_mtime > @
|
|
40
|
+
latest_source_mtime > File.mtime(@config.manifest_path)
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
def latest_source_mtime
|
|
@@ -51,7 +54,7 @@ module RailsVite
|
|
|
51
54
|
latest = NEVER
|
|
52
55
|
source_path = Rails.root.join(@config.source_dir).to_s
|
|
53
56
|
|
|
54
|
-
Find.find(source_path) do |path|
|
|
57
|
+
Find.find(source_path, *auto_build_path_matches) do |path|
|
|
55
58
|
next unless File.file?(path)
|
|
56
59
|
mtime = File.mtime(path)
|
|
57
60
|
latest = mtime if mtime > latest
|
|
@@ -61,5 +64,18 @@ module RailsVite
|
|
|
61
64
|
rescue Errno::ENOENT
|
|
62
65
|
Time.now # source dir missing — trigger build to surface the error
|
|
63
66
|
end
|
|
67
|
+
|
|
68
|
+
def auto_build_path_matches
|
|
69
|
+
root = Rails.root.to_s
|
|
70
|
+
@config.auto_build_paths.flat_map do |pattern|
|
|
71
|
+
Dir.glob(pattern.to_s, base: root).filter_map { |match| existing_realpath(match, root) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def existing_realpath(path, root)
|
|
76
|
+
File.realpath(path, root)
|
|
77
|
+
rescue SystemCallError
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
64
80
|
end
|
|
65
81
|
end
|
data/lib/rails_vite/config.rb
CHANGED
|
@@ -2,7 +2,7 @@ module RailsVite
|
|
|
2
2
|
class Config
|
|
3
3
|
META_FILENAME = "rails-vite.json"
|
|
4
4
|
|
|
5
|
-
attr_writer :dev_meta_path, :manifest_path, :asset_prefix, :auto_build, :build_dir
|
|
5
|
+
attr_writer :dev_meta_path, :manifest_path, :asset_prefix, :auto_build, :build_dir, :vite_executable, :auto_build_paths, :build_mode
|
|
6
6
|
|
|
7
7
|
def dev_meta_path
|
|
8
8
|
@dev_meta_path || Rails.root.join("tmp", META_FILENAME)
|
|
@@ -12,6 +12,16 @@ module RailsVite
|
|
|
12
12
|
@build_dir || (Rails.env.test? ? "vite-test" : "vite")
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# The `--mode` for `vite build`. nil or false passes no `--mode`, so Vite uses its default.
|
|
16
|
+
def build_mode
|
|
17
|
+
return @build_mode.presence if defined?(@build_mode)
|
|
18
|
+
"test" if Rails.env.test?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def vite_executable
|
|
22
|
+
@vite_executable || "vite"
|
|
23
|
+
end
|
|
24
|
+
|
|
15
25
|
def manifest_path
|
|
16
26
|
@manifest_path || Rails.root.join("public", build_dir, "manifest.json")
|
|
17
27
|
end
|
|
@@ -33,6 +43,19 @@ module RailsVite
|
|
|
33
43
|
Rails.env.local?
|
|
34
44
|
end
|
|
35
45
|
|
|
46
|
+
# Paths or globs relative to Rails.root that auto build checks for changes,
|
|
47
|
+
# in addition to source_dir. Directories are walked recursively.
|
|
48
|
+
def auto_build_paths
|
|
49
|
+
@auto_build_paths ||= [
|
|
50
|
+
"vite.config.*",
|
|
51
|
+
"postcss.config.*",
|
|
52
|
+
"tailwind.config.*",
|
|
53
|
+
"tsconfig*.json",
|
|
54
|
+
"package.json",
|
|
55
|
+
*Tasks::LOCKFILES.values.flatten
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
|
|
36
59
|
def dev_server_running?
|
|
37
60
|
return false if Rails.env.test?
|
|
38
61
|
!!dev_server_url
|
|
@@ -61,14 +84,37 @@ module RailsVite
|
|
|
61
84
|
end
|
|
62
85
|
|
|
63
86
|
def load_plugin_meta
|
|
64
|
-
JSON.parse(dev_meta_path.read)
|
|
87
|
+
meta = JSON.parse(dev_meta_path.read)
|
|
88
|
+
return meta if dev_server_alive?(meta)
|
|
89
|
+
|
|
90
|
+
load_build_meta
|
|
65
91
|
rescue Errno::ENOENT
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
load_build_meta
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def load_build_meta
|
|
96
|
+
JSON.parse(manifest_path.dirname.join(META_FILENAME).read)
|
|
97
|
+
rescue Errno::ENOENT, JSON::ParserError
|
|
98
|
+
{}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# A hard kill of Vite (SIGKILL, OOM) does not remove the dev meta file.
|
|
102
|
+
# Ignore the file when its process is gone.
|
|
103
|
+
def dev_server_alive?(meta)
|
|
104
|
+
pid = meta["pid"]
|
|
105
|
+
return true unless pid.is_a?(Integer) && pid.positive?
|
|
106
|
+
return true unless meta["hostname"] == Socket.gethostname
|
|
107
|
+
|
|
108
|
+
Process.kill(0, pid)
|
|
109
|
+
true
|
|
110
|
+
rescue Errno::EPERM
|
|
111
|
+
true
|
|
112
|
+
rescue Errno::ESRCH
|
|
113
|
+
unless @stale_dev_meta_pid == pid
|
|
114
|
+
@stale_dev_meta_pid = pid
|
|
115
|
+
Rails.logger&.warn("rails-vite: ignoring #{dev_meta_path}, Vite process #{pid} is not running")
|
|
71
116
|
end
|
|
117
|
+
false
|
|
72
118
|
end
|
|
73
119
|
end
|
|
74
120
|
end
|
data/lib/rails_vite/manifest.rb
CHANGED
|
@@ -14,11 +14,16 @@ module RailsVite
|
|
|
14
14
|
entry = manifest[name] || resolve_with_extension(name, manifest) ||
|
|
15
15
|
raise(MissingEntryError.new(name, @path))
|
|
16
16
|
|
|
17
|
+
imports = []
|
|
18
|
+
css = []
|
|
19
|
+
walk_imports(entry, Set.new, manifest, imports, css)
|
|
20
|
+
css.concat(entry.fetch("css", []))
|
|
21
|
+
|
|
17
22
|
{
|
|
18
23
|
file: entry["file"],
|
|
19
24
|
integrity: entry["integrity"],
|
|
20
|
-
css: resolve_css(
|
|
21
|
-
imports:
|
|
25
|
+
css: resolve_css(css.uniq, manifest),
|
|
26
|
+
imports: imports
|
|
22
27
|
}
|
|
23
28
|
end
|
|
24
29
|
|
|
@@ -58,23 +63,24 @@ module RailsVite
|
|
|
58
63
|
nil
|
|
59
64
|
end
|
|
60
65
|
|
|
61
|
-
def resolve_css(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
def resolve_css(css_files, manifest)
|
|
67
|
+
return [] if css_files.empty?
|
|
68
|
+
|
|
69
|
+
by_file = {}
|
|
70
|
+
manifest.each_value { |e| by_file[e["file"]] ||= e }
|
|
71
|
+
css_files.map { |css_file| {file: css_file, integrity: by_file[css_file]&.dig("integrity")} }
|
|
66
72
|
end
|
|
67
73
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
next
|
|
71
|
-
seen.add(import_key)
|
|
74
|
+
def walk_imports(chunk, seen, manifest, imports, css)
|
|
75
|
+
chunk.fetch("imports", []).each do |import_key|
|
|
76
|
+
next unless seen.add?(import_key)
|
|
72
77
|
|
|
73
78
|
imported = manifest[import_key]
|
|
74
|
-
next
|
|
79
|
+
next unless imported
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
imports << {file: imported["file"], integrity: imported["integrity"]}
|
|
82
|
+
walk_imports(imported, seen, manifest, imports, css)
|
|
83
|
+
css.concat(imported.fetch("css", []))
|
|
78
84
|
end
|
|
79
85
|
end
|
|
80
86
|
end
|
|
@@ -33,10 +33,14 @@ module RailsVite
|
|
|
33
33
|
if config.dev_server_running?
|
|
34
34
|
"#{config.dev_server_url}/#{resolved}"
|
|
35
35
|
else
|
|
36
|
-
|
|
36
|
+
vite_build_path(RailsVite.manifest.path_for(resolved))
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
def vite_asset_url(name)
|
|
41
|
+
url_to_asset(vite_asset_path(name))
|
|
42
|
+
end
|
|
43
|
+
|
|
40
44
|
def vite_image_tag(name, **options)
|
|
41
45
|
image_tag(vite_asset_path(name), **options)
|
|
42
46
|
end
|
|
@@ -47,8 +51,14 @@ module RailsVite
|
|
|
47
51
|
tags = []
|
|
48
52
|
|
|
49
53
|
unless @_vite_client_emitted
|
|
54
|
+
# The client/react-refresh tags are emitted once, on the first vite_*
|
|
55
|
+
# call. Their nonce must come from the request — not from whichever call
|
|
56
|
+
# happened to be first — so a nonce-less first call (e.g. a stylesheet)
|
|
57
|
+
# doesn't ship a nonce-less @vite/client under a `strict-dynamic` CSP.
|
|
58
|
+
client_nonce = nonce || (content_security_policy_nonce if respond_to?(:content_security_policy_nonce))
|
|
59
|
+
|
|
50
60
|
if RailsVite.config.react_refresh?
|
|
51
|
-
tags << tag.script(type: "module", nonce:
|
|
61
|
+
tags << tag.script(type: "module", nonce: client_nonce) {
|
|
52
62
|
<<~JS.squish.html_safe
|
|
53
63
|
import{injectIntoGlobalHook}from'#{dev_url}/@react-refresh';
|
|
54
64
|
injectIntoGlobalHook(window);
|
|
@@ -57,7 +67,7 @@ module RailsVite
|
|
|
57
67
|
JS
|
|
58
68
|
}
|
|
59
69
|
end
|
|
60
|
-
tags << tag.script(src: "#{dev_url}/@vite/client", type: "module", nonce:
|
|
70
|
+
tags << tag.script(src: "#{dev_url}/@vite/client", type: "module", nonce: client_nonce)
|
|
61
71
|
@_vite_client_emitted = true
|
|
62
72
|
end
|
|
63
73
|
|
|
@@ -71,6 +81,7 @@ module RailsVite
|
|
|
71
81
|
def vite_prod_tags(entries, nonce: nil, **options)
|
|
72
82
|
tags = []
|
|
73
83
|
preloaded = Set.new
|
|
84
|
+
linked_css = Set.new
|
|
74
85
|
|
|
75
86
|
entries.each do |entry|
|
|
76
87
|
result = RailsVite.manifest.lookup(entry)
|
|
@@ -78,15 +89,16 @@ module RailsVite
|
|
|
78
89
|
Array(result[:imports]).each do |import_entry|
|
|
79
90
|
next if preloaded.include?(import_entry[:file])
|
|
80
91
|
preloaded.add(import_entry[:file])
|
|
81
|
-
tags << tag.link(rel: "modulepreload", href:
|
|
92
|
+
tags << tag.link(rel: "modulepreload", href: vite_build_path(import_entry[:file]),
|
|
82
93
|
nonce: nonce, **sri_attrs(import_entry[:integrity]))
|
|
83
94
|
end
|
|
84
95
|
|
|
85
|
-
tags << build_asset_tag(entry,
|
|
96
|
+
tags << build_asset_tag(entry, vite_build_path(result[:file]),
|
|
86
97
|
nonce: nonce, integrity: result[:integrity], **options)
|
|
87
98
|
|
|
88
99
|
Array(result[:css]).each do |css_entry|
|
|
89
|
-
|
|
100
|
+
next unless linked_css.add?(css_entry[:file])
|
|
101
|
+
tags << tag.link(rel: "stylesheet", href: vite_build_path(css_entry[:file]),
|
|
90
102
|
nonce: nonce, **sri_attrs(css_entry[:integrity]), **options)
|
|
91
103
|
end
|
|
92
104
|
end
|
|
@@ -111,38 +123,21 @@ module RailsVite
|
|
|
111
123
|
end
|
|
112
124
|
|
|
113
125
|
def resolve_vite_entry(entry, source_dir, entrypoints_dir)
|
|
114
|
-
return entry if entry.start_with?("
|
|
126
|
+
return entry if entry.start_with?("/")
|
|
127
|
+
return entry if !source_dir.empty? && entry.start_with?("#{source_dir}/")
|
|
115
128
|
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
# source_dir is empty when the plugin's `root` is the source directory
|
|
130
|
+
# (prependSourceDirToEntries: false) — entries are then looked up by their
|
|
131
|
+
# bare, manifest-relative names.
|
|
132
|
+
[source_dir, entrypoints_dir, entry].reject { |part| part.nil? || part.empty? }.join("/")
|
|
118
133
|
end
|
|
119
134
|
|
|
120
|
-
def
|
|
121
|
-
|
|
135
|
+
def vite_build_path(file)
|
|
136
|
+
path_to_asset("#{RailsVite.config.asset_prefix}/#{file}")
|
|
122
137
|
end
|
|
123
138
|
|
|
124
139
|
def css_entry?(entry)
|
|
125
140
|
CSS_EXTENSIONS.match?(entry)
|
|
126
141
|
end
|
|
127
|
-
|
|
128
|
-
def vite_prefix_asset_host(path)
|
|
129
|
-
host = vite_resolve_asset_host
|
|
130
|
-
host ? "#{host}#{path}" : path
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Memoized per view instance (one per request). Safe for Proc hosts
|
|
134
|
-
# since each request gets a fresh ActionView::Base instance.
|
|
135
|
-
def vite_resolve_asset_host
|
|
136
|
-
return @_vite_asset_host if defined?(@_vite_asset_host)
|
|
137
|
-
|
|
138
|
-
@_vite_asset_host = begin
|
|
139
|
-
case (asset_host = Rails.application.config.action_controller.asset_host)
|
|
140
|
-
when String then asset_host.chomp("/")
|
|
141
|
-
when Proc then asset_host.call("")&.chomp("/")
|
|
142
|
-
end
|
|
143
|
-
rescue NoMethodError
|
|
144
|
-
nil
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
142
|
end
|
|
148
143
|
end
|
data/lib/rails_vite/tasks.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
1
3
|
module RailsVite
|
|
2
4
|
module Tasks
|
|
3
5
|
extend self
|
|
@@ -5,13 +7,15 @@ module RailsVite
|
|
|
5
7
|
BUN_CMD = defined?(Bundlebun) ? Bundlebun::Runner.binstub_or_binary_path : "bun"
|
|
6
8
|
|
|
7
9
|
COMMANDS = {
|
|
8
|
-
bun: {install: "#{BUN_CMD} install", add: "#{BUN_CMD} add -D",
|
|
9
|
-
yarn: {install: "yarn install", add: "yarn add -D",
|
|
10
|
-
pnpm: {install: "pnpm install", add: "pnpm add -D",
|
|
11
|
-
npm: {install: "npm install", add: "npm install -D",
|
|
10
|
+
bun: {install: "#{BUN_CMD} install", add: "#{BUN_CMD} add -D", exec: "#{BUN_CMD} run", run: "#{BUN_CMD} run"},
|
|
11
|
+
yarn: {install: "yarn install", add: "yarn add -D", exec: "yarn", run: "yarn run"},
|
|
12
|
+
pnpm: {install: "pnpm install", add: "pnpm add -D", exec: "pnpm", run: "pnpm run"},
|
|
13
|
+
npm: {install: "npm install", add: "npm install -D", exec: "npx", run: "npm run"},
|
|
14
|
+
aube: {install: "aube install", add: "aube add -D", exec: "aube exec", run: "aube run"}
|
|
12
15
|
}.freeze
|
|
13
16
|
|
|
14
17
|
LOCKFILES = {
|
|
18
|
+
aube: %w[aube-lock.yaml],
|
|
15
19
|
bun: %w[bun.lockb bun.lock],
|
|
16
20
|
yarn: %w[yarn.lock],
|
|
17
21
|
pnpm: %w[pnpm-lock.yaml],
|
|
@@ -27,29 +31,55 @@ module RailsVite
|
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def dev_command
|
|
30
|
-
|
|
34
|
+
"#{vite_command} dev"
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def build_command
|
|
34
|
-
cmd =
|
|
35
|
-
|
|
38
|
+
cmd = "#{vite_command} build"
|
|
39
|
+
mode = RailsVite.config.build_mode
|
|
40
|
+
cmd += " --mode #{Shellwords.escape(mode)}" if mode
|
|
36
41
|
cmd
|
|
37
42
|
end
|
|
38
43
|
|
|
44
|
+
def precompile_command
|
|
45
|
+
return build_command if Rails.env.test? || RailsVite.config.build_mode || !package_json_build_script?
|
|
46
|
+
"#{command_for(:run)} build"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Use with build_command. The plugin builds into this dir, so the gem and
|
|
50
|
+
# the plugin use the same dir for all build modes.
|
|
51
|
+
def build_env
|
|
52
|
+
env = {"RAILS_VITE_BUILD_DIR" => RailsVite.config.build_dir}
|
|
53
|
+
asset_host = Rails.application.config.action_controller.asset_host
|
|
54
|
+
env["RAILS_VITE_ASSET_HOST"] = asset_host if asset_host.is_a?(String) && asset_host.present? && !asset_host.include?("%d")
|
|
55
|
+
env
|
|
56
|
+
end
|
|
57
|
+
|
|
39
58
|
def tool
|
|
40
59
|
tool_determined_by_lockfile || tool_determined_by_executable
|
|
41
60
|
end
|
|
42
61
|
|
|
43
62
|
private
|
|
44
63
|
|
|
64
|
+
def vite_command
|
|
65
|
+
"#{command_for(:exec)} #{RailsVite.config.vite_executable}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def package_json_build_script?
|
|
69
|
+
path = Rails.root.join("package.json")
|
|
70
|
+
path.exist? && JSON.parse(path.read).dig("scripts", "build").present?
|
|
71
|
+
rescue JSON::ParserError
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
|
|
45
75
|
def command_for(key)
|
|
46
76
|
COMMANDS.dig(tool, key) ||
|
|
47
|
-
raise("rails_vite: No suitable JS package manager found for '#{key}'. Ensure npm, yarn, pnpm, or
|
|
77
|
+
raise("rails_vite: No suitable JS package manager found for '#{key}'. Ensure npm, yarn, pnpm, bun, or aube is available.")
|
|
48
78
|
end
|
|
49
79
|
|
|
50
80
|
def tool_determined_by_lockfile
|
|
51
81
|
LOCKFILES.each do |tool_name, files|
|
|
52
|
-
return tool_name if files.any? { |f|
|
|
82
|
+
return tool_name if files.any? { |f| Rails.root.join(f).exist? }
|
|
53
83
|
end
|
|
54
84
|
nil
|
|
55
85
|
end
|
data/lib/rails_vite/version.rb
CHANGED
data/lib/rails_vite.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "digest"
|
|
3
|
+
require "socket"
|
|
3
4
|
|
|
4
5
|
require_relative "rails_vite/errors"
|
|
5
6
|
require_relative "rails_vite/manifest"
|
|
@@ -24,6 +25,20 @@ module RailsVite
|
|
|
24
25
|
manifest.digest
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# A Content Security Policy source for the running Vite dev server. Returns
|
|
29
|
+
# a lambda so the URL resolves per request: it tracks the dev server's real
|
|
30
|
+
# (possibly auto-incremented) port and contributes nothing when the server
|
|
31
|
+
# isn't running. Pass websocket: true for the HMR socket (http -> ws).
|
|
32
|
+
#
|
|
33
|
+
# RailsVite.config is qualified explicitly because Rails resolves CSP Proc
|
|
34
|
+
# sources via instance_exec, which rebinds self to the controller.
|
|
35
|
+
def dev_server_csp_source(websocket: false)
|
|
36
|
+
-> {
|
|
37
|
+
url = RailsVite.config.dev_server_url
|
|
38
|
+
url && (websocket ? url.sub(/\Ahttp/, "ws") : url)
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
27
42
|
def reset!
|
|
28
43
|
@config = nil
|
|
29
44
|
@manifest = nil
|
|
@@ -2,13 +2,13 @@ namespace :vite do
|
|
|
2
2
|
desc "Install JavaScript dependencies"
|
|
3
3
|
task :install do
|
|
4
4
|
command = RailsVite::Tasks.install_command
|
|
5
|
-
system(command) || raise("rails_vite: Command install failed, ensure #{command.split.first} is installed")
|
|
5
|
+
system(command, chdir: Rails.root) || raise("rails_vite: Command install failed, ensure #{command.split.first} is installed")
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
desc "Build Vite assets for production"
|
|
9
|
-
task :
|
|
10
|
-
command = RailsVite::Tasks.
|
|
11
|
-
system(command) || raise("rails_vite: Command build failed, ensure `#{command}` runs without errors")
|
|
9
|
+
task build: :environment do
|
|
10
|
+
command = RailsVite::Tasks.precompile_command
|
|
11
|
+
system(RailsVite::Tasks.build_env, command, chdir: Rails.root) || raise("rails_vite: Command build failed, ensure `#{command}` runs without errors")
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
Rake::Task["vite:build"].prereqs << :install unless ENV["SKIP_VITE_INSTALL"]
|
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.
|
|
4
|
+
version: 0.3.0.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
@@ -71,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
71
71
|
- !ruby/object:Gem::Version
|
|
72
72
|
version: '0'
|
|
73
73
|
requirements: []
|
|
74
|
-
rubygems_version: 4.0.
|
|
74
|
+
rubygems_version: 4.0.20
|
|
75
75
|
specification_version: 4
|
|
76
76
|
summary: Vite integration for Rails
|
|
77
77
|
test_files: []
|