react-manifest-rails 0.2.8 → 0.2.9
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 +15 -0
- data/README.md +133 -92
- data/lib/react_manifest/layout_patcher.rb +121 -0
- data/lib/react_manifest/sprockets_manifest_patcher.rb +96 -0
- data/lib/react_manifest/version.rb +1 -1
- data/lib/react_manifest.rb +2 -0
- data/tasks/react_manifest.rake +93 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a26dc7e8929f42fed07d13c8d38d3390d99d8063c81e5887ac171e52c1d9303
|
|
4
|
+
data.tar.gz: defd50858d68d0e22fdd396d6be214af15edb6eb6c5c030c1047c17f1f2ab0e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d94c1392eaaa325b56225b618f318d0af156c4696dc806a2ba238796c10c75f128c4a7369475a455d1e6f851edd8ca66a5a10e86ed246a0d15917ba00a198bab
|
|
7
|
+
data.tar.gz: 86ddf03428eb1889ea9bcabd463fa761bc5738a724e201197323228ef067cd1d400f85da40d273a69cc06bf7c8016a9bba732ddd36e6c56147d980d14bda74ce
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.9] - 2026-04-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `rails react_manifest:setup` — one-command onboarding that patches `application.js`, `manifest.js`, and layout files, then generates initial bundles. Supports `DRY_RUN=1` preview mode.
|
|
12
|
+
- `ReactManifest::LayoutPatcher` — automatically inserts `react_bundle_tag` into ERB, HAML, and Slim layouts after `javascript_include_tag`, with `</head>` fallback. Preserves indentation and is idempotent.
|
|
13
|
+
- `ReactManifest::SprocketsManifestPatcher` — adds the `//= link_tree ../javascripts/ux_manifests .js` directive to `app/assets/config/manifest.js` so Sprockets 4 compiles the generated bundles.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Removed a `Proc` from `config.assets.precompile` that crashed Sprockets 4.2+ with `NoMethodError: undefined method 'start_with?' for an instance of Proc`. The `link_tree` directive (handled by `SprocketsManifestPatcher`) is the correct mechanism.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `react_manifest:generate` now warns if the Sprockets manifest has not yet been patched.
|
|
20
|
+
- README rewritten with a streamlined Quick Start centred on `react_manifest:setup`.
|
|
21
|
+
- Confirmed full production asset pipeline compatibility: all ux bundles compile, minify, digest, and gzip correctly via `assets:precompile` with any standard JS compressor (uglifier/mini_racer, terser, libv8).
|
|
22
|
+
|
|
8
23
|
## [0.2.8] - 2026-04-15
|
|
9
24
|
|
|
10
25
|
### Fixed
|
data/README.md
CHANGED
|
@@ -2,144 +2,183 @@
|
|
|
2
2
|
|
|
3
3
|
Generate per-controller Sprockets manifests for React code in Rails.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Instead of one monolithic `application.js`, each controller gets a lean `ux_<controller>.js` bundle that only loads what it needs. Shared components, hooks, and libraries are automatically bundled into a single `ux_shared.js` included on every page.
|
|
6
6
|
|
|
7
|
-
## Quick Start
|
|
7
|
+
## Quick Start
|
|
8
8
|
|
|
9
|
-
1. Add
|
|
9
|
+
### 1. Add to Gemfile
|
|
10
10
|
|
|
11
11
|
```ruby
|
|
12
|
-
# Gemfile
|
|
13
12
|
gem "react-manifest-rails"
|
|
14
13
|
|
|
15
14
|
group :development do
|
|
16
|
-
gem "listen", "~> 3.0"
|
|
15
|
+
gem "listen", "~> 3.0" # optional — enables auto-regeneration on file change
|
|
17
16
|
end
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
2. Install:
|
|
21
|
-
|
|
22
19
|
```bash
|
|
23
20
|
bundle install
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```text
|
|
29
|
-
ux_root: app/assets/javascripts/ux
|
|
30
|
-
app_dir: app
|
|
31
|
-
output_dir: app/assets/javascripts
|
|
32
|
-
extensions: js, jsx
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
3. Add initializer:
|
|
23
|
+
### 2. Add initializer
|
|
36
24
|
|
|
37
25
|
```ruby
|
|
38
26
|
# config/initializers/react_manifest.rb
|
|
39
|
-
require "react_manifest"
|
|
40
|
-
|
|
41
27
|
ReactManifest.configure do |config|
|
|
42
|
-
|
|
43
|
-
config.
|
|
28
|
+
# All defaults shown — only override what you need to change.
|
|
29
|
+
config.ux_root = "app/assets/javascripts/ux"
|
|
30
|
+
config.app_dir = "app"
|
|
44
31
|
config.output_dir = "app/assets/javascripts"
|
|
45
|
-
config.manifest_subdir = "ux_manifests"
|
|
46
|
-
config.shared_bundle = "ux_shared"
|
|
47
|
-
config.always_include = []
|
|
48
|
-
config.ignore = []
|
|
49
|
-
config.exclude_paths = ["react", "react_dev", "vendor"]
|
|
50
|
-
config.size_threshold_kb = 500
|
|
51
|
-
config.dry_run = false
|
|
52
|
-
config.verbose = false
|
|
53
|
-
config.stdout_logging = true
|
|
54
32
|
end
|
|
55
33
|
```
|
|
56
34
|
|
|
57
|
-
|
|
35
|
+
### 3. Run setup
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bundle exec rails react_manifest:setup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This one command:
|
|
42
|
+
- Patches `application.js` to remove globally-required React files (they'll be per-controller instead)
|
|
43
|
+
- Patches `app/assets/config/manifest.js` to add the `link_tree` directive Sprockets needs to compile the bundles
|
|
44
|
+
- Patches your layout(s) to insert `react_bundle_tag`
|
|
45
|
+
- Generates the initial `ux_*.js` manifests
|
|
46
|
+
|
|
47
|
+
Preview changes without writing anything:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
DRY_RUN=1 bundle exec rails react_manifest:setup
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. Organise your React files
|
|
58
54
|
|
|
59
55
|
```text
|
|
60
56
|
app/assets/javascripts/ux/
|
|
61
57
|
app/
|
|
62
|
-
users/
|
|
58
|
+
users/ → compiled into ux_users.js
|
|
63
59
|
users_index.jsx
|
|
64
|
-
|
|
60
|
+
dashboard/ → compiled into ux_dashboard.js
|
|
61
|
+
dashboard.jsx
|
|
62
|
+
components/ → compiled into ux_shared.js (included on every page)
|
|
65
63
|
nav.jsx
|
|
64
|
+
button.jsx
|
|
65
|
+
hooks/ → also goes into ux_shared.js
|
|
66
|
+
use_modal.js
|
|
67
|
+
lib/ → also goes into ux_shared.js
|
|
68
|
+
helpers.js
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
5.
|
|
69
|
-
|
|
70
|
-
```erb
|
|
71
|
-
<%= react_bundle_tag defer: true %>
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
6. Start server:
|
|
71
|
+
### 5. Start the server
|
|
75
72
|
|
|
76
73
|
```bash
|
|
77
74
|
bundle exec rails s
|
|
78
75
|
```
|
|
79
76
|
|
|
80
77
|
In development:
|
|
81
|
-
-
|
|
82
|
-
- If `listen` is installed, file
|
|
83
|
-
-
|
|
84
|
-
- Set `config.stdout_logging = false` to silence ReactManifest console lines while keeping Rails logger output.
|
|
78
|
+
- Missing `ux_*.js` files are generated automatically on boot.
|
|
79
|
+
- If `listen` is installed, saving any file under `ux/` regenerates affected manifests instantly.
|
|
80
|
+
- Without `listen`, run `bundle exec rails react_manifest:generate` after adding files.
|
|
85
81
|
|
|
86
|
-
##
|
|
82
|
+
## How Bundle Inclusion Works
|
|
87
83
|
|
|
88
|
-
-
|
|
89
|
-
- Skips controller directory names directly under `ux/app/`.
|
|
90
|
-
- Example: `ignore = ["admin"]` skips `ux/app/admin/*`.
|
|
84
|
+
Generation is **directory-based** — deterministic and conservative by design.
|
|
91
85
|
|
|
92
|
-
- `
|
|
93
|
-
|
|
94
|
-
- Example: `exclude_paths = ["vendor"]` excludes `ux/vendor/*` and any nested `.../vendor/...` segment.
|
|
95
|
-
- This is not based on what is included in `application.js`.
|
|
86
|
+
- `ux_shared.js`: every file from directories outside `ux/app/` (i.e. `components/`, `hooks/`, `lib/`, etc.)
|
|
87
|
+
- `ux_<controller>.js`: `ux_shared` + every file under `ux/app/<controller>/`
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
- Preview mode only; computes and prints changes but writes no files.
|
|
89
|
+
Namespace fallback for nested controllers: `admin/reports/summary` tries `ux_admin_reports_summary`, then `ux_admin_reports`, then `ux_admin`, then `ux_summary`. The most specific match wins.
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
- `verbose`: extra diagnostic detail.
|
|
102
|
-
- `stdout_logging`: whether ReactManifest status lines are printed to terminal stdout.
|
|
103
|
-
|
|
104
|
-
- Scope:
|
|
105
|
-
- Manifest generation only scans files under `ux_root`.
|
|
91
|
+
The gem's scanner uses regex to detect which shared symbols are referenced in each controller directory (for the `react_manifest:analyze` report). Generation itself stays directory-based to avoid brittle runtime misses from dynamic component references.
|
|
106
92
|
|
|
107
93
|
## What Gets Generated
|
|
108
94
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
```
|
|
96
|
+
app/assets/javascripts/ux_manifests/ ← generated; do not edit
|
|
97
|
+
ux_shared.js
|
|
98
|
+
ux_users.js
|
|
99
|
+
ux_dashboard.js
|
|
100
|
+
...
|
|
101
|
+
```
|
|
113
102
|
|
|
114
|
-
|
|
115
|
-
- `ux/app/users/...` -> `ux_users.js`
|
|
116
|
-
- `ux/app/admin/...` -> `ux_admin.js`
|
|
103
|
+
Files carry an `AUTO-GENERATED` header. Any file without it is never overwritten — you can pin a manifest by removing the header.
|
|
117
104
|
|
|
118
|
-
|
|
105
|
+
Writes are atomic (temp file + rename) and idempotent (SHA-256 comparison skips unchanged files).
|
|
119
106
|
|
|
120
|
-
|
|
107
|
+
## Asset Compilation & Minification
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
bundle exec rails react_manifest:generate
|
|
124
|
-
```
|
|
109
|
+
The generated files are standard Sprockets manifests — `//= require` directives only. Sprockets processes them identically to `application.js`:
|
|
125
110
|
|
|
126
|
-
-
|
|
111
|
+
- **Development**: concatenated and served from memory.
|
|
112
|
+
- **Production** (`assets:precompile`): concatenated, minified (with whatever JS compressor your app uses — `uglifier`/`mini_racer`, `terser`, libv8, etc.), digested, and gzipped.
|
|
127
113
|
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
The gem hooks into `assets:precompile` as a prerequisite, so manifest generation always runs before Sprockets begins compiling.
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
ReactManifest.configure do |config|
|
|
120
|
+
config.ux_root = "app/assets/javascripts/ux"
|
|
121
|
+
config.app_dir = "app" # subdirectory of ux_root containing per-controller dirs
|
|
122
|
+
config.output_dir = "app/assets/javascripts"
|
|
123
|
+
config.manifest_subdir = "ux_manifests" # subdirectory of output_dir for generated files
|
|
124
|
+
config.shared_bundle = "ux_shared"
|
|
125
|
+
config.extensions = %w[js jsx] # add ts tsx for TypeScript
|
|
126
|
+
config.always_include = [] # extra shared files always added to every bundle
|
|
127
|
+
config.ignore = [] # controller dir names to skip entirely
|
|
128
|
+
config.exclude_paths = ["react", "react_dev", "vendor"] # path segments to exclude
|
|
129
|
+
config.size_threshold_kb = 500 # warn if a bundle exceeds this
|
|
130
|
+
config.dry_run = false # never write; only print what would change
|
|
131
|
+
config.verbose = false # extra diagnostic detail
|
|
132
|
+
config.stdout_logging = true # print status lines to terminal
|
|
133
|
+
end
|
|
130
134
|
```
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
### Key option notes
|
|
137
|
+
|
|
138
|
+
- **`ignore`**: skips entire controller dirs under `ux/app/`. `ignore = ["admin"]` excludes `ux/app/admin/`.
|
|
139
|
+
- **`exclude_paths`**: excludes files whose path contains any listed segment. Not based on `application.js`.
|
|
140
|
+
- **`dry_run`**: also honoured by `DRY_RUN=1` environment variable at runtime.
|
|
141
|
+
- **`extensions`**: add `ts` and `tsx` to enable TypeScript source detection.
|
|
142
|
+
|
|
143
|
+
## Commands
|
|
133
144
|
|
|
134
145
|
```bash
|
|
146
|
+
# First-time setup (patches application.js, manifest.js, layouts; generates manifests)
|
|
147
|
+
bundle exec rails react_manifest:setup
|
|
148
|
+
|
|
149
|
+
# Regenerate all manifests
|
|
150
|
+
bundle exec rails react_manifest:generate
|
|
151
|
+
|
|
152
|
+
# Preview any command without writing
|
|
153
|
+
DRY_RUN=1 bundle exec rails react_manifest:setup
|
|
154
|
+
DRY_RUN=1 bundle exec rails react_manifest:generate
|
|
155
|
+
|
|
156
|
+
# Analyse which shared symbols are used per controller
|
|
157
|
+
bundle exec rails react_manifest:analyze
|
|
158
|
+
|
|
159
|
+
# Print bundle size report
|
|
135
160
|
bundle exec rails react_manifest:report
|
|
161
|
+
|
|
162
|
+
# Watch for changes in foreground (debugging only — dev server already does this)
|
|
163
|
+
bundle exec rails react_manifest:watch
|
|
164
|
+
|
|
165
|
+
# Remove all generated manifests
|
|
166
|
+
bundle exec rails react_manifest:clean
|
|
136
167
|
```
|
|
137
168
|
|
|
138
169
|
## Troubleshooting
|
|
139
170
|
|
|
140
|
-
### `
|
|
171
|
+
### `AssetNotPrecompiledError` for `ux_*.js`
|
|
172
|
+
|
|
173
|
+
Sprockets 4 requires an explicit `link_tree` directive to compile files from non-standard paths. Run setup (or manually add the directive):
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bundle exec rails react_manifest:setup
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This adds `//= link_tree ../javascripts/ux_manifests .js` to `app/assets/config/manifest.js`.
|
|
141
180
|
|
|
142
|
-
|
|
181
|
+
### `react_manifest` tasks not found
|
|
143
182
|
|
|
144
183
|
```bash
|
|
145
184
|
bundle exec rails -T | grep react_manifest
|
|
@@ -147,28 +186,30 @@ bundle exec rails -T | grep react_manifest
|
|
|
147
186
|
|
|
148
187
|
If nothing appears:
|
|
149
188
|
- Confirm the gem is in `Gemfile` and installed (`bundle show react-manifest-rails`).
|
|
150
|
-
- Ensure
|
|
151
|
-
- Restart Spring
|
|
189
|
+
- Ensure it is not loaded with `require: false`.
|
|
190
|
+
- Restart Spring: `bin/spring stop`.
|
|
152
191
|
|
|
153
|
-
### Server starts but
|
|
192
|
+
### Server starts but no bundles are served
|
|
154
193
|
|
|
155
|
-
Check
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
Check in order:
|
|
195
|
+
1. `app/assets/javascripts/ux/` exists.
|
|
196
|
+
2. Controller files are under `ux/app/<controller>/`.
|
|
197
|
+
3. Your layout includes `<%= react_bundle_tag %>`.
|
|
198
|
+
4. Run `bundle exec rails react_manifest:generate` and confirm files appear in `ux_manifests/`.
|
|
199
|
+
5. Confirm `app/assets/config/manifest.js` contains the `link_tree` directive (run setup again if missing).
|
|
160
200
|
|
|
161
|
-
### Auto-watch
|
|
201
|
+
### Auto-watch not running in development
|
|
162
202
|
|
|
163
|
-
-
|
|
164
|
-
- Restart the Rails server
|
|
165
|
-
-
|
|
203
|
+
- Add `listen` to the development group in your Gemfile and `bundle install`.
|
|
204
|
+
- Restart the Rails server.
|
|
205
|
+
- Without `listen`, run `react_manifest:generate` manually after making changes.
|
|
166
206
|
|
|
167
207
|
## Compatibility
|
|
168
208
|
|
|
169
209
|
- Ruby: 3.2+
|
|
170
|
-
- Rails
|
|
171
|
-
- Asset pipeline: Sprockets
|
|
210
|
+
- Rails: 7.x – 8.x
|
|
211
|
+
- Asset pipeline: Sprockets 3 and 4
|
|
212
|
+
- JS compressors: uglifier / mini_racer, terser, libv8 / therubyracer — all work transparently
|
|
172
213
|
|
|
173
214
|
## License
|
|
174
215
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module ReactManifest
|
|
2
|
+
# Patches Rails layout files to insert `react_bundle_tag` after the
|
|
3
|
+
# `javascript_include_tag` call, or before `</head>` as a fallback.
|
|
4
|
+
#
|
|
5
|
+
# Supports .html.erb, .html.haml, and .html.slim.
|
|
6
|
+
# Skips files that already contain `react_bundle_tag`.
|
|
7
|
+
# Atomic write: backs up original before writing.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ReactManifest::LayoutPatcher.new(config).patch!
|
|
11
|
+
class LayoutPatcher
|
|
12
|
+
LAYOUTS_GLOB = "app/views/layouts/*.html.{erb,haml,slim}".freeze
|
|
13
|
+
BUNDLE_TAG_ERB = "<%= react_bundle_tag defer: true %>\n".freeze
|
|
14
|
+
BUNDLE_TAG_HAML = "= react_bundle_tag defer: true\n".freeze
|
|
15
|
+
|
|
16
|
+
Result = Struct.new(:file, :status, :detail, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
def initialize(config = ReactManifest.configuration)
|
|
19
|
+
@config = config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Patch all layout files. Returns array of Result objects.
|
|
23
|
+
def patch!
|
|
24
|
+
layouts = find_layouts
|
|
25
|
+
if layouts.empty?
|
|
26
|
+
$stdout.puts "[ReactManifest] No layout files found in #{layouts_dir}"
|
|
27
|
+
return []
|
|
28
|
+
end
|
|
29
|
+
layouts.map { |f| patch_file(f) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def find_layouts
|
|
35
|
+
Dir.glob(File.join(layouts_dir, "*.html.{erb,haml,slim}"))
|
|
36
|
+
.reject { |f| File.directory?(f) }
|
|
37
|
+
.sort
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def layouts_dir
|
|
41
|
+
Rails.root.join("app", "views", "layouts").to_s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def patch_file(path)
|
|
45
|
+
content = File.read(path, encoding: "utf-8")
|
|
46
|
+
|
|
47
|
+
if content.include?("react_bundle_tag")
|
|
48
|
+
return Result.new(file: path, status: :already_patched,
|
|
49
|
+
detail: "react_bundle_tag already present")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
ext = detect_ext(path)
|
|
53
|
+
new_content = insert_bundle_tag(content, ext)
|
|
54
|
+
|
|
55
|
+
if new_content.nil?
|
|
56
|
+
return Result.new(file: path, status: :no_injection_point,
|
|
57
|
+
detail: "Could not find javascript_include_tag or </head> — add react_bundle_tag manually")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if @config.dry_run?
|
|
61
|
+
$stdout.puts "[ReactManifest] DRY-RUN: would patch #{short(path)}"
|
|
62
|
+
print_diff(content, new_content)
|
|
63
|
+
return Result.new(file: path, status: :dry_run, detail: nil)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
File.write(path, new_content, encoding: "utf-8")
|
|
67
|
+
$stdout.puts "[ReactManifest] Patched layout: #{short(path)}"
|
|
68
|
+
Result.new(file: path, status: :patched, detail: nil)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Insert the bundle tag line after `javascript_include_tag` (preferred),
|
|
72
|
+
# or before `</head>` / `%head` close as a fallback.
|
|
73
|
+
def insert_bundle_tag(content, ext)
|
|
74
|
+
lines = content.lines
|
|
75
|
+
|
|
76
|
+
# Preferred: insert after javascript_include_tag
|
|
77
|
+
js_idx = lines.index { |l| l.include?("javascript_include_tag") }
|
|
78
|
+
|
|
79
|
+
# Fallback: insert before closing </head>
|
|
80
|
+
head_idx = lines.rindex { |l| l.include?("</head>") } unless js_idx
|
|
81
|
+
|
|
82
|
+
insert_after = js_idx || (head_idx ? head_idx - 1 : nil)
|
|
83
|
+
return nil if insert_after.nil?
|
|
84
|
+
|
|
85
|
+
# Match indentation of the reference line
|
|
86
|
+
indent = lines[js_idx || head_idx][/^\s*/]
|
|
87
|
+
tag = bundle_tag_line(ext, indent)
|
|
88
|
+
|
|
89
|
+
new_lines = lines.dup
|
|
90
|
+
new_lines.insert(insert_after + 1, tag)
|
|
91
|
+
new_lines.join
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def bundle_tag_line(ext, indent)
|
|
95
|
+
case ext
|
|
96
|
+
when :haml, :slim
|
|
97
|
+
"#{indent}#{BUNDLE_TAG_HAML}"
|
|
98
|
+
else
|
|
99
|
+
"#{indent}#{BUNDLE_TAG_ERB}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def detect_ext(path)
|
|
104
|
+
case path
|
|
105
|
+
when /\.html\.haml$/ then :haml
|
|
106
|
+
when /\.html\.slim$/ then :slim
|
|
107
|
+
else :erb
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def short(path)
|
|
112
|
+
path.to_s.sub("#{Rails.root}/", "")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def print_diff(old_content, new_content)
|
|
116
|
+
old_lines = old_content.lines.map(&:chomp)
|
|
117
|
+
new_lines = new_content.lines.map(&:chomp)
|
|
118
|
+
(new_lines - old_lines).each { |l| $stdout.puts " + #{l}" }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module ReactManifest
|
|
2
|
+
# Patches the Sprockets asset manifest (app/assets/config/manifest.js) to add a
|
|
3
|
+
# `link_tree` directive for the ux_manifests directory.
|
|
4
|
+
#
|
|
5
|
+
# This is required so Sprockets knows to compile and serve the generated ux_*.js
|
|
6
|
+
# bundle files. Without it, javascript_include_tag raises AssetNotPrecompiledError.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ReactManifest::SprocketsManifestPatcher.new(config).patch!
|
|
10
|
+
class SprocketsManifestPatcher
|
|
11
|
+
MANIFEST_GLOB = "app/assets/config/manifest.js".freeze
|
|
12
|
+
|
|
13
|
+
Result = Struct.new(:file, :status, :detail, keyword_init: true)
|
|
14
|
+
|
|
15
|
+
def initialize(config = ReactManifest.configuration)
|
|
16
|
+
@config = config
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Patch the Sprockets manifest file. Returns a Result.
|
|
20
|
+
def patch!
|
|
21
|
+
path = manifest_path
|
|
22
|
+
unless path
|
|
23
|
+
return Result.new(
|
|
24
|
+
file: nil,
|
|
25
|
+
status: :not_found,
|
|
26
|
+
detail: "#{MANIFEST_GLOB} not found — create it and add //= link_tree ../javascripts/ux_manifests .js"
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
content = File.read(path, encoding: "utf-8")
|
|
31
|
+
directive = link_tree_directive
|
|
32
|
+
|
|
33
|
+
if content.include?(directive.strip)
|
|
34
|
+
return Result.new(file: path, status: :already_patched, detail: "link_tree directive already present")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
new_content = append_directive(content, directive)
|
|
38
|
+
|
|
39
|
+
if @config.dry_run?
|
|
40
|
+
$stdout.puts "[ReactManifest] DRY-RUN: would patch #{short(path)}"
|
|
41
|
+
$stdout.puts " + #{directive.strip}"
|
|
42
|
+
return Result.new(file: path, status: :dry_run, detail: nil)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
File.write(path, new_content, encoding: "utf-8")
|
|
46
|
+
$stdout.puts "[ReactManifest] Patched Sprockets manifest: #{short(path)}"
|
|
47
|
+
Result.new(file: path, status: :patched, detail: nil)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns the directive string that should be present.
|
|
51
|
+
def link_tree_directive
|
|
52
|
+
subdir = normalize_subdir
|
|
53
|
+
"//= link_tree ../javascripts/#{subdir} .js\n"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns true if the manifest file already contains the link_tree directive.
|
|
57
|
+
def already_patched?
|
|
58
|
+
path = manifest_path
|
|
59
|
+
return false unless path
|
|
60
|
+
|
|
61
|
+
File.read(path, encoding: "utf-8").include?(link_tree_directive.strip)
|
|
62
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def manifest_path
|
|
69
|
+
candidate = Rails.root.join(MANIFEST_GLOB).to_s
|
|
70
|
+
File.exist?(candidate) ? candidate : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def normalize_subdir
|
|
74
|
+
# Strip leading/trailing slashes from manifest_subdir for path safety.
|
|
75
|
+
@config.manifest_subdir.to_s.gsub(%r{^/+|/+$}, "")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def append_directive(content, directive)
|
|
79
|
+
# Insert after last //= line for clean ordering.
|
|
80
|
+
lines = content.lines
|
|
81
|
+
last_directive_idx = lines.rindex { |l| l.strip.start_with?("//=") }
|
|
82
|
+
|
|
83
|
+
if last_directive_idx
|
|
84
|
+
new_lines = lines.dup
|
|
85
|
+
new_lines.insert(last_directive_idx + 1, directive)
|
|
86
|
+
new_lines.join
|
|
87
|
+
else
|
|
88
|
+
content + directive
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def short(path)
|
|
93
|
+
path.to_s.sub("#{Rails.root}/", "")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/react_manifest.rb
CHANGED
|
@@ -8,6 +8,8 @@ require "react_manifest/dependency_map"
|
|
|
8
8
|
require "react_manifest/generator"
|
|
9
9
|
require "react_manifest/application_analyzer"
|
|
10
10
|
require "react_manifest/application_migrator"
|
|
11
|
+
require "react_manifest/layout_patcher"
|
|
12
|
+
require "react_manifest/sprockets_manifest_patcher"
|
|
11
13
|
require "react_manifest/watcher"
|
|
12
14
|
require "react_manifest/reporter"
|
|
13
15
|
require "react_manifest/view_helpers"
|
data/tasks/react_manifest.rake
CHANGED
|
@@ -1,4 +1,91 @@
|
|
|
1
1
|
namespace :react_manifest do
|
|
2
|
+
desc "One-step setup: patch application.js, layout files, then generate manifests"
|
|
3
|
+
task setup: :environment do
|
|
4
|
+
dry = ENV["DRY_RUN"].to_s =~ /\A(1|true|yes)\z/i || ReactManifest.configuration.dry_run?
|
|
5
|
+
ReactManifest.configure { |c| c.dry_run = true } if dry
|
|
6
|
+
|
|
7
|
+
config = ReactManifest.configuration
|
|
8
|
+
|
|
9
|
+
puts ""
|
|
10
|
+
puts "=== react-manifest-rails setup ==="
|
|
11
|
+
puts "(DRY-RUN — no files will be written)\n" if config.dry_run?
|
|
12
|
+
puts ""
|
|
13
|
+
|
|
14
|
+
# 1. Confirm ux_root exists
|
|
15
|
+
puts "1) Checking ux_root..."
|
|
16
|
+
unless Dir.exist?(config.abs_ux_root)
|
|
17
|
+
puts " ! Not found: #{config.ux_root}"
|
|
18
|
+
puts " Create #{config.ux_root}/app/<controller>/ with your JSX files, then re-run."
|
|
19
|
+
puts ""
|
|
20
|
+
next
|
|
21
|
+
end
|
|
22
|
+
puts " ✓ #{config.ux_root} found"
|
|
23
|
+
|
|
24
|
+
# 2. Patch application.js (remove ux requires, keep vendor)
|
|
25
|
+
puts "\n2) Patching application.js..."
|
|
26
|
+
migrator = ReactManifest::ApplicationMigrator.new(config)
|
|
27
|
+
mig_results = migrator.migrate!
|
|
28
|
+
if mig_results.empty?
|
|
29
|
+
puts " No application*.js files found — nothing to migrate."
|
|
30
|
+
else
|
|
31
|
+
mig_results.each do |r|
|
|
32
|
+
case r[:status]
|
|
33
|
+
when :already_clean then puts " ✓ #{r[:file].sub("#{Rails.root}/", '')} — already clean"
|
|
34
|
+
when :migrated then puts " ✓ #{r[:file].sub("#{Rails.root}/", '')} — migrated (backup at .bak)"
|
|
35
|
+
when :dry_run then puts " ~ #{r[:file].sub("#{Rails.root}/", '')} — (dry-run preview above)"
|
|
36
|
+
when :backup_failed then puts " ✗ #{r[:file].sub("#{Rails.root}/", '')} — backup failed: #{r[:error]}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 3. Patch Sprockets manifest.js (add link_tree for ux_manifests)
|
|
42
|
+
puts "\n3) Patching Sprockets asset manifest..."
|
|
43
|
+
sm_glob = ReactManifest::SprocketsManifestPatcher::MANIFEST_GLOB
|
|
44
|
+
sm_result = ReactManifest::SprocketsManifestPatcher.new(config).patch!
|
|
45
|
+
case sm_result.status
|
|
46
|
+
when :patched then puts " ✓ #{sm_result.file.sub("#{Rails.root}/", '')}"
|
|
47
|
+
when :already_patched then puts " ✓ #{sm_glob} — already has link_tree"
|
|
48
|
+
when :dry_run then puts " ~ #{sm_glob} — (dry-run preview above)"
|
|
49
|
+
when :not_found then puts " ! #{sm_result.detail}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 4. Patch layout files (insert react_bundle_tag)
|
|
53
|
+
puts "\n4) Patching layout files..."
|
|
54
|
+
patcher = ReactManifest::LayoutPatcher.new(config)
|
|
55
|
+
patch_results = patcher.patch!
|
|
56
|
+
if patch_results.empty?
|
|
57
|
+
puts " No layout files found."
|
|
58
|
+
puts " Add <%= react_bundle_tag defer: true %> to your layout manually."
|
|
59
|
+
else
|
|
60
|
+
patch_results.each do |r|
|
|
61
|
+
case r.status
|
|
62
|
+
when :patched then puts " ✓ #{r.file.sub("#{Rails.root}/", '')}"
|
|
63
|
+
when :already_patched then puts " ✓ #{r.file.sub("#{Rails.root}/", '')} — already has react_bundle_tag"
|
|
64
|
+
when :dry_run then puts " ~ #{r.file.sub("#{Rails.root}/", '')} — (dry-run preview above)"
|
|
65
|
+
when :no_injection_point then puts " ! #{r.file.sub("#{Rails.root}/", '')} — #{r.detail}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# 5. Generate manifests
|
|
71
|
+
puts "\n5) Generating ux_*.js manifests..."
|
|
72
|
+
gen_results = ReactManifest::Generator.new(config).run!
|
|
73
|
+
written = gen_results.count { |r| r[:status] == :written }
|
|
74
|
+
unchanged = gen_results.count { |r| r[:status] == :unchanged }
|
|
75
|
+
dry_count = gen_results.count { |r| r[:status] == :dry_run }
|
|
76
|
+
|
|
77
|
+
if config.dry_run?
|
|
78
|
+
puts " ~ #{dry_count} manifest(s) would be written"
|
|
79
|
+
else
|
|
80
|
+
puts " ✓ #{written} written, #{unchanged} unchanged"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Done
|
|
84
|
+
puts "\n=== Setup #{'(dry-run) ' if config.dry_run?}complete ==="
|
|
85
|
+
puts "Start your Rails server — react_bundle_tag will serve the right JS per controller." unless config.dry_run?
|
|
86
|
+
puts ""
|
|
87
|
+
end
|
|
88
|
+
|
|
2
89
|
desc "Generate all ux_*.js Sprockets manifests from the ux/ directory tree"
|
|
3
90
|
task generate: :environment do
|
|
4
91
|
config = ReactManifest.configuration
|
|
@@ -25,8 +112,12 @@ namespace :react_manifest do
|
|
|
25
112
|
end
|
|
26
113
|
end
|
|
27
114
|
|
|
28
|
-
# Print any scanner warnings
|
|
29
|
-
|
|
115
|
+
# Print any scanner warnings # warnings are printed inline by scanner via $stdout in verbose mode
|
|
116
|
+
|
|
117
|
+
unless ReactManifest::SprocketsManifestPatcher.new(config).already_patched?
|
|
118
|
+
puts "[ReactManifest] NOTICE: app/assets/config/manifest.js is missing the link_tree directive."
|
|
119
|
+
puts "[ReactManifest] Run `rails react_manifest:setup` to add it automatically."
|
|
120
|
+
end
|
|
30
121
|
end
|
|
31
122
|
|
|
32
123
|
desc "Print the JSX dependency map and warnings without writing any files"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: react-manifest-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oliver Noonan
|
|
@@ -141,9 +141,11 @@ files:
|
|
|
141
141
|
- lib/react_manifest/configuration.rb
|
|
142
142
|
- lib/react_manifest/dependency_map.rb
|
|
143
143
|
- lib/react_manifest/generator.rb
|
|
144
|
+
- lib/react_manifest/layout_patcher.rb
|
|
144
145
|
- lib/react_manifest/railtie.rb
|
|
145
146
|
- lib/react_manifest/reporter.rb
|
|
146
147
|
- lib/react_manifest/scanner.rb
|
|
148
|
+
- lib/react_manifest/sprockets_manifest_patcher.rb
|
|
147
149
|
- lib/react_manifest/tree_classifier.rb
|
|
148
150
|
- lib/react_manifest/version.rb
|
|
149
151
|
- lib/react_manifest/view_helpers.rb
|