rails_vite 0.2.3 → 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 +35 -0
- data/README.md +91 -3
- data/lib/generators/rails_vite/install/install_generator.rb +26 -1
- data/lib/rails_vite/auto_build.rb +15 -2
- data/lib/rails_vite/config.rb +53 -7
- data/lib/rails_vite/manifest.rb +20 -14
- data/lib/rails_vite/tag_helper.rb +12 -26
- data/lib/rails_vite/tasks.rb +39 -9
- data/lib/rails_vite/version.rb +1 -1
- data/lib/rails_vite.rb +1 -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,36 @@ 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
|
+
|
|
8
38
|
## rails_vite@0.2.3 / rails-vite-plugin@0.2.5 - 2026-06-09
|
|
9
39
|
|
|
10
40
|
### Added
|
|
@@ -93,6 +123,11 @@ and this project adheres to [Semantic Versioning].
|
|
|
93
123
|
|
|
94
124
|
[@skryukov]: https://github.com/skryukov
|
|
95
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
|
|
96
131
|
|
|
97
132
|
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
|
|
98
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:
|
|
@@ -166,6 +167,19 @@ Then reference them in views:
|
|
|
166
167
|
<%= vite_image_tag "app/assets/images/logo.png", alt: "Logo" %>
|
|
167
168
|
```
|
|
168
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
|
+
|
|
169
183
|
## Vite Config
|
|
170
184
|
|
|
171
185
|
The install generator creates a minimal `vite.config.ts`:
|
|
@@ -181,6 +195,23 @@ export default defineConfig({
|
|
|
181
195
|
});
|
|
182
196
|
```
|
|
183
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
|
+
|
|
184
215
|
### Plugin Options
|
|
185
216
|
|
|
186
217
|
| Option | Default | Description |
|
|
@@ -190,9 +221,10 @@ export default defineConfig({
|
|
|
190
221
|
| `ssr` | — | SSR entry point |
|
|
191
222
|
| `ssrOutDir` | `'ssr'` | SSR output directory |
|
|
192
223
|
| `devMetaFile` | `'tmp/rails-vite.json'` | Dev metadata file path |
|
|
193
|
-
| `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 |
|
|
194
225
|
| `publicDir` | `'public'` | Public directory |
|
|
195
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 |
|
|
196
228
|
| `prependSourceDirToEntries` | `true` | When `false`, entries are resolved without the `sourceDir` prefix. Set this when Vite's `root` is your `sourceDir` (see below) |
|
|
197
229
|
|
|
198
230
|
### Multiple Entry Points
|
|
@@ -303,12 +335,34 @@ npx vite build && npx vite build --ssr
|
|
|
303
335
|
node ssr/ssr.js
|
|
304
336
|
```
|
|
305
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
|
+
|
|
306
346
|
## Auto Build
|
|
307
347
|
|
|
308
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`.
|
|
309
349
|
|
|
310
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.
|
|
311
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
|
+
|
|
312
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.
|
|
313
367
|
|
|
314
368
|
Disable it:
|
|
@@ -322,6 +376,31 @@ By default, auto build is enabled in development and test (`Rails.env.local?`).
|
|
|
322
376
|
|
|
323
377
|
Note: for parallel test runners, disable auto build and use `rake vite:build` before the suite instead.
|
|
324
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
|
+
|
|
325
404
|
## Testing the Build
|
|
326
405
|
|
|
327
406
|
To verify your production build works in development:
|
|
@@ -331,7 +410,7 @@ rake vite:build # build assets
|
|
|
331
410
|
bin/rails s # start Rails without Vite dev server
|
|
332
411
|
```
|
|
333
412
|
|
|
334
|
-
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.
|
|
335
414
|
|
|
336
415
|
Clean up built assets with `rake vite:clobber`.
|
|
337
416
|
|
|
@@ -357,6 +436,14 @@ Defaults match the plugin defaults — no config needed if you follow convention
|
|
|
357
436
|
|
|
358
437
|
`vite:build` hooks into `assets:precompile` and `test:prepare` automatically. Skip with `SKIP_VITE_BUILD=1`.
|
|
359
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
|
+
|
|
360
447
|
|
|
361
448
|
## jsbundling Mode
|
|
362
449
|
|
|
@@ -390,6 +477,7 @@ export default defineConfig({
|
|
|
390
477
|
| `outputDir` | `'public/assets'` | Public directory for the full Vite build output |
|
|
391
478
|
| `ssr` | — | SSR entry point. String or `{ entry, outDir }` |
|
|
392
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 |
|
|
393
481
|
| `devMetaFile` | `'tmp/rails-vite.json'` | Dev metadata file path. Set to `false` to disable |
|
|
394
482
|
|
|
395
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
|
|
@@ -26,7 +26,7 @@ module RailsVite
|
|
|
26
26
|
|
|
27
27
|
# Build quietly: vite's per-build summary is noise on every auto-build
|
|
28
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_command} --logLevel warn")
|
|
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
|
|
@@ -54,7 +54,7 @@ module RailsVite
|
|
|
54
54
|
latest = NEVER
|
|
55
55
|
source_path = Rails.root.join(@config.source_dir).to_s
|
|
56
56
|
|
|
57
|
-
Find.find(source_path) do |path|
|
|
57
|
+
Find.find(source_path, *auto_build_path_matches) do |path|
|
|
58
58
|
next unless File.file?(path)
|
|
59
59
|
mtime = File.mtime(path)
|
|
60
60
|
latest = mtime if mtime > latest
|
|
@@ -64,5 +64,18 @@ module RailsVite
|
|
|
64
64
|
rescue Errno::ENOENT
|
|
65
65
|
Time.now # source dir missing — trigger build to surface the error
|
|
66
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
|
|
67
80
|
end
|
|
68
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
|
|
@@ -77,6 +81,7 @@ module RailsVite
|
|
|
77
81
|
def vite_prod_tags(entries, nonce: nil, **options)
|
|
78
82
|
tags = []
|
|
79
83
|
preloaded = Set.new
|
|
84
|
+
linked_css = Set.new
|
|
80
85
|
|
|
81
86
|
entries.each do |entry|
|
|
82
87
|
result = RailsVite.manifest.lookup(entry)
|
|
@@ -84,15 +89,16 @@ module RailsVite
|
|
|
84
89
|
Array(result[:imports]).each do |import_entry|
|
|
85
90
|
next if preloaded.include?(import_entry[:file])
|
|
86
91
|
preloaded.add(import_entry[:file])
|
|
87
|
-
tags << tag.link(rel: "modulepreload", href:
|
|
92
|
+
tags << tag.link(rel: "modulepreload", href: vite_build_path(import_entry[:file]),
|
|
88
93
|
nonce: nonce, **sri_attrs(import_entry[:integrity]))
|
|
89
94
|
end
|
|
90
95
|
|
|
91
|
-
tags << build_asset_tag(entry,
|
|
96
|
+
tags << build_asset_tag(entry, vite_build_path(result[:file]),
|
|
92
97
|
nonce: nonce, integrity: result[:integrity], **options)
|
|
93
98
|
|
|
94
99
|
Array(result[:css]).each do |css_entry|
|
|
95
|
-
|
|
100
|
+
next unless linked_css.add?(css_entry[:file])
|
|
101
|
+
tags << tag.link(rel: "stylesheet", href: vite_build_path(css_entry[:file]),
|
|
96
102
|
nonce: nonce, **sri_attrs(css_entry[:integrity]), **options)
|
|
97
103
|
end
|
|
98
104
|
end
|
|
@@ -126,32 +132,12 @@ module RailsVite
|
|
|
126
132
|
[source_dir, entrypoints_dir, entry].reject { |part| part.nil? || part.empty? }.join("/")
|
|
127
133
|
end
|
|
128
134
|
|
|
129
|
-
def
|
|
130
|
-
|
|
135
|
+
def vite_build_path(file)
|
|
136
|
+
path_to_asset("#{RailsVite.config.asset_prefix}/#{file}")
|
|
131
137
|
end
|
|
132
138
|
|
|
133
139
|
def css_entry?(entry)
|
|
134
140
|
CSS_EXTENSIONS.match?(entry)
|
|
135
141
|
end
|
|
136
|
-
|
|
137
|
-
def vite_prefix_asset_host(path)
|
|
138
|
-
host = vite_resolve_asset_host
|
|
139
|
-
host ? "#{host}#{path}" : path
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Memoized per view instance (one per request). Safe for Proc hosts
|
|
143
|
-
# since each request gets a fresh ActionView::Base instance.
|
|
144
|
-
def vite_resolve_asset_host
|
|
145
|
-
return @_vite_asset_host if defined?(@_vite_asset_host)
|
|
146
|
-
|
|
147
|
-
@_vite_asset_host = begin
|
|
148
|
-
case (asset_host = Rails.application.config.action_controller.asset_host)
|
|
149
|
-
when String then asset_host.chomp("/")
|
|
150
|
-
when Proc then asset_host.call("")&.chomp("/")
|
|
151
|
-
end
|
|
152
|
-
rescue NoMethodError
|
|
153
|
-
nil
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
142
|
end
|
|
157
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
|
@@ -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: []
|