rspack_rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/lib/install/application.js +2 -0
- data/lib/install/dev +10 -0
- data/lib/install/install.rb +73 -0
- data/lib/install/package.json +4 -0
- data/lib/install/rspack.config.mjs +40 -0
- data/lib/install/stimulus/application.js +9 -0
- data/lib/install/stimulus/autoload.js +31 -0
- data/lib/install/stimulus/hello_controller.js +8 -0
- data/lib/install/stimulus.rb +33 -0
- data/lib/rspack_rails/configuration.rb +31 -0
- data/lib/rspack_rails/dev_server.rb +40 -0
- data/lib/rspack_rails/doctor.rb +191 -0
- data/lib/rspack_rails/helper.rb +22 -0
- data/lib/rspack_rails/node_version.rb +58 -0
- data/lib/rspack_rails/package_manager.rb +62 -0
- data/lib/rspack_rails/railtie.rb +16 -0
- data/lib/rspack_rails/version.rb +5 -0
- data/lib/rspack_rails.rb +27 -0
- data/lib/tasks/rspack_rails.rake +52 -0
- data/sig/rspack_rails.rbs +38 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a3eb3563c7877029531bd93b2ed9036ce92b8a89c4008600ea251dfad3b641ff
|
|
4
|
+
data.tar.gz: 5d210e018044764c5e2f3f94cd06e3cb296d16e1da97d010b9e18907c1416644
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5dc66b20aadec8d3695d98448c80eb39e31a0d2c55d693002c6d318ac25c95a96ff81b8ac4829d0e60a6e09af7959ceca287f1f5c440a09af333069b6ffa0458
|
|
7
|
+
data.tar.gz: fc965e7fd0ddd80f60d120b77cf135ce9e4da264b87e66e298767c5e14e367cf9bbfda64130abc6d03d2f0368528196826e5425f8dcae8ddbe36714e9c534da8
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-11-13
|
|
4
|
+
|
|
5
|
+
- `rspack:doctor`: checks Node.js against rspack's `engines`, packages, config, entry, build output, layout, Procfile.dev, Stimulus and the dev server
|
|
6
|
+
- `rspack:install:stimulus`: Stimulus with automatic controller registration and hot-swapping under `rspack serve`
|
|
7
|
+
- `rspack_javascript_tag` / `rspack_stylesheet_tag` helpers that load from `rspack serve` in development and fall back to the asset pipeline
|
|
8
|
+
- Dev server with hot module replacement (`npm run dev`), configured via `RSPACK_DEV_SERVER_*` env vars or `RspackRails.configure`
|
|
9
|
+
- Installer adds `@rspack/dev-server` and upgrades Phase 1 layouts and `Procfile.dev`
|
|
10
|
+
- Railtie with `rspack:install`, `rspack:build`, `rspack:dependencies` and `rspack:clobber` tasks
|
|
11
|
+
- Hook `rspack:build` into `assets:precompile` and test prepare tasks
|
|
12
|
+
- Package manager detection (bun, yarn, pnpm, npm)
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 tungnt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# rspack_rails
|
|
2
|
+
|
|
3
|
+
Bundle your Rails app's JavaScript with [Rspack](https://rspack.rs), the Rust-powered, webpack-compatible bundler.
|
|
4
|
+
|
|
5
|
+
rspack_rails follows the `jsbundling-rails` model: rspack writes bundles to `app/assets/builds`, and Propshaft (or Sprockets) serves and fingerprints them.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add rspack_rails
|
|
11
|
+
bin/rails rspack:install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The installer:
|
|
15
|
+
|
|
16
|
+
- creates `rspack.config.mjs` and `app/javascript/application.js`
|
|
17
|
+
- adds `build`, `build:watch` and `dev` (`rspack serve`) scripts
|
|
18
|
+
- adds `rspack_javascript_tag "application"` to your layout
|
|
19
|
+
- adds a `js:` process running the dev server to `Procfile.dev` and sets up `bin/dev`
|
|
20
|
+
- installs `@rspack/cli`, `@rspack/core` and `@rspack/dev-server` with your package manager (bun, yarn, pnpm or npm, detected from the lockfile)
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Run `bin/dev` to start Rails and the rspack dev server (with hot module replacement) together.
|
|
25
|
+
|
|
26
|
+
### View helpers
|
|
27
|
+
|
|
28
|
+
```erb
|
|
29
|
+
<%= rspack_javascript_tag "application", "data-turbo-track": "reload" %>
|
|
30
|
+
<%= rspack_stylesheet_tag "application" %>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
In development, when `rspack serve` is reachable, the helpers load bundles from the dev server (`http://localhost:3036/application.js`), so edits are hot-reloaded. Otherwise they fall back to `javascript_include_tag` / `stylesheet_link_tag`, served and fingerprinted by Propshaft from `app/assets/builds`. Production always uses the asset pipeline. `rspack_javascript_tag` adds `defer` by default.
|
|
34
|
+
|
|
35
|
+
### Dev server settings
|
|
36
|
+
|
|
37
|
+
`rspack.config.mjs` and the gem read the same environment variables, so a `.env` file (loaded by foreman) changes both:
|
|
38
|
+
|
|
39
|
+
| Variable | Default |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `RSPACK_DEV_SERVER_HOST` | `localhost` |
|
|
42
|
+
| `RSPACK_DEV_SERVER_PORT` | `3036` |
|
|
43
|
+
| `RSPACK_DEV_SERVER=false` | turns the dev server check off |
|
|
44
|
+
|
|
45
|
+
Rails-side settings can also go in an initializer (this does not change `rspack.config.mjs`, so prefer env vars for host and port):
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
RspackRails.configure do |config|
|
|
49
|
+
config.dev_server_port = 4000
|
|
50
|
+
config.dev_server_enabled = false
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Stimulus
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bin/rails rspack:install:stimulus
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This installs `@hotwired/stimulus` and adds `app/javascript/controllers/autoload.js`, imported from `application.js`. Every `*_controller.js` under `app/javascript/controllers` is registered automatically, with the usual naming (`admin/user_card_controller.js` becomes `admin--user-card`). There is no manifest to keep up to date.
|
|
61
|
+
|
|
62
|
+
Under `rspack serve`, editing a controller or adding a new one hot-swaps it in the open page without a reload, so form input and other page state survive.
|
|
63
|
+
|
|
64
|
+
If `application.js` imported `./controllers` (the stimulus-rails index), the installer switches it to `./controllers/autoload`. stimulus-rails may still rewrite `controllers/index.js` when you run `bin/rails generate stimulus`, but nothing imports that file any more.
|
|
65
|
+
|
|
66
|
+
### Tasks
|
|
67
|
+
|
|
68
|
+
| Task | What it does |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `bin/rails rspack:install:stimulus` | Sets up Stimulus with controller autoloading |
|
|
71
|
+
| `bin/rails rspack:doctor` | Checks Node.js, packages, config, layout, Procfile and dev server |
|
|
72
|
+
| `bin/rails rspack:build` | Installs JS dependencies, then runs `build` |
|
|
73
|
+
| `bin/rails rspack:clobber` | Removes rspack output from `app/assets/builds` |
|
|
74
|
+
|
|
75
|
+
`rspack:build` runs automatically before `assets:precompile`, `test:prepare`, `spec:prepare` and `db:test:prepare`. Set `SKIP_RSPACK_BUILD=1` to turn that off.
|
|
76
|
+
|
|
77
|
+
When `RAILS_ENV` or `NODE_ENV` is `production`, the generated config builds in production mode (minified). Lazy-loaded chunks are named `*.digested.js`, so Propshaft serves them without fingerprinting them a second time.
|
|
78
|
+
|
|
79
|
+
### Doctor
|
|
80
|
+
|
|
81
|
+
`bin/rails rspack:doctor` checks the setup and prints a hint for anything missing:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
✓ Node.js: v22.12.0 (rspack needs ^20.19.0 || >=22.12.0)
|
|
85
|
+
✓ @rspack/core: 2.3.2
|
|
86
|
+
✓ Layout: uses rspack_javascript_tag
|
|
87
|
+
! Dev server: not running at http://localhost:3036
|
|
88
|
+
→ Start it with bin/dev; until then pages use app/assets/builds
|
|
89
|
+
|
|
90
|
+
0 error(s), 1 warning(s).
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The required Node.js version comes from the `engines` field of the installed `@rspack/core`. The task exits with status 1 when there are errors, so it can run in CI.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
bin/setup
|
|
99
|
+
bundle exec rake # specs + rubocop
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
Available under the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/install/dev
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Rails application template run by `bin/rails rspack:install`.
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
pm = RspackRails::PackageManager.detect(Rails.root)
|
|
7
|
+
|
|
8
|
+
say "Creating app/assets/builds"
|
|
9
|
+
empty_directory "app/assets/builds"
|
|
10
|
+
create_file "app/assets/builds/.keep", "" unless File.exist?("app/assets/builds/.keep")
|
|
11
|
+
|
|
12
|
+
if File.exist?(".gitignore") && !File.read(".gitignore").include?("/app/assets/builds/*")
|
|
13
|
+
append_to_file ".gitignore", "\n/app/assets/builds/*\n!/app/assets/builds/.keep\n/node_modules\n"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if Rails.root.join("app/assets/config/manifest.js").exist?
|
|
17
|
+
append_to_file "app/assets/config/manifest.js", %(//= link_tree ../builds\n)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
say "Creating rspack entry point"
|
|
21
|
+
unless File.exist?("app/javascript/application.js")
|
|
22
|
+
copy_file "#{__dir__}/application.js",
|
|
23
|
+
"app/javascript/application.js"
|
|
24
|
+
end
|
|
25
|
+
copy_file "#{__dir__}/rspack.config.mjs", "rspack.config.mjs"
|
|
26
|
+
|
|
27
|
+
say "Configuring package.json"
|
|
28
|
+
copy_file "#{__dir__}/package.json", "package.json" unless File.exist?("package.json")
|
|
29
|
+
package = JSON.parse(File.read("package.json"))
|
|
30
|
+
package["scripts"] ||= {}
|
|
31
|
+
package["scripts"]["build"] = "rspack build"
|
|
32
|
+
package["scripts"]["build:watch"] = "rspack build --watch"
|
|
33
|
+
package["scripts"]["dev"] = "rspack serve"
|
|
34
|
+
File.write("package.json", "#{JSON.pretty_generate(package)}\n")
|
|
35
|
+
|
|
36
|
+
include_tag = %(<%= rspack_javascript_tag "application", "data-turbo-track": "reload" %>)
|
|
37
|
+
layout = Rails.root.join("app/views/layouts/application.html.erb")
|
|
38
|
+
if layout.exist?
|
|
39
|
+
content = File.read(layout)
|
|
40
|
+
if content.include?("javascript_importmap_tags")
|
|
41
|
+
say "Found javascript_importmap_tags in your layout. Remove it if you are replacing importmap with rspack.",
|
|
42
|
+
:yellow
|
|
43
|
+
end
|
|
44
|
+
if content.match?(/javascript_include_tag "application".*$/)
|
|
45
|
+
say "Switching javascript_include_tag to rspack_javascript_tag"
|
|
46
|
+
gsub_file layout, /<%= javascript_include_tag "application".*%>/, include_tag
|
|
47
|
+
elsif !content.include?("rspack_javascript_tag")
|
|
48
|
+
say "Adding rspack_javascript_tag to application layout"
|
|
49
|
+
insert_into_file layout, " #{include_tag}\n", before: %r{^[ \t]*</head>}
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
say "Add this to your layout: #{include_tag}", :yellow
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
say "Configuring bin/dev"
|
|
56
|
+
js_process = "js: #{pm.run_command("dev")}"
|
|
57
|
+
if !File.exist?("Procfile.dev")
|
|
58
|
+
create_file "Procfile.dev", "web: env RUBY_DEBUG_OPEN=true bin/rails server\n#{js_process}\n"
|
|
59
|
+
elsif File.read("Procfile.dev").match?(/^js: .*build:watch$/)
|
|
60
|
+
gsub_file "Procfile.dev", /^js: .*build:watch$/, js_process
|
|
61
|
+
elsif !File.read("Procfile.dev").match?(/^js:/)
|
|
62
|
+
append_to_file "Procfile.dev", "#{js_process}\n"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless File.exist?("bin/dev") && File.read("bin/dev").include?("Procfile.dev")
|
|
66
|
+
copy_file "#{__dir__}/dev", "bin/dev", force: true
|
|
67
|
+
chmod "bin/dev", 0o755, verbose: false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
say "Installing @rspack/cli, @rspack/core and @rspack/dev-server"
|
|
71
|
+
run pm.add_dev_command("@rspack/cli", "@rspack/core", "@rspack/dev-server")
|
|
72
|
+
|
|
73
|
+
say "rspack_rails installed. Run bin/dev to start developing.", :green
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { defineConfig } from "@rspack/cli";
|
|
4
|
+
|
|
5
|
+
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const isDevServer = process.argv.includes("serve");
|
|
7
|
+
const isProduction =
|
|
8
|
+
!isDevServer &&
|
|
9
|
+
(process.env.RAILS_ENV === "production" || process.env.NODE_ENV === "production");
|
|
10
|
+
|
|
11
|
+
// Keep these in sync with RspackRails.config (same env vars, same defaults).
|
|
12
|
+
const host = process.env.RSPACK_DEV_SERVER_HOST || "localhost";
|
|
13
|
+
const port = Number(process.env.RSPACK_DEV_SERVER_PORT || 3036);
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
mode: isProduction ? "production" : "development",
|
|
17
|
+
devtool: "source-map",
|
|
18
|
+
entry: {
|
|
19
|
+
application: "./app/javascript/application.js",
|
|
20
|
+
},
|
|
21
|
+
output: {
|
|
22
|
+
path: path.resolve(root, "app/assets/builds"),
|
|
23
|
+
filename: "[name].js",
|
|
24
|
+
// ".digested" tells Propshaft the file is already fingerprinted.
|
|
25
|
+
chunkFilename: "[name]-[contenthash].digested.js",
|
|
26
|
+
// Under `rspack serve`, chunks and hot updates load from the dev server.
|
|
27
|
+
publicPath: isDevServer ? `http://${host}:${port}/` : "/assets/",
|
|
28
|
+
},
|
|
29
|
+
devServer: {
|
|
30
|
+
host,
|
|
31
|
+
port,
|
|
32
|
+
hot: true,
|
|
33
|
+
allowedHosts: "all",
|
|
34
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
35
|
+
client: {
|
|
36
|
+
webSocketURL: `ws://${host}:${port}/ws`,
|
|
37
|
+
overlay: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Registers every app/javascript/controllers/**/*_controller.js with Stimulus.
|
|
2
|
+
// Managed by rspack_rails: new controllers need no manual import, and edited
|
|
3
|
+
// controllers are hot-swapped under `rspack serve` without a page reload.
|
|
4
|
+
import { application } from "./application"
|
|
5
|
+
|
|
6
|
+
const controllers = () =>
|
|
7
|
+
import.meta.webpackContext("./", { recursive: true, regExp: /_controller\.js$/ })
|
|
8
|
+
|
|
9
|
+
// "./users/profile_controller.js" -> "users--profile"
|
|
10
|
+
const identifierFor = (key) =>
|
|
11
|
+
key
|
|
12
|
+
.replace(/^\.\//, "")
|
|
13
|
+
.replace(/_controller\.js$/, "")
|
|
14
|
+
.replace(/\//g, "--")
|
|
15
|
+
.replace(/_/g, "-")
|
|
16
|
+
|
|
17
|
+
const registerAll = (context) => {
|
|
18
|
+
context.keys().forEach((key) => {
|
|
19
|
+
const controller = context(key).default
|
|
20
|
+
if (controller) application.register(identifierFor(key), controller)
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const context = controllers()
|
|
25
|
+
registerAll(context)
|
|
26
|
+
|
|
27
|
+
if (import.meta.webpackHot) {
|
|
28
|
+
// Stimulus unloads an identifier before registering it again, so
|
|
29
|
+
// re-registering swaps connected controllers in place.
|
|
30
|
+
import.meta.webpackHot.accept(context.id, () => registerAll(controllers()))
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Rails application template run by `bin/rails rspack:install:stimulus`.
|
|
4
|
+
pm = RspackRails::PackageManager.detect(Rails.root)
|
|
5
|
+
entry = "app/javascript/application.js"
|
|
6
|
+
|
|
7
|
+
say "Installing Stimulus with controller autoloading"
|
|
8
|
+
unless File.exist?("app/javascript/controllers/application.js")
|
|
9
|
+
copy_file "#{__dir__}/stimulus/application.js", "app/javascript/controllers/application.js"
|
|
10
|
+
end
|
|
11
|
+
copy_file "#{__dir__}/stimulus/autoload.js", "app/javascript/controllers/autoload.js"
|
|
12
|
+
|
|
13
|
+
if Dir.glob("app/javascript/controllers/**/*_controller.js").empty?
|
|
14
|
+
copy_file "#{__dir__}/stimulus/hello_controller.js", "app/javascript/controllers/hello_controller.js"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if !File.exist?(entry)
|
|
18
|
+
create_file entry, %(import "./controllers/autoload"\n)
|
|
19
|
+
elsif File.read(entry).match?(%r{^import ["']\./controllers(/index(\.js)?)?["'];?$})
|
|
20
|
+
say "Replacing the controllers/index.js import with controllers/autoload"
|
|
21
|
+
gsub_file entry, %r{^import ["']\./controllers(/index(\.js)?)?["'];?$}, %(import "./controllers/autoload")
|
|
22
|
+
elsif !File.read(entry).include?("./controllers/autoload")
|
|
23
|
+
append_to_file entry, %(import "./controllers/autoload"\n)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if File.exist?("app/javascript/controllers/index.js")
|
|
27
|
+
say "app/javascript/controllers/index.js is no longer imported; controllers/autoload.js replaces it.", :yellow
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
say "Installing @hotwired/stimulus"
|
|
31
|
+
run pm.add_command("@hotwired/stimulus")
|
|
32
|
+
|
|
33
|
+
say "Stimulus installed. Controllers in app/javascript/controllers are registered automatically.", :green
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RspackRails
|
|
4
|
+
# Settings shared by the dev server check and the view helpers.
|
|
5
|
+
# Defaults come from the same environment variables rspack.config.mjs reads.
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :dev_server_host, :dev_server_port
|
|
8
|
+
attr_writer :dev_server_enabled
|
|
9
|
+
|
|
10
|
+
def initialize(env = ENV)
|
|
11
|
+
@env = env
|
|
12
|
+
@dev_server_host = env.fetch("RSPACK_DEV_SERVER_HOST", "localhost")
|
|
13
|
+
@dev_server_port = Integer(env.fetch("RSPACK_DEV_SERVER_PORT", 3036))
|
|
14
|
+
@dev_server_enabled = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Unless set explicitly, the dev server is used only in development and
|
|
18
|
+
# can be switched off with RSPACK_DEV_SERVER=false.
|
|
19
|
+
def dev_server_enabled
|
|
20
|
+
return @dev_server_enabled unless @dev_server_enabled.nil?
|
|
21
|
+
|
|
22
|
+
return false if @env["RSPACK_DEV_SERVER"] == "false"
|
|
23
|
+
|
|
24
|
+
defined?(::Rails.env) ? ::Rails.env.development? : false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dev_server_url
|
|
28
|
+
"http://#{dev_server_host}:#{dev_server_port}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module RspackRails
|
|
6
|
+
# Answers whether `rspack serve` is reachable. The result is cached briefly
|
|
7
|
+
# so a page with several tags does not open a socket per tag.
|
|
8
|
+
class DevServer
|
|
9
|
+
CONNECT_TIMEOUT = 0.05
|
|
10
|
+
|
|
11
|
+
def initialize(config, check_interval: 1.0)
|
|
12
|
+
@config = config
|
|
13
|
+
@check_interval = check_interval
|
|
14
|
+
@checked_at = nil
|
|
15
|
+
@running = false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def running?
|
|
19
|
+
return false unless @config.dev_server_enabled
|
|
20
|
+
|
|
21
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
22
|
+
return @running if @checked_at && now - @checked_at < @check_interval
|
|
23
|
+
|
|
24
|
+
@checked_at = now
|
|
25
|
+
@running = reachable?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def url(path)
|
|
29
|
+
"#{@config.dev_server_url}/#{path.to_s.delete_prefix("/")}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def reachable?
|
|
35
|
+
Socket.tcp(@config.dev_server_host, @config.dev_server_port, connect_timeout: CONNECT_TIMEOUT) { true }
|
|
36
|
+
rescue SystemCallError, SocketError, IOError
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require_relative "node_version"
|
|
7
|
+
|
|
8
|
+
module RspackRails
|
|
9
|
+
# Inspects a Rails app's rspack setup and reports what is missing.
|
|
10
|
+
class Doctor
|
|
11
|
+
Check = Struct.new(:status, :name, :detail, :hint)
|
|
12
|
+
SYMBOLS = { ok: "✓", warn: "!", error: "✗" }.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(root, config: RspackRails.config, dev_server: RspackRails.dev_server, node_version: nil)
|
|
15
|
+
@root = Pathname(root)
|
|
16
|
+
@config = config
|
|
17
|
+
@dev_server = dev_server
|
|
18
|
+
@node_version = node_version
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def checks
|
|
22
|
+
@checks ||= [
|
|
23
|
+
node_check, package_check(:cli), package_check(:core), package_check(:"dev-server"),
|
|
24
|
+
config_check, scripts_check, entry_check, build_check, layout_check, procfile_check,
|
|
25
|
+
stimulus_check, dev_server_check
|
|
26
|
+
].compact
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ok?
|
|
30
|
+
checks.none? { |check| check.status == :error }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def report(io = $stdout)
|
|
34
|
+
checks.each do |check|
|
|
35
|
+
io.puts "#{SYMBOLS.fetch(check.status)} #{check.name}: #{check.detail}"
|
|
36
|
+
io.puts " → #{check.hint}" if check.hint && check.status != :ok
|
|
37
|
+
end
|
|
38
|
+
io.puts
|
|
39
|
+
io.puts summary
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def summary
|
|
43
|
+
errors = checks.count { |c| c.status == :error }
|
|
44
|
+
warnings = checks.count { |c| c.status == :warn }
|
|
45
|
+
return "Everything looks good." if errors.zero? && warnings.zero?
|
|
46
|
+
|
|
47
|
+
"#{errors} error(s), #{warnings} warning(s)."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def node_check
|
|
53
|
+
version = node_version
|
|
54
|
+
return Check.new(:error, "Node.js", "not found", "Install Node.js and make sure `node` is on PATH") unless version
|
|
55
|
+
|
|
56
|
+
required = installed_package("core")&.dig("engines", "node")
|
|
57
|
+
return Check.new(:ok, "Node.js", version) unless required
|
|
58
|
+
|
|
59
|
+
case NodeVersion.check(version, required)
|
|
60
|
+
when true then Check.new(:ok, "Node.js", "#{version} (rspack needs #{required})")
|
|
61
|
+
when false then Check.new(:error, "Node.js", "#{version}, but rspack needs #{required}", "Upgrade Node.js")
|
|
62
|
+
else Check.new(:warn, "Node.js", "#{version}; could not read rspack's requirement #{required}")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def package_check(name)
|
|
67
|
+
package = installed_package(name)
|
|
68
|
+
label = "@rspack/#{name}"
|
|
69
|
+
if package
|
|
70
|
+
Check.new(:ok, label, package["version"])
|
|
71
|
+
else
|
|
72
|
+
Check.new(name == :"dev-server" ? :warn : :error, label, "not installed",
|
|
73
|
+
"Run `#{package_manager.add_dev_command(label)}`")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def config_check
|
|
78
|
+
return Check.new(:ok, "rspack.config.mjs", "found") if exist?("rspack.config.mjs")
|
|
79
|
+
|
|
80
|
+
Check.new(:error, "rspack.config.mjs", "missing", "Run `bin/rails rspack:install`")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def scripts_check
|
|
84
|
+
scripts = package_json&.fetch("scripts", {}) || {}
|
|
85
|
+
missing = %w[build dev].reject { |name| scripts.key?(name) }
|
|
86
|
+
return Check.new(:ok, "package.json scripts", "build, dev") if missing.empty?
|
|
87
|
+
|
|
88
|
+
Check.new(missing.include?("build") ? :error : :warn, "package.json scripts",
|
|
89
|
+
"missing #{missing.join(", ")}", "Run `bin/rails rspack:install`")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def entry_check
|
|
93
|
+
return Check.new(:ok, "Entry point", "app/javascript/application.js") if exist?("app/javascript/application.js")
|
|
94
|
+
|
|
95
|
+
Check.new(:error, "Entry point", "app/javascript/application.js is missing",
|
|
96
|
+
"Create it, or point `entry` in rspack.config.mjs at your entry file")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_check
|
|
100
|
+
built = @root.join("app/assets/builds/application.js")
|
|
101
|
+
return Check.new(:ok, "Build output", "app/assets/builds/application.js (#{built.size} bytes)") if built.exist?
|
|
102
|
+
|
|
103
|
+
Check.new(:warn, "Build output", "app/assets/builds/application.js not built yet",
|
|
104
|
+
"Run `bin/rails rspack:build` (only needed without the dev server)")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def layout_check
|
|
108
|
+
layout = read("app/views/layouts/application.html.erb")
|
|
109
|
+
return Check.new(:warn, "Layout", "app/views/layouts/application.html.erb not found") unless layout
|
|
110
|
+
|
|
111
|
+
if layout.include?("javascript_importmap_tags")
|
|
112
|
+
return Check.new(:warn, "Layout", "still includes javascript_importmap_tags",
|
|
113
|
+
"Remove it if rspack replaces importmap, or the app may load JavaScript twice")
|
|
114
|
+
end
|
|
115
|
+
return Check.new(:ok, "Layout", "uses rspack_javascript_tag") if layout.include?("rspack_javascript_tag")
|
|
116
|
+
|
|
117
|
+
Check.new(:warn, "Layout", "does not use rspack_javascript_tag",
|
|
118
|
+
%(Add <%= rspack_javascript_tag "application" %> to get the dev server and HMR))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def procfile_check
|
|
122
|
+
procfile = read("Procfile.dev")
|
|
123
|
+
if procfile&.match?(/^js: .*\bdev$/)
|
|
124
|
+
Check.new(:ok, "Procfile.dev", "runs the dev server")
|
|
125
|
+
elsif procfile&.match?(/^js:/)
|
|
126
|
+
Check.new(:warn, "Procfile.dev", "js process does not run `dev`",
|
|
127
|
+
"Use `js: #{package_manager.run_command("dev")}` for HMR")
|
|
128
|
+
else
|
|
129
|
+
Check.new(:warn, "Procfile.dev", "no js process", "Run `bin/rails rspack:install`")
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def stimulus_check
|
|
134
|
+
return unless exist?("app/javascript/controllers/autoload.js")
|
|
135
|
+
|
|
136
|
+
if node_modules_package("@hotwired/stimulus")
|
|
137
|
+
Check.new(:ok, "Stimulus", "autoload enabled")
|
|
138
|
+
else
|
|
139
|
+
Check.new(:error, "Stimulus", "@hotwired/stimulus is not installed",
|
|
140
|
+
"Run `#{package_manager.add_command("@hotwired/stimulus")}`")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def dev_server_check
|
|
145
|
+
return Check.new(:ok, "Dev server", "disabled in this environment") unless @config.dev_server_enabled
|
|
146
|
+
return Check.new(:ok, "Dev server", "running at #{@config.dev_server_url}") if @dev_server.running?
|
|
147
|
+
|
|
148
|
+
Check.new(:warn, "Dev server", "not running at #{@config.dev_server_url}",
|
|
149
|
+
"Start it with bin/dev; until then pages use app/assets/builds")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def node_version
|
|
153
|
+
return @node_version if @node_version
|
|
154
|
+
|
|
155
|
+
out, status = Open3.capture2("node", "--version")
|
|
156
|
+
status.success? ? out.strip : nil
|
|
157
|
+
rescue SystemCallError
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def installed_package(name)
|
|
162
|
+
node_modules_package("@rspack/#{name}")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def node_modules_package(name)
|
|
166
|
+
path = @root.join("node_modules", name, "package.json")
|
|
167
|
+
JSON.parse(path.read) if path.exist?
|
|
168
|
+
rescue JSON::ParserError
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def package_json
|
|
173
|
+
JSON.parse(read("package.json")) if exist?("package.json")
|
|
174
|
+
rescue JSON::ParserError
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def package_manager
|
|
179
|
+
@package_manager ||= PackageManager.detect(@root)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def exist?(path)
|
|
183
|
+
@root.join(path).exist?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def read(path)
|
|
187
|
+
file = @root.join(path)
|
|
188
|
+
file.read if file.exist?
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RspackRails
|
|
4
|
+
# View helpers that load bundles from `rspack serve` when it is running
|
|
5
|
+
# and fall back to the asset pipeline (app/assets/builds) otherwise.
|
|
6
|
+
module Helper
|
|
7
|
+
def rspack_javascript_tag(*entries, **options)
|
|
8
|
+
options = { defer: true }.merge(options)
|
|
9
|
+
return javascript_include_tag(*entries, **options) unless RspackRails.dev_server.running?
|
|
10
|
+
|
|
11
|
+
safe_join(entries.map { |entry| javascript_include_tag(RspackRails.dev_server.url("#{entry}.js"), **options) },
|
|
12
|
+
"\n")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def rspack_stylesheet_tag(*entries, **options)
|
|
16
|
+
return stylesheet_link_tag(*entries, **options) unless RspackRails.dev_server.running?
|
|
17
|
+
|
|
18
|
+
safe_join(entries.map { |entry| stylesheet_link_tag(RspackRails.dev_server.url("#{entry}.css"), **options) },
|
|
19
|
+
"\n")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RspackRails
|
|
4
|
+
# Minimal semver range matching for package.json "engines" fields.
|
|
5
|
+
# Supports `||`, space-separated comparators, ^, ~, >=, <=, >, <, = and
|
|
6
|
+
# partial versions (20, 20.x).
|
|
7
|
+
module NodeVersion
|
|
8
|
+
COMPARATOR = /\A(\^|~|>=|<=|>|<|=)?v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?\z/
|
|
9
|
+
|
|
10
|
+
OPERATORS = {
|
|
11
|
+
"^" => ->(cmp, current, target) { cmp >= 0 && current[0] == target[0] },
|
|
12
|
+
"~" => ->(cmp, current, target) { cmp >= 0 && current[0..1] == target[0..1] },
|
|
13
|
+
">=" => ->(cmp, _, _) { cmp >= 0 },
|
|
14
|
+
"<=" => ->(cmp, _, _) { cmp <= 0 },
|
|
15
|
+
">" => ->(cmp, _, _) { cmp.positive? },
|
|
16
|
+
"<" => ->(cmp, _, _) { cmp.negative? }
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
Comparator = Struct.new(:operator, :parts) do
|
|
20
|
+
def match?(current)
|
|
21
|
+
target = parts + ([0] * (3 - parts.size))
|
|
22
|
+
operation = OPERATORS[operator]
|
|
23
|
+
return current.take(parts.size) == parts unless operation
|
|
24
|
+
|
|
25
|
+
operation.call(current <=> target, current, target)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def parse(version)
|
|
32
|
+
version.to_s.strip.match(/\Av?(\d+)\.(\d+)\.(\d+)/)&.captures&.map(&:to_i)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# true or false when the range can be read; nil when the version or any
|
|
36
|
+
# part of the range is in a form this matcher does not support.
|
|
37
|
+
def check(version, range)
|
|
38
|
+
current = parse(version)
|
|
39
|
+
sets = parse_range(range)
|
|
40
|
+
return nil unless current && sets
|
|
41
|
+
|
|
42
|
+
sets.any? { |comparators| comparators.all? { |comparator| comparator.match?(current) } }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_range(range)
|
|
46
|
+
sets = range.to_s.split("||").map { |set| set.strip.split(/\s+/).map { |c| parse_comparator(c) } }
|
|
47
|
+
sets.flatten.all? ? sets : nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_comparator(text)
|
|
51
|
+
match = text.match(COMPARATOR)
|
|
52
|
+
return unless match
|
|
53
|
+
|
|
54
|
+
parts = match.captures.drop(1).take_while { |part| part&.match?(/\A\d+\z/) }.map(&:to_i)
|
|
55
|
+
Comparator.new(match[1], parts)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RspackRails
|
|
4
|
+
# Detects the JavaScript package manager of an app from its lockfile and
|
|
5
|
+
# builds the shell commands rspack_rails needs.
|
|
6
|
+
class PackageManager
|
|
7
|
+
LOCKFILES = {
|
|
8
|
+
bun: %w[bun.lock bun.lockb],
|
|
9
|
+
yarn: %w[yarn.lock],
|
|
10
|
+
pnpm: %w[pnpm-lock.yaml],
|
|
11
|
+
npm: %w[package-lock.json]
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
INSTALL = {
|
|
15
|
+
bun: "bun install",
|
|
16
|
+
yarn: "yarn install",
|
|
17
|
+
pnpm: "pnpm install",
|
|
18
|
+
npm: "npm install"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
ADD = {
|
|
22
|
+
bun: "bun add",
|
|
23
|
+
yarn: "yarn add",
|
|
24
|
+
pnpm: "pnpm add",
|
|
25
|
+
npm: "npm install"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
ADD_DEV = {
|
|
29
|
+
bun: "bun add --dev",
|
|
30
|
+
yarn: "yarn add --dev",
|
|
31
|
+
pnpm: "pnpm add --save-dev",
|
|
32
|
+
npm: "npm install --save-dev"
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :root, :name
|
|
36
|
+
|
|
37
|
+
def self.detect(root = Dir.pwd)
|
|
38
|
+
new(root)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(root)
|
|
42
|
+
@root = root.to_s
|
|
43
|
+
@name = LOCKFILES.find { |_, files| files.any? { |f| File.exist?(File.join(@root, f)) } }&.first || :npm
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def install_command
|
|
47
|
+
INSTALL.fetch(name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def add_command(*packages)
|
|
51
|
+
[ADD.fetch(name), *packages].join(" ")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_dev_command(*packages)
|
|
55
|
+
[ADD_DEV.fetch(name), *packages].join(" ")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run_command(script)
|
|
59
|
+
"#{name} run #{script}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module RspackRails
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
rake_tasks do
|
|
8
|
+
load File.expand_path("../tasks/rspack_rails.rake", __dir__)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
initializer "rspack_rails.helper" do
|
|
12
|
+
require_relative "helper"
|
|
13
|
+
ActiveSupport.on_load(:action_view) { include RspackRails::Helper }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/rspack_rails.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rspack_rails/version"
|
|
4
|
+
require_relative "rspack_rails/package_manager"
|
|
5
|
+
require_relative "rspack_rails/configuration"
|
|
6
|
+
require_relative "rspack_rails/dev_server"
|
|
7
|
+
|
|
8
|
+
# Rspack integration for Rails: rake tasks, installer and view helpers.
|
|
9
|
+
module RspackRails
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def config
|
|
14
|
+
@config ||= Configuration.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configure
|
|
18
|
+
yield config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def dev_server
|
|
22
|
+
@dev_server ||= DevServer.new(config)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
require_relative "rspack_rails/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :rspack do
|
|
4
|
+
desc "Set up rspack in this Rails app"
|
|
5
|
+
task :install do
|
|
6
|
+
template = File.expand_path("../install/install.rb", __dir__)
|
|
7
|
+
system(RbConfig.ruby, "./bin/rails", "app:template", "LOCATION=#{template}") ||
|
|
8
|
+
raise(RspackRails::Error, "rspack:install failed")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
namespace :install do
|
|
12
|
+
desc "Set up Stimulus with automatic controller registration"
|
|
13
|
+
task :stimulus do
|
|
14
|
+
template = File.expand_path("../install/stimulus.rb", __dir__)
|
|
15
|
+
system(RbConfig.ruby, "./bin/rails", "app:template", "LOCATION=#{template}") ||
|
|
16
|
+
raise(RspackRails::Error, "rspack:install:stimulus failed")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Check the rspack setup of this app"
|
|
21
|
+
task doctor: :environment do
|
|
22
|
+
require "rspack_rails/doctor"
|
|
23
|
+
doctor = RspackRails::Doctor.new(Rails.root)
|
|
24
|
+
doctor.report
|
|
25
|
+
exit 1 unless doctor.ok?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Install JavaScript dependencies"
|
|
29
|
+
task :dependencies do
|
|
30
|
+
command = RspackRails::PackageManager.detect(Rails.root).install_command
|
|
31
|
+
system(command, chdir: Rails.root) || raise(RspackRails::Error, "#{command} failed")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "Build JavaScript bundles with rspack"
|
|
35
|
+
task build: :dependencies do
|
|
36
|
+
command = RspackRails::PackageManager.detect(Rails.root).run_command("build")
|
|
37
|
+
system(command, chdir: Rails.root) || raise(RspackRails::Error, "#{command} failed")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "Remove rspack build output"
|
|
41
|
+
task :clobber do
|
|
42
|
+
Dir[Rails.root.join("app/assets/builds/**/*.{js,js.map,css,css.map}")].each { |f| File.delete(f) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
unless ENV["SKIP_RSPACK_BUILD"]
|
|
47
|
+
%w[assets:precompile test:prepare spec:prepare db:test:prepare].each do |name|
|
|
48
|
+
Rake::Task[name].enhance(["rspack:build"]) if Rake::Task.task_defined?(name)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Rake::Task["assets:clobber"].enhance(["rspack:clobber"]) if Rake::Task.task_defined?("assets:clobber")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module RspackRails
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class PackageManager
|
|
8
|
+
attr_reader root: String
|
|
9
|
+
attr_reader name: Symbol
|
|
10
|
+
def self.detect: (?untyped root) -> PackageManager
|
|
11
|
+
def initialize: (untyped root) -> void
|
|
12
|
+
def install_command: () -> String
|
|
13
|
+
def add_command: (*String packages) -> String
|
|
14
|
+
def add_dev_command: (*String packages) -> String
|
|
15
|
+
def run_command: (String script) -> String
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module RspackRails
|
|
20
|
+
def self.config: () -> Configuration
|
|
21
|
+
def self.configure: () { (Configuration) -> void } -> void
|
|
22
|
+
def self.dev_server: () -> DevServer
|
|
23
|
+
|
|
24
|
+
class Configuration
|
|
25
|
+
attr_accessor dev_server_host: String
|
|
26
|
+
attr_accessor dev_server_port: Integer
|
|
27
|
+
attr_writer dev_server_enabled: bool?
|
|
28
|
+
def initialize: (?Hash[String, String] env) -> void
|
|
29
|
+
def dev_server_enabled: () -> bool
|
|
30
|
+
def dev_server_url: () -> String
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class DevServer
|
|
34
|
+
def initialize: (Configuration config, ?check_interval: Float | Integer) -> void
|
|
35
|
+
def running?: () -> bool
|
|
36
|
+
def url: (String | Symbol path) -> String
|
|
37
|
+
end
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rspack_rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- tungnt
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-11-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: railties
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '7.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9'
|
|
33
|
+
description: 'Bundle Rails JavaScript with Rspack: one-command installer, dev server
|
|
34
|
+
with hot module replacement, Stimulus controller autoloading and a setup doctor.
|
|
35
|
+
Works with Propshaft and Sprockets.'
|
|
36
|
+
email:
|
|
37
|
+
- tungnguyen120301@gmail.com
|
|
38
|
+
executables: []
|
|
39
|
+
extensions: []
|
|
40
|
+
extra_rdoc_files: []
|
|
41
|
+
files:
|
|
42
|
+
- CHANGELOG.md
|
|
43
|
+
- LICENSE.txt
|
|
44
|
+
- README.md
|
|
45
|
+
- lib/install/application.js
|
|
46
|
+
- lib/install/dev
|
|
47
|
+
- lib/install/install.rb
|
|
48
|
+
- lib/install/package.json
|
|
49
|
+
- lib/install/rspack.config.mjs
|
|
50
|
+
- lib/install/stimulus.rb
|
|
51
|
+
- lib/install/stimulus/application.js
|
|
52
|
+
- lib/install/stimulus/autoload.js
|
|
53
|
+
- lib/install/stimulus/hello_controller.js
|
|
54
|
+
- lib/rspack_rails.rb
|
|
55
|
+
- lib/rspack_rails/configuration.rb
|
|
56
|
+
- lib/rspack_rails/dev_server.rb
|
|
57
|
+
- lib/rspack_rails/doctor.rb
|
|
58
|
+
- lib/rspack_rails/helper.rb
|
|
59
|
+
- lib/rspack_rails/node_version.rb
|
|
60
|
+
- lib/rspack_rails/package_manager.rb
|
|
61
|
+
- lib/rspack_rails/railtie.rb
|
|
62
|
+
- lib/rspack_rails/version.rb
|
|
63
|
+
- lib/tasks/rspack_rails.rake
|
|
64
|
+
- sig/rspack_rails.rbs
|
|
65
|
+
homepage: https://github.com/tungnt1203/rspack_rails
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
allowed_push_host: https://rubygems.org
|
|
70
|
+
homepage_uri: https://github.com/tungnt1203/rspack_rails
|
|
71
|
+
changelog_uri: https://github.com/tungnt1203/rspack_rails/blob/main/CHANGELOG.md
|
|
72
|
+
rubygems_mfa_required: 'true'
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.1.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.5.22
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Rspack integration for Ruby on Rails
|
|
92
|
+
test_files: []
|