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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d0636de70da440909d01ca2748f51e9b73533a2c787b59984d906bac001b34c
4
- data.tar.gz: d5a1c1982d5e875b0794c0645b07391edfff54f210f3dc394c0650ab0929d76d
3
+ metadata.gz: 5a26dc7e8929f42fed07d13c8d38d3390d99d8063c81e5887ac171e52c1d9303
4
+ data.tar.gz: defd50858d68d0e22fdd396d6be214af15edb6eb6c5c030c1047c17f1f2ab0e6
5
5
  SHA512:
6
- metadata.gz: fd86d54537b980dfff2ad4a7ab620b483716a8df735fed7cb4a6f0cc92c65297552c2295f2eda81017bb5fe290e2fc9500c91e04e2ff1700637162749609f2e8
7
- data.tar.gz: 3bad752f3e4404f33a01fabd28c35b4dea27216b8adb8f4e312a0d3748bdbba85e06250866adc166bb9d49a723c1846163b2b8f8a0d75c50aa589ff461eb34dd
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
- This gem creates `ux_*.js` bundles and includes them with `react_bundle_tag`.
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 (Development)
7
+ ## Quick Start
8
8
 
9
- 1. Add gems:
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
- If your app uses this standard layout, you can keep defaults and only create the initializer if you want overrides:
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
- config.ux_root = "app/assets/javascripts/ux"
43
- config.app_dir = "app"
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
- 4. Put React files under:
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
- components/
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. Include bundles in your layout:
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
- - If expected `ux_*.js` files are missing, the gem generates them once on boot.
82
- - If `listen` is installed, file changes regenerate manifests automatically.
83
- - If `listen` is missing, run `bundle exec rails react_manifest:generate` manually.
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
- ## Configuration Notes
82
+ ## How Bundle Inclusion Works
87
83
 
88
- - `ignore`:
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
- - `exclude_paths`:
93
- - Excludes files by matching path segments while scanning the `ux_root` tree.
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
- - `dry_run`:
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
- - `verbose` vs `stdout_logging`:
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
- - `ux_shared.js`: shared files outside `ux/app/`
110
- - `ux_<controller>.js`: one bundle per directory under `ux/app/`
111
- - Generated manifests are written to `app/assets/javascripts/ux_manifests/` by default.
112
- - Existing legacy `app/assets/javascripts/ux_*.js` files are moved automatically on generation.
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
- Example:
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
- ## Commands
105
+ Writes are atomic (temp file + rename) and idempotent (SHA-256 comparison skips unchanged files).
119
106
 
120
- - Generate manifests now:
107
+ ## Asset Compilation & Minification
121
108
 
122
- ```bash
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
- - Watch in foreground (debugging only; not required in normal dev):
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
- ```bash
129
- bundle exec rails react_manifest:watch
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
- - Print bundle size report:
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
- ### `react_manifest:generate` is not recognized
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
- Run:
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 the gem is not disabled with `require: false`.
151
- - Restart Spring/server (`bin/spring stop`, then re-run command).
189
+ - Ensure it is not loaded with `require: false`.
190
+ - Restart Spring: `bin/spring stop`.
152
191
 
153
- ### Server starts but nothing happens
192
+ ### Server starts but no bundles are served
154
193
 
155
- Check these in order:
156
- - `app/assets/javascripts/ux` exists.
157
- - Your controller files are under `ux/app/<controller>/`.
158
- - Your layout includes `<%= react_bundle_tag %>`.
159
- - Run `bundle exec rails react_manifest:generate` once and check for generated `ux_*.js` in `app/assets/javascripts`.
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 in dev does not run
201
+ ### Auto-watch not running in development
162
202
 
163
- - Ensure `listen` is in the development group.
164
- - Restart the Rails server after adding `listen`.
165
- - If you choose not to install `listen`, manual generation is the supported fallback.
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/Railties: 7.x to 8.x
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
@@ -1,3 +1,3 @@
1
1
  module ReactManifest
2
- VERSION = "0.2.8".freeze
2
+ VERSION = "0.2.9".freeze
3
3
  end
@@ -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"
@@ -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
- results # warnings are printed inline by scanner via $stdout in verbose mode
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.8
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