rspack_rails 0.2.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +61 -2
- data/lib/install/install.rb +1 -1
- data/lib/install/migrate.rb +103 -0
- data/lib/install/rspack.config.mjs +49 -30
- data/lib/install/stimulus.rb +4 -4
- data/lib/install/typescript.rb +1 -1
- data/lib/rspack_rails/doctor.rb +11 -6
- data/lib/rspack_rails/entrypoints.rb +27 -0
- data/lib/rspack_rails/migration.rb +125 -0
- data/lib/rspack_rails/version.rb +1 -1
- data/lib/rspack_rails.rb +2 -0
- data/lib/tasks/rspack_rails.rake +14 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5c0f485458181960a59600c16b354bbb260d5856d37200ffaf416e4d6c88222
|
|
4
|
+
data.tar.gz: a06bf0ec56b0850342ea39c4d461f4d165e8b3c1f046c7c41d6149b5412c5fbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70886ccbe82932f10548639cb3190b4a06ad032fa854c5677c87553c93d4a32feb4a9968a5b72881fa5cfb94833ca02994168a541771c95a33222d51422677f4
|
|
7
|
+
data.tar.gz: 989663562bc6cd2e8a72ef05872319117c5f21e9b43697fc57874ea6c725370a86118ec3e01bd950f854dddbc57d48a8e9f10b675f2ef0686c1126df93573203
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-11-13
|
|
4
|
+
|
|
5
|
+
- `rspack:migrate`: moves an importmap-rails or jsbundling-rails (esbuild/webpack/rollup) app to rspack. Requires a clean git tree (or `FORCE=1`)
|
|
6
|
+
- `rspack.config.mjs` resolves bare imports from `app/javascript` (`resolve.modules`), so importmap-style `import "controllers/application"` keeps working
|
|
7
|
+
- `rspack:install:stimulus` also rewrites a bare `import "controllers"`
|
|
8
|
+
- Installers stop when a package install fails instead of carrying on
|
|
9
|
+
|
|
10
|
+
## [0.3.0] - 2026-11-13
|
|
11
|
+
|
|
12
|
+
- Multiple entry points: every `app/javascript/entrypoints/*.{js,ts}` becomes a bundle named after the file; `application.ts` works as the main entry
|
|
13
|
+
- `rspack:doctor` lists entry points and reports any that are not built
|
|
14
|
+
- Fix: the dev server no longer writes `*.hot-update.js` files into `app/assets/builds`
|
|
15
|
+
- Existing apps: re-run `bin/rails rspack:install` and accept the new `rspack.config.mjs`
|
|
16
|
+
|
|
3
17
|
## [0.2.0] - 2026-11-13
|
|
4
18
|
|
|
5
19
|
- CSS imported from JavaScript is extracted to `app/assets/builds/<entry>.bundle.css` and hot-reloaded under `rspack serve`; `*.module.css` are CSS modules
|
data/README.md
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
# rspack_rails
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/rspack_rails)
|
|
4
|
+
[](https://github.com/tungnt1203/rspack_rails/actions/workflows/main.yml)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Bundle your Rails app's JavaScript and CSS with [Rspack](https://rspack.rs), the Rust-powered, webpack-compatible bundler, and get **hot module replacement** in development.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
*Above: CSS and a Stimulus controller are edited while the page is open. Both update in place, and the counter keeps its value because the page never reloads.*
|
|
11
|
+
|
|
12
|
+
- **One command setup**: `bin/rails rspack:install`, then `bin/dev`
|
|
13
|
+
- **HMR for JavaScript, CSS and Stimulus controllers**, with an automatic fallback to the asset pipeline when the dev server is not running
|
|
14
|
+
- **Stimulus autoloading**: drop a `*_controller.js` (or `.ts`) in `app/javascript/controllers`, no manifest to maintain
|
|
15
|
+
- **TypeScript and CSS modules** out of the box, through Rspack's built-in SWC and native CSS
|
|
16
|
+
- **Multiple entry points** from `app/javascript/entrypoints/`
|
|
17
|
+
- **`rspack:migrate`** moves an importmap or jsbundling app over in one command
|
|
18
|
+
- **`rspack:doctor`** checks Node.js, packages, config and layout, and says what to fix
|
|
19
|
+
- Works with **Propshaft and Sprockets**; CI runs a real app on Rails 7.2 and 8.1
|
|
20
|
+
|
|
21
|
+
rspack_rails follows the `jsbundling-rails` model: rspack writes bundles to `app/assets/builds`, and Propshaft (or Sprockets) serves and fingerprints them in production.
|
|
6
22
|
|
|
7
23
|
## Installation
|
|
8
24
|
|
|
@@ -19,6 +35,28 @@ The installer:
|
|
|
19
35
|
- adds a `js:` process running the dev server to `Procfile.dev` and sets up `bin/dev`
|
|
20
36
|
- installs `@rspack/cli`, `@rspack/core` and `@rspack/dev-server` with your package manager (bun, yarn, pnpm or npm, detected from the lockfile)
|
|
21
37
|
|
|
38
|
+
## Migrating from importmap or jsbundling
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git commit -am "Before rspack" # rspack:migrate refuses to run on a dirty tree (FORCE=1 to override)
|
|
42
|
+
bin/rails rspack:migrate
|
|
43
|
+
git diff # review
|
|
44
|
+
bin/dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**From importmap-rails**
|
|
48
|
+
- Every `pin` in `config/importmap.rb` becomes an npm dependency. Versions recorded by importmap (`# @1.2.3` or jspm URLs) are kept. Gem-served pins such as Turbo install the latest npm release.
|
|
49
|
+
- `javascript_importmap_tags` is replaced with `rspack_javascript_tag`, and `import "controllers"` with the Stimulus autoloader.
|
|
50
|
+
- `config/importmap.rb`, `bin/importmap`, vendored packages, the `importmap-rails` gem, `stale_when_importmap_changes` and the `bin/importmap audit` CI step are removed.
|
|
51
|
+
- Bare imports of app code (`import "controllers/application"`) keep working through `resolve.modules`.
|
|
52
|
+
|
|
53
|
+
**From jsbundling-rails** (esbuild, webpack, rollup)
|
|
54
|
+
- The `jsbundling-rails` gem, the old bundler package and its config file are removed. `Procfile.dev` runs the rspack dev server.
|
|
55
|
+
- `javascript_include_tag "application"` becomes `rspack_javascript_tag`, and the generated `controllers/index.js` gives way to the autoloader.
|
|
56
|
+
- Extra entry files in the root of `app/javascript/` are listed so you can move them to `app/javascript/entrypoints/`.
|
|
57
|
+
|
|
58
|
+
The task finishes by listing any remaining lines that mention importmap or jsbundling. Shakapacker/Webpacker apps are not supported yet.
|
|
59
|
+
|
|
22
60
|
## Usage
|
|
23
61
|
|
|
24
62
|
Run `bin/dev` to start Rails and the rspack dev server (with hot module replacement) together.
|
|
@@ -63,6 +101,23 @@ Under `rspack serve`, editing a controller or adding a new one hot-swaps it in t
|
|
|
63
101
|
|
|
64
102
|
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
103
|
|
|
104
|
+
### Multiple entry points
|
|
105
|
+
|
|
106
|
+
Besides `app/javascript/application.js` (or `.ts`), every file in `app/javascript/entrypoints/` becomes its own bundle, named after the file:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
app/javascript/application.js -> application.js
|
|
110
|
+
app/javascript/entrypoints/admin.ts -> admin.js
|
|
111
|
+
app/javascript/entrypoints/checkout.js -> checkout.js (+ checkout.bundle.css if it imports CSS)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```erb
|
|
115
|
+
<%# app/views/layouts/admin.html.erb %>
|
|
116
|
+
<%= rspack_javascript_tag "admin" %>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each bundle is self-contained, so load one entry per page (for example one per layout). Restart the dev server after adding a new entry point.
|
|
120
|
+
|
|
66
121
|
### CSS
|
|
67
122
|
|
|
68
123
|
Import CSS from your JavaScript and rspack extracts it to `app/assets/builds/<entry>.bundle.css`:
|
|
@@ -95,6 +150,7 @@ npm run typecheck
|
|
|
95
150
|
|---|---|
|
|
96
151
|
| `bin/rails rspack:install:stimulus` | Sets up Stimulus with controller autoloading |
|
|
97
152
|
| `bin/rails rspack:install:typescript` | Adds tsconfig.json, typescript and a `typecheck` script |
|
|
153
|
+
| `bin/rails rspack:migrate` | Moves an importmap or jsbundling app to rspack |
|
|
98
154
|
| `bin/rails rspack:doctor` | Checks Node.js, packages, config, layout, Procfile and dev server |
|
|
99
155
|
| `bin/rails rspack:build` | Installs JS dependencies, then runs `build` |
|
|
100
156
|
| `bin/rails rspack:clobber` | Removes rspack output from `app/assets/builds` |
|
|
@@ -124,6 +180,9 @@ The required Node.js version comes from the `engines` field of the installed `@r
|
|
|
124
180
|
```bash
|
|
125
181
|
bin/setup
|
|
126
182
|
bundle exec rake # specs + rubocop
|
|
183
|
+
bin/integration # new Rails app + every installer, build, doctor and precompile
|
|
184
|
+
bin/integration "~> 7.2.0" # against a specific Rails version
|
|
185
|
+
bin/integration-migrate importmap # rails new + rspack:migrate (or: jsbundling)
|
|
127
186
|
```
|
|
128
187
|
|
|
129
188
|
## License
|
data/lib/install/install.rb
CHANGED
|
@@ -68,6 +68,6 @@ unless File.exist?("bin/dev") && File.read("bin/dev").include?("Procfile.dev")
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
say "Installing @rspack/cli, @rspack/core and @rspack/dev-server"
|
|
71
|
-
run pm.add_dev_command("@rspack/cli", "@rspack/core", "@rspack/dev-server")
|
|
71
|
+
run pm.add_dev_command("@rspack/cli", "@rspack/core", "@rspack/dev-server"), abort_on_failure: true
|
|
72
72
|
|
|
73
73
|
say "rspack_rails installed. Run bin/dev to start developing.", :green
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Rails application template run by `bin/rails rspack:migrate`.
|
|
4
|
+
# Moves an app from importmap-rails or jsbundling-rails to rspack_rails.
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
migration = RspackRails::Migration.new(Rails.root)
|
|
8
|
+
pm = RspackRails::PackageManager.detect(Rails.root)
|
|
9
|
+
layout = "app/views/layouts/application.html.erb"
|
|
10
|
+
stimulus = File.exist?("app/javascript/controllers/application.js")
|
|
11
|
+
|
|
12
|
+
def remove_gem(name)
|
|
13
|
+
gem_line = /^[ \t]*gem[ \t]+["']#{Regexp.escape(name)}["'].*\n/
|
|
14
|
+
gsub_file "Gemfile", /^[ \t]*#.*\n(?=#{gem_line.source})/, "" # the comment describing it
|
|
15
|
+
gsub_file "Gemfile", gem_line, ""
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
case migration.source
|
|
19
|
+
when :rspack
|
|
20
|
+
say "This app already uses rspack_rails. Run `bin/rails rspack:doctor` to check it.", :green
|
|
21
|
+
exit
|
|
22
|
+
when :shakapacker
|
|
23
|
+
say "Shakapacker/Webpacker apps are not supported by rspack:migrate yet.", :red
|
|
24
|
+
exit 1
|
|
25
|
+
when :none
|
|
26
|
+
say "No importmap-rails or jsbundling-rails setup found. Running rspack:install instead."
|
|
27
|
+
when :importmap
|
|
28
|
+
say "Migrating from importmap-rails"
|
|
29
|
+
packages = migration.npm_install_args
|
|
30
|
+
vendored = migration.vendored_files
|
|
31
|
+
|
|
32
|
+
gsub_file layout, /^\s*<%= javascript_importmap_tags.*%>\n/, "" if File.exist?(layout)
|
|
33
|
+
if File.exist?("app/assets/config/manifest.js")
|
|
34
|
+
gsub_file "app/assets/config/manifest.js", %r{^//= link_tree \.\./\.\./javascript .js\n}, ""
|
|
35
|
+
gsub_file "app/assets/config/manifest.js", %r{^//= link_tree \.\./\.\./\.\./vendor/javascript .js\n}, ""
|
|
36
|
+
end
|
|
37
|
+
if File.exist?("app/javascript/application.js")
|
|
38
|
+
gsub_file "app/javascript/application.js", %r{^// Configure your import map in config/importmap.rb.*\n}, ""
|
|
39
|
+
end
|
|
40
|
+
Dir.glob("app/controllers/**/*.rb").each do |controller|
|
|
41
|
+
next unless File.read(controller).include?("stale_when_importmap_changes")
|
|
42
|
+
|
|
43
|
+
gsub_file controller, /^[ \t]*# Changes to the importmap will invalidate the etag.*\n/, ""
|
|
44
|
+
gsub_file controller, /^[ \t]*stale_when_importmap_changes.*\n/, ""
|
|
45
|
+
end
|
|
46
|
+
gsub_file "config/ci.rb", %r{^[ \t]*step .*bin/importmap audit.*\n}, "" if File.exist?("config/ci.rb")
|
|
47
|
+
remove_file "config/importmap.rb"
|
|
48
|
+
remove_file "bin/importmap"
|
|
49
|
+
vendored.each { |path| remove_file path }
|
|
50
|
+
remove_gem "importmap-rails"
|
|
51
|
+
when :jsbundling
|
|
52
|
+
say "Migrating from jsbundling-rails"
|
|
53
|
+
bundlers = migration.bundler_packages
|
|
54
|
+
migration.bundler_configs.each { |path| remove_file path }
|
|
55
|
+
gsub_file "Procfile.dev", /^js: .*\n/, "js: #{pm.run_command("dev")}\n" if File.exist?("Procfile.dev")
|
|
56
|
+
remove_gem "jsbundling-rails"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
apply "#{__dir__}/install.rb"
|
|
60
|
+
|
|
61
|
+
if migration.source == :importmap && packages.any?
|
|
62
|
+
say "Installing packages that were pinned in config/importmap.rb: #{packages.join(", ")}"
|
|
63
|
+
run pm.add_command(*packages), abort_on_failure: true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if migration.source == :jsbundling && bundlers.any?
|
|
67
|
+
say "Removing the previous bundler: #{bundlers.join(", ")}"
|
|
68
|
+
package = JSON.parse(File.read("package.json"))
|
|
69
|
+
bundlers.each do |name|
|
|
70
|
+
package["dependencies"]&.delete(name)
|
|
71
|
+
package["devDependencies"]&.delete(name)
|
|
72
|
+
end
|
|
73
|
+
File.write("package.json", "#{JSON.pretty_generate(package)}\n")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
apply "#{__dir__}/stimulus.rb" if stimulus
|
|
77
|
+
|
|
78
|
+
controllers_index = "app/javascript/controllers/index.js"
|
|
79
|
+
if migration.source == :importmap && File.exist?(controllers_index) &&
|
|
80
|
+
File.read(controllers_index).include?("@hotwired/stimulus-loading")
|
|
81
|
+
remove_file controllers_index # replaced by controllers/autoload.js
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if migration.source == :jsbundling
|
|
85
|
+
migration.extra_root_entries.each do |path|
|
|
86
|
+
say "#{path} was an entry point under jsbundling. Move it to app/javascript/entrypoints/ " \
|
|
87
|
+
"so rspack builds it.", :yellow
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
leftovers = Dir.glob("{app,config,lib}/**/*.{rb,erb,js,ts,yml}").flat_map do |path|
|
|
92
|
+
File.readlines(path).each_with_index.filter_map do |line, index|
|
|
93
|
+
next if line.strip.start_with?("#", "//", "<%#")
|
|
94
|
+
|
|
95
|
+
"#{path}:#{index + 1}: #{line.strip}" if line.match?(/importmap|jsbundling/i)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
if leftovers.any?
|
|
99
|
+
say "These lines still mention importmap/jsbundling and may need a manual change:", :yellow
|
|
100
|
+
leftovers.each { |line| say " #{line}", :yellow }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
say "Migration finished. Review the changes with `git diff`, then run `bin/rails rspack:doctor`.", :green
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { defineConfig } from "@rspack/cli";
|
|
@@ -12,51 +13,69 @@ const isProduction =
|
|
|
12
13
|
const host = process.env.RSPACK_DEV_SERVER_HOST || "localhost";
|
|
13
14
|
const port = Number(process.env.RSPACK_DEV_SERVER_PORT || 3036);
|
|
14
15
|
|
|
16
|
+
// Entries: app/javascript/application.{js,ts} plus every file in
|
|
17
|
+
// app/javascript/entrypoints/, named after the file (admin.js -> "admin").
|
|
18
|
+
// Restart the dev server after adding an entrypoint.
|
|
19
|
+
const entry = {};
|
|
20
|
+
for (const ext of ["js", "ts"]) {
|
|
21
|
+
const file = `./app/javascript/application.${ext}`;
|
|
22
|
+
if (!entry.application && fs.existsSync(path.resolve(root, file))) entry.application = file;
|
|
23
|
+
}
|
|
24
|
+
const entrypointsDir = path.resolve(root, "app/javascript/entrypoints");
|
|
25
|
+
if (fs.existsSync(entrypointsDir)) {
|
|
26
|
+
for (const file of fs.readdirSync(entrypointsDir).sort()) {
|
|
27
|
+
const match = file.match(/^(.+)\.(js|ts)$/);
|
|
28
|
+
if (match) entry[match[1]] = `./app/javascript/entrypoints/${file}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
export default defineConfig({
|
|
16
33
|
mode: isProduction ? "production" : "development",
|
|
17
34
|
devtool: "source-map",
|
|
18
|
-
entry
|
|
19
|
-
application: "./app/javascript/application.js",
|
|
20
|
-
},
|
|
35
|
+
entry,
|
|
21
36
|
output: {
|
|
22
37
|
path: path.resolve(root, "app/assets/builds"),
|
|
23
38
|
filename: "[name].js",
|
|
24
39
|
// ".digested" tells Propshaft the file is already fingerprinted.
|
|
25
|
-
chunkFilename: "[name]-[contenthash].digested.js",
|
|
26
|
-
// CSS imported from JavaScript. ".bundle" avoids clashing with
|
|
27
|
-
// app/assets/stylesheets/application.css in Propshaft.
|
|
28
|
-
cssFilename: "[name].bundle.css",
|
|
29
|
-
cssChunkFilename: "[name]-[contenthash].digested.css",
|
|
40
|
+
chunkFilename: "[name]-[contenthash].digested.js",
|
|
41
|
+
// CSS imported from JavaScript. ".bundle" avoids clashing with
|
|
42
|
+
// app/assets/stylesheets/application.css in Propshaft.
|
|
43
|
+
cssFilename: "[name].bundle.css",
|
|
44
|
+
cssChunkFilename: "[name]-[contenthash].digested.css",
|
|
30
45
|
// Under `rspack serve`, chunks and hot updates load from the dev server.
|
|
31
46
|
publicPath: isDevServer ? `http://${host}:${port}/` : "/assets/",
|
|
32
47
|
},
|
|
33
|
-
resolve: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
resolve: {
|
|
49
|
+
extensions: ["...", ".ts"],
|
|
50
|
+
// Bare imports of app code work as they did with importmap
|
|
51
|
+
// (import "controllers/application", import "lib/utils").
|
|
52
|
+
modules: ["node_modules", path.resolve(root, "app/javascript")],
|
|
53
|
+
},
|
|
54
|
+
module: {
|
|
55
|
+
rules: [
|
|
56
|
+
{
|
|
57
|
+
// Transpile TypeScript with Rspack's built-in SWC (no type checking).
|
|
58
|
+
test: /\.ts$/,
|
|
59
|
+
exclude: /node_modules/,
|
|
60
|
+
loader: "builtin:swc-loader",
|
|
61
|
+
options: { jsc: { parser: { syntax: "typescript" } } },
|
|
62
|
+
type: "javascript/auto",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
// Native CSS; *.module.css files become CSS modules.
|
|
66
|
+
test: /\.css$/,
|
|
67
|
+
type: "css/auto",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
53
71
|
devServer: {
|
|
54
72
|
host,
|
|
55
73
|
port,
|
|
56
74
|
hot: true,
|
|
57
75
|
// Keep app/assets/builds current while the dev server runs, so Propshaft
|
|
58
|
-
// (and `stylesheet_link_tag :app`) never serve a stale bundle.
|
|
59
|
-
|
|
76
|
+
// (and `stylesheet_link_tag :app`) never serve a stale bundle. Hot update
|
|
77
|
+
// files stay in memory so they never reach public/assets.
|
|
78
|
+
devMiddleware: { writeToDisk: (file) => !/\.hot-update\./.test(file) },
|
|
60
79
|
allowedHosts: "all",
|
|
61
80
|
headers: { "Access-Control-Allow-Origin": "*" },
|
|
62
81
|
client: {
|
data/lib/install/stimulus.rb
CHANGED
|
@@ -16,9 +16,9 @@ end
|
|
|
16
16
|
|
|
17
17
|
if !File.exist?(entry)
|
|
18
18
|
create_file entry, %(import "./controllers/autoload"\n)
|
|
19
|
-
elsif File.read(entry).match?(%r{^import ["']
|
|
20
|
-
say "Replacing the controllers
|
|
21
|
-
gsub_file entry, %r{^import ["']
|
|
19
|
+
elsif File.read(entry).match?(%r{^import ["'](?:\./)?controllers(/index(\.js)?)?["'];?$})
|
|
20
|
+
say "Replacing the controllers import with controllers/autoload"
|
|
21
|
+
gsub_file entry, %r{^import ["'](?:\./)?controllers(/index(\.js)?)?["'];?$}, %(import "./controllers/autoload")
|
|
22
22
|
elsif !File.read(entry).include?("./controllers/autoload")
|
|
23
23
|
append_to_file entry, %(import "./controllers/autoload"\n)
|
|
24
24
|
end
|
|
@@ -28,6 +28,6 @@ if File.exist?("app/javascript/controllers/index.js")
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
say "Installing @hotwired/stimulus"
|
|
31
|
-
run pm.add_command("@hotwired/stimulus")
|
|
31
|
+
run pm.add_command("@hotwired/stimulus"), abort_on_failure: true
|
|
32
32
|
|
|
33
33
|
say "Stimulus installed. Controllers in app/javascript/controllers are registered automatically.", :green
|
data/lib/install/typescript.rb
CHANGED
|
@@ -14,6 +14,6 @@ package = JSON.parse(File.read("package.json"))
|
|
|
14
14
|
File.write("package.json", "#{JSON.pretty_generate(package)}\n")
|
|
15
15
|
|
|
16
16
|
say "Installing typescript"
|
|
17
|
-
run pm.add_dev_command("typescript")
|
|
17
|
+
run pm.add_dev_command("typescript"), abort_on_failure: true
|
|
18
18
|
|
|
19
19
|
say "TypeScript ready. Rspack compiles .ts files; run `#{pm.run_command("typecheck")}` to type-check.", :green
|
data/lib/rspack_rails/doctor.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "open3"
|
|
5
5
|
require "pathname"
|
|
6
6
|
require_relative "node_version"
|
|
7
|
+
require_relative "entrypoints"
|
|
7
8
|
|
|
8
9
|
module RspackRails
|
|
9
10
|
# Inspects a Rails app's rspack setup and reports what is missing.
|
|
@@ -91,17 +92,17 @@ module RspackRails
|
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
def entry_check
|
|
94
|
-
return Check.new(:ok, "Entry
|
|
95
|
+
return Check.new(:ok, "Entry points", entrypoints.keys.join(", ")) if entrypoints.any?
|
|
95
96
|
|
|
96
|
-
Check.new(:error, "Entry
|
|
97
|
-
"Create
|
|
97
|
+
Check.new(:error, "Entry points", "none found",
|
|
98
|
+
"Create app/javascript/application.js or a file in app/javascript/entrypoints/")
|
|
98
99
|
end
|
|
99
100
|
|
|
100
101
|
def build_check
|
|
101
|
-
|
|
102
|
-
return Check.new(:ok, "Build output", "app/assets/builds
|
|
102
|
+
missing = entrypoints.keys.reject { |name| exist?("app/assets/builds/#{name}.js") }
|
|
103
|
+
return Check.new(:ok, "Build output", "app/assets/builds has #{entrypoints.keys.join(", ")}") if missing.empty?
|
|
103
104
|
|
|
104
|
-
Check.new(:warn, "Build output", "
|
|
105
|
+
Check.new(:warn, "Build output", "not built yet: #{missing.join(", ")}",
|
|
105
106
|
"Run `bin/rails rspack:build` (only needed without the dev server)")
|
|
106
107
|
end
|
|
107
108
|
|
|
@@ -186,6 +187,10 @@ module RspackRails
|
|
|
186
187
|
nil
|
|
187
188
|
end
|
|
188
189
|
|
|
190
|
+
def entrypoints
|
|
191
|
+
@entrypoints ||= Entrypoints.discover(@root)
|
|
192
|
+
end
|
|
193
|
+
|
|
189
194
|
def package_manager
|
|
190
195
|
@package_manager ||= PackageManager.detect(@root)
|
|
191
196
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module RspackRails
|
|
6
|
+
# Mirrors the entry discovery in the generated rspack.config.mjs:
|
|
7
|
+
# app/javascript/application.{js,ts} plus every
|
|
8
|
+
# app/javascript/entrypoints/*.{js,ts}, named after the file.
|
|
9
|
+
module Entrypoints
|
|
10
|
+
EXTENSIONS = %w[js ts].freeze
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# => { "application" => "app/javascript/application.js", "admin" => "app/javascript/entrypoints/admin.js" }
|
|
15
|
+
def discover(root)
|
|
16
|
+
root = Pathname(root)
|
|
17
|
+
entries = {}
|
|
18
|
+
main = EXTENSIONS.map { |ext| "app/javascript/application.#{ext}" }.find { |path| root.join(path).file? }
|
|
19
|
+
entries["application"] = main if main
|
|
20
|
+
|
|
21
|
+
Dir.glob("app/javascript/entrypoints/*.{#{EXTENSIONS.join(",")}}", base: root).sort.each do |path|
|
|
22
|
+
entries[File.basename(path, ".*")] = path
|
|
23
|
+
end
|
|
24
|
+
entries
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module RspackRails
|
|
7
|
+
# Detects how an app bundles JavaScript today and works out what
|
|
8
|
+
# `rspack:migrate` has to change. Pure inspection: nothing here writes files.
|
|
9
|
+
class Migration
|
|
10
|
+
Pin = Struct.new(:name, :to, :version, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
# importmap-only helpers with no npm equivalent.
|
|
13
|
+
IMPORTMAP_ONLY = %w[@hotwired/stimulus-loading].freeze
|
|
14
|
+
|
|
15
|
+
BUNDLER_PACKAGES = %w[esbuild webpack webpack-cli rollup @rollup/plugin-node-resolve
|
|
16
|
+
@rollup/plugin-commonjs @rollup/plugin-terser].freeze
|
|
17
|
+
BUNDLER_CONFIGS = %w[webpack.config.js rollup.config.js rollup.config.mjs].freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :root
|
|
20
|
+
|
|
21
|
+
def initialize(root)
|
|
22
|
+
@root = Pathname(root)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# :rspack, :shakapacker, :importmap, :jsbundling or :none
|
|
26
|
+
def source
|
|
27
|
+
@source ||= if file?("rspack.config.mjs") then :rspack
|
|
28
|
+
elsif file?("config/shakapacker.yml") || file?("config/webpacker.yml") then :shakapacker
|
|
29
|
+
elsif file?("config/importmap.rb") then :importmap
|
|
30
|
+
elsif jsbundling? then :jsbundling
|
|
31
|
+
else
|
|
32
|
+
:none
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def pins
|
|
37
|
+
@pins ||= parse_pins(read("config/importmap.rb").to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# { "package" => "version or nil" } for every pin that comes from npm. The version is
|
|
41
|
+
# known only when importmap recorded it (vendored "# @1.2.3" pins or jspm URLs); gem-served
|
|
42
|
+
# pins such as turbo-rails install the latest npm release.
|
|
43
|
+
def npm_packages
|
|
44
|
+
pins.reject { |pin| local_pin?(pin) || IMPORTMAP_ONLY.include?(pin.name) }
|
|
45
|
+
.to_h { |pin| [package_name(pin.name), pin.version] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The argument list for `npm install` / `yarn add`, versions pinned when known.
|
|
49
|
+
def npm_install_args
|
|
50
|
+
npm_packages.map { |name, version| version ? "#{name}@#{version}" : name }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Vendored copies of packages that now come from npm.
|
|
54
|
+
def vendored_files
|
|
55
|
+
pins.reject { |pin| local_pin?(pin) }.filter_map do |pin|
|
|
56
|
+
path = "vendor/javascript/#{pin.to || "#{pin.name}.js"}"
|
|
57
|
+
path if file?(path)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def bundler_packages
|
|
62
|
+
dev = package_json.fetch("devDependencies", {}).keys + package_json.fetch("dependencies", {}).keys
|
|
63
|
+
dev & BUNDLER_PACKAGES
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def bundler_configs
|
|
67
|
+
BUNDLER_CONFIGS.select { |path| file?(path) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# jsbundling's esbuild default builds every file in app/javascript; rspack
|
|
71
|
+
# only builds application.* and entrypoints/*.
|
|
72
|
+
def extra_root_entries
|
|
73
|
+
Dir.glob("app/javascript/*.{js,ts}", base: root).sort.grep_v(%r{/application\.[jt]s\z})
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def parse_pins(source)
|
|
77
|
+
source.each_line.filter_map do |line|
|
|
78
|
+
match = line.match(/^\s*pin\s+["']([^"']+)["'](.*)$/)
|
|
79
|
+
next unless match
|
|
80
|
+
|
|
81
|
+
rest = match[2]
|
|
82
|
+
to = rest[/to:\s*["']([^"']+)["']/, 1]
|
|
83
|
+
version = rest[/#\s*@(\S+)/, 1] || to.to_s[%r{npm:(?:@[^/]+/)?[^@/]+@([^/]+)/}, 1]
|
|
84
|
+
Pin.new(name: match[1], to: to, version: version)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def jsbundling?
|
|
91
|
+
read("Gemfile").to_s.match?(/^\s*gem\s+["']jsbundling-rails["']/) ||
|
|
92
|
+
package_json.dig("scripts", "build").to_s.match?(/\b(esbuild|webpack|rollup|bun build)\b/)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A pin is app code when it, or the file it points to, lives in app/javascript.
|
|
96
|
+
def local_pin?(pin)
|
|
97
|
+
return true if pin.name == "application"
|
|
98
|
+
|
|
99
|
+
target = pin.to || "#{pin.name}.js"
|
|
100
|
+
return false if target.match?(%r{\Ahttps?://})
|
|
101
|
+
|
|
102
|
+
file?("app/javascript/#{target}")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# "@rails/actioncable/src" -> "@rails/actioncable", "lodash/debounce" -> "lodash"
|
|
106
|
+
def package_name(specifier)
|
|
107
|
+
parts = specifier.split("/")
|
|
108
|
+
specifier.start_with?("@") ? parts.first(2).join("/") : parts.first
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def package_json
|
|
112
|
+
@package_json ||= file?("package.json") ? JSON.parse(read("package.json")) : {}
|
|
113
|
+
rescue JSON::ParserError
|
|
114
|
+
@package_json = {}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def file?(path)
|
|
118
|
+
root.join(path).file?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def read(path)
|
|
122
|
+
root.join(path).read if file?(path)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
data/lib/rspack_rails/version.rb
CHANGED
data/lib/rspack_rails.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative "rspack_rails/version"
|
|
|
4
4
|
require_relative "rspack_rails/package_manager"
|
|
5
5
|
require_relative "rspack_rails/configuration"
|
|
6
6
|
require_relative "rspack_rails/dev_server"
|
|
7
|
+
require_relative "rspack_rails/entrypoints"
|
|
8
|
+
require_relative "rspack_rails/migration"
|
|
7
9
|
|
|
8
10
|
# Rspack integration for Rails: rake tasks, installer and view helpers.
|
|
9
11
|
module RspackRails
|
data/lib/tasks/rspack_rails.rake
CHANGED
|
@@ -24,6 +24,20 @@ namespace :rspack do
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
desc "Migrate from importmap-rails or jsbundling-rails to rspack (FORCE=1 to skip the clean git check)"
|
|
28
|
+
task :migrate do
|
|
29
|
+
dirty = system("git", "rev-parse", "--is-inside-work-tree", out: File::NULL, err: File::NULL) &&
|
|
30
|
+
!`git status --porcelain`.strip.empty?
|
|
31
|
+
if dirty && ENV["FORCE"].to_s.empty?
|
|
32
|
+
raise RspackRails::Error,
|
|
33
|
+
"rspack:migrate removes and rewrites files. Commit or stash your changes first, or run with FORCE=1."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
template = File.expand_path("../install/migrate.rb", __dir__)
|
|
37
|
+
system(RbConfig.ruby, "./bin/rails", "app:template", "LOCATION=#{template}") ||
|
|
38
|
+
raise(RspackRails::Error, "rspack:migrate failed")
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
desc "Check the rspack setup of this app"
|
|
28
42
|
task doctor: :environment do
|
|
29
43
|
require "rspack_rails/doctor"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspack_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tungnt
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- lib/install/application.js
|
|
46
46
|
- lib/install/dev
|
|
47
47
|
- lib/install/install.rb
|
|
48
|
+
- lib/install/migrate.rb
|
|
48
49
|
- lib/install/package.json
|
|
49
50
|
- lib/install/rspack.config.mjs
|
|
50
51
|
- lib/install/stimulus.rb
|
|
@@ -57,7 +58,9 @@ files:
|
|
|
57
58
|
- lib/rspack_rails/configuration.rb
|
|
58
59
|
- lib/rspack_rails/dev_server.rb
|
|
59
60
|
- lib/rspack_rails/doctor.rb
|
|
61
|
+
- lib/rspack_rails/entrypoints.rb
|
|
60
62
|
- lib/rspack_rails/helper.rb
|
|
63
|
+
- lib/rspack_rails/migration.rb
|
|
61
64
|
- lib/rspack_rails/node_version.rb
|
|
62
65
|
- lib/rspack_rails/package_manager.rb
|
|
63
66
|
- lib/rspack_rails/railtie.rb
|