react-manifest-rails 0.2.22 → 0.2.24

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: c87f68d7ed8248c19732ce3b946a1207ed85907a92bd39ab5a2eda665f5b001e
4
- data.tar.gz: 7ee42c73f25048a31d8ca713d0cdfc23b344eb99ec68d1f72c58b5cfda3b635d
3
+ metadata.gz: 786d53e91fe8a65339def86bc62a94179762364cf13d1abcc8427ea9585f0c38
4
+ data.tar.gz: 90c58cd92bc06eb2911deaf829aca60ed8533e6dc58e535759e9f6d60eefdcd2
5
5
  SHA512:
6
- metadata.gz: d71e1c8b255678a3b1a72df766134b80dadd8bf7553d4380e240d3a8b526dff5e6f6ef848574b93c86f6778feef896cc04b945a0f020d03d0bccf034acfe7d74
7
- data.tar.gz: 874538d0cd06e8b8a121d9343a3846649b6612cd3ad4bb8c0c28a7c1589c1e05bf2f48b80ae0fe6a8af478d07a3d4309d9a5bc692536f4cdebeeed90b89ca74c
6
+ metadata.gz: 30c7f0633646f06cfc497a617c76dbbb50f74e84104fd590f3054bff5c228a42e08aa6683dfdc96702656a52cec57533917b2438d4bb39ede667ef6c0686f219
7
+ data.tar.gz: 0ea044f3d55bb64be2ce9a725cc90cf9b33da8f343fde043707c098995ce6e88840e609947867203efe739623ce5dba3a32e4d444dfb9b3f8b252b56b991adbb
data/CHANGELOG.md CHANGED
@@ -7,10 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.24] - 2026-04-22
11
+
12
+ ### Fixed
13
+ - Controller manifests now inline files from bundles listed in `always_include` (for example `ux_main`), so runtime symbol availability no longer depends on cross-bundle script execution order in production.
14
+ - Scanner analysis no longer emits warnings for ux/app file naming convention mismatches, reducing noise for apps that intentionally use custom filename patterns.
15
+
16
+ ### Changed
17
+ - Updated `Gemfile.lock` to keep lockfile state aligned with the released codebase.
18
+
19
+ ## [0.2.23] - 2026-04-21
20
+
10
21
  ### Fixed
11
22
  - `react_component` now emits only `ux_shared` plus the direct owning bundle for the requested component symbol, instead of also emitting transitive controller manifests as separate script tags. This prevents unnecessary network requests for additional `ux_*.js` manifests while preserving dependency loading through generated manifest `require` directives.
12
23
  - Generator now skips `external_roots` and `external_providers` entries that resolve to files already included in `ux_shared`, preventing duplicate runtime declarations from overlapping shared/external includes.
13
24
  - View helper bundle deduplication now canonicalizes bundle names (e.g. `ux_shared` vs `ux_manifests/ux_shared`) to avoid re-emitting equivalent script tags.
25
+ - Controller manifests now include scanner-detected shared dependencies after `ux_shared` removal, including transitive shared dependencies needed by shared components (for example `DataTable` -> `SortHeader`).
26
+ - Shared `ux/lib` utility files are now included in controller manifests, restoring runtime availability for global helper functions such as `formatDate` and `formatCurrency`.
14
27
 
15
28
  ## [0.2.10] - 2026-04-16
16
29
 
@@ -42,12 +42,12 @@ module ReactManifest
42
42
  # written and others stale/missing.
43
43
  def run!
44
44
  classification = @classifier.classify
45
- controller_context = build_controller_context(classification.controller_dirs, classification.shared_dirs)
45
+ scan_result = Scanner.new(@config).scan(classification)
46
+ controller_context = build_controller_context(classification.controller_dirs, classification.shared_dirs,
47
+ scan_result)
46
48
 
47
49
  # Phase 1: build all content in memory — no I/O.
48
- manifests = []
49
- manifests << build_shared(classification.shared_dirs)
50
- classification.controller_dirs.each { |ctrl| manifests << build_controller(ctrl, controller_context) }
50
+ manifests = classification.controller_dirs.map { |ctrl| build_controller(ctrl, controller_context) }
51
51
 
52
52
  migrate_legacy_manifests!
53
53
 
@@ -62,33 +62,19 @@ module ReactManifest
62
62
 
63
63
  # ------------------------------------------------------------------ shared
64
64
 
65
- def build_shared(shared_dirs)
66
- lines = header_lines
67
- any_files = false
68
-
69
- shared_dirs.each do |shared_dir|
70
- files = js_files_in(shared_dir[:path])
71
- next if files.empty?
72
-
73
- any_files = true
74
- files.each { |f| lines << "//= require #{relative_require_path(f)}" }
75
- end
76
-
77
- lines << "// (no shared files found)" unless any_files
78
-
79
- { filename: "#{@config.shared_bundle}.js", content: "#{lines.join("\n")}\n" }
80
- end
81
-
82
65
  # --------------------------------------------------------------- controller
83
66
 
84
67
  def build_controller(ctrl, controller_context)
85
68
  lines = header_lines
69
+ always_include_reqs = controller_context[:always_include_requires].fetch(ctrl[:bundle_name], [])
86
70
  dep_requires = controller_dependency_requires(ctrl[:bundle_name], controller_context)
71
+ lib_reqs = controller_context[:shared_lib_requires]
72
+ shared_reqs = controller_context[:shared_requires].fetch(ctrl[:bundle_name], Set.new).to_a.sort
87
73
  ext_reqs = controller_context[:external_requires].fetch(ctrl[:bundle_name], Set.new).to_a.sort
88
74
 
89
75
  files = js_files_in(ctrl[:path])
90
76
  own_requires = files.map { |f| relative_require_path(f) }
91
- all_requires = (dep_requires + ext_reqs + own_requires).uniq
77
+ all_requires = (always_include_reqs + dep_requires + lib_reqs + shared_reqs + ext_reqs + own_requires).uniq
92
78
 
93
79
  if all_requires.empty?
94
80
  lines << "// (no JSX files found in #{ctrl[:name]}/)"
@@ -99,16 +85,26 @@ module ReactManifest
99
85
  { filename: "#{ctrl[:bundle_name]}.js", content: "#{lines.join("\n")}\n" }
100
86
  end
101
87
 
102
- def build_controller_context(controller_dirs, shared_dirs)
88
+ # rubocop:disable Metrics/AbcSize
89
+ def build_controller_context(controller_dirs, shared_dirs, scan_result)
103
90
  bundle_files = {}
104
91
  symbol_to_bundle = {}
105
92
  external_symbol_to_require = {}
106
93
  dependencies = Hash.new { |h, k| h[k] = Set.new }
107
94
  external_requires = Hash.new { |h, k| h[k] = Set.new }
108
95
  shared_require_paths = shared_require_path_set(shared_dirs)
96
+ shared_requires = Hash.new { |h, k| h[k] = Set.new }
97
+ shared_dependency_map = build_shared_dependency_map(shared_dirs, shared_require_paths, scan_result)
98
+ shared_lib_requires = shared_lib_require_paths(shared_dirs)
109
99
 
110
- # Index controller-defined symbols for cross-app detection
111
100
  controller_dirs.each do |ctrl|
101
+ scan_result.controller_usages.fetch(ctrl[:name], []).each do |req_path|
102
+ shared_requires[ctrl[:bundle_name]] << req_path
103
+ end
104
+ shared_requires[ctrl[:bundle_name]] = expand_shared_requires(shared_requires[ctrl[:bundle_name]],
105
+ shared_dependency_map)
106
+
107
+ # Index controller-defined symbols for cross-app detection
112
108
  bundle_name = ctrl[:bundle_name]
113
109
  files = js_files_in(ctrl[:path])
114
110
  bundle_files[bundle_name] = files
@@ -127,10 +123,6 @@ module ReactManifest
127
123
  abs_root = abs_external_root(root_path)
128
124
  external_js_files_in(abs_root).each do |file_path|
129
125
  req_path = relative_require_path(file_path)
130
- if shared_require_paths.include?(normalize_require_path(req_path))
131
- warn "[ReactManifest] Skipping external_roots file already provided by shared bundle: #{req_path}"
132
- next
133
- end
134
126
 
135
127
  warn_on_external_controller_references(file_path, symbol_to_bundle)
136
128
 
@@ -142,12 +134,6 @@ module ReactManifest
142
134
 
143
135
  # Explicit external_providers win over scanned roots on symbol conflicts
144
136
  @config.external_providers.each do |sym, req_path|
145
- if shared_require_paths.include?(normalize_require_path(req_path))
146
- warn "[ReactManifest] Skipping external provider '#{sym}' because it is already " \
147
- "provided by shared bundle: #{req_path}"
148
- next
149
- end
150
-
151
137
  external_symbol_to_require[sym] = req_path
152
138
  end
153
139
 
@@ -164,12 +150,18 @@ module ReactManifest
164
150
  end
165
151
  end
166
152
 
153
+ always_include_requires = build_always_include_requires(bundle_files, dependencies)
154
+
167
155
  {
168
156
  bundle_files: bundle_files,
169
157
  dependencies: dependencies,
158
+ always_include_requires: always_include_requires,
159
+ shared_lib_requires: shared_lib_requires,
160
+ shared_requires: shared_requires,
170
161
  external_requires: external_requires
171
162
  }
172
163
  end
164
+ # rubocop:enable Metrics/AbcSize
173
165
 
174
166
  def controller_dependency_requires(bundle_name, controller_context)
175
167
  deps = transitive_dependencies(bundle_name, controller_context[:dependencies])
@@ -199,6 +191,32 @@ module ReactManifest
199
191
  ordered
200
192
  end
201
193
 
194
+ def build_always_include_requires(bundle_files, dependencies)
195
+ bundles = @config.always_include.map(&:to_s).reject(&:empty?).uniq
196
+ return Hash.new { |h, k| h[k] = [] } if bundles.empty?
197
+
198
+ requires_by_bundle = Hash.new { |h, k| h[k] = [] }
199
+
200
+ bundle_files.each_key do |bundle_name|
201
+ requires = Set.new
202
+
203
+ bundles.each do |always_bundle|
204
+ next if always_bundle == bundle_name
205
+
206
+ transitive = [always_bundle] + transitive_dependencies(always_bundle, dependencies)
207
+ transitive.each do |dep_bundle|
208
+ bundle_files.fetch(dep_bundle, []).each do |abs_path|
209
+ requires << relative_require_path(abs_path)
210
+ end
211
+ end
212
+ end
213
+
214
+ requires_by_bundle[bundle_name] = requires.to_a.sort
215
+ end
216
+
217
+ requires_by_bundle
218
+ end
219
+
202
220
  # --------------------------------------------------------------- write
203
221
 
204
222
  def write_manifest(filename, content)
@@ -370,6 +388,58 @@ module ReactManifest
370
388
  end
371
389
  end
372
390
 
391
+ def shared_lib_require_paths(shared_dirs)
392
+ shared_dirs.each_with_object([]) do |shared_dir, paths|
393
+ next unless File.basename(shared_dir[:path]) == "lib"
394
+
395
+ js_files_in(shared_dir[:path]).each do |file_path|
396
+ paths << normalize_require_path(relative_require_path(file_path))
397
+ end
398
+ end.sort.uniq
399
+ end
400
+
401
+ def build_shared_dependency_map(shared_dirs, shared_require_paths, scan_result)
402
+ dependency_map = Hash.new { |h, k| h[k] = Set.new }
403
+
404
+ shared_symbol_index = scan_result.symbol_index.each_with_object({}) do |(sym, req_path), index|
405
+ normalized = normalize_require_path(req_path)
406
+ next unless shared_require_paths.include?(normalized)
407
+
408
+ index[sym] = normalized
409
+ end
410
+
411
+ shared_dirs.each do |shared_dir|
412
+ js_files_in(shared_dir[:path]).each do |file_path|
413
+ from_req = normalize_require_path(relative_require_path(file_path))
414
+ extract_used_component_symbols(file_path).each do |sym|
415
+ to_req = shared_symbol_index[sym]
416
+ next if to_req.nil? || to_req == from_req
417
+
418
+ dependency_map[from_req] << to_req
419
+ end
420
+ end
421
+ end
422
+
423
+ dependency_map
424
+ end
425
+
426
+ def expand_shared_requires(initial_requires, dependency_map)
427
+ expanded = Set.new(initial_requires)
428
+ queue = initial_requires.to_a
429
+
430
+ until queue.empty?
431
+ req = queue.shift
432
+ dependency_map.fetch(req, Set.new).each do |dep_req|
433
+ next if expanded.include?(dep_req)
434
+
435
+ expanded << dep_req
436
+ queue << dep_req
437
+ end
438
+ end
439
+
440
+ expanded
441
+ end
442
+
373
443
  def normalize_require_path(path)
374
444
  path.to_s.sub(/\.js\.jsx$/, "").sub(/\.jsx$/, "").sub(/\.js$/, "")
375
445
  end
@@ -9,7 +9,6 @@ module ReactManifest
9
9
  # Phase 1 — builds a symbol index from shared dirs:
10
10
  # "PrimaryButton" => "ux/components/buttons/primary_button"
11
11
  # "useFetch" => "ux/hooks/use_fetch"
12
- # "formatDate" => "ux/lib/format_date"
13
12
  #
14
13
  # Phase 2 — scans controller files for usage of those symbols
15
14
  # and produces per-controller lists of referenced shared files.
@@ -21,22 +20,19 @@ module ReactManifest
21
20
  DEFINITION_PATTERNS = [
22
21
  # CommonJS / variable-assignment style
23
22
  /(?:const|let|var)\s+([A-Z][A-Za-z0-9_]*)\s*=/, # const FooBar =
24
- /function\s+([A-Z][A-Za-z0-9_]*)\s*\(/, # function FooBar(
25
- /class\s+([A-Z][A-Za-z0-9_]*)\s*(?:extends|\{)/, # class FooBar
23
+ /function\s+([A-Z][A-Za-z0-9_]*)\s*\(/, # function FooBar(
24
+ /class\s+([A-Z][A-Za-z0-9_]*)\s*(?:extends|\{)/, # class FooBar
26
25
  /(?:const|let|var)\s+(use[A-Z][A-Za-z0-9_]*)\s*=/, # const useFoo = (hooks)
27
26
  /function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/, # function useFoo(
28
- /(?:const|let|var)\s+([a-z][A-Za-z0-9_]{2,})\s*=\s*(?:function|\()/, # const formatDate = function/arrow
29
- /^function\s+([a-z][A-Za-z0-9_]{2,})\s*\(/, # function formatDate( at line start
30
27
 
31
28
  # ES module style (export default / named exports)
32
- /^export\s+default\s+(?:function|class)\s+([A-Z][A-Za-z0-9_]*)/, # export default function Foo
33
- /^export\s+default\s+(?:function|class)\s+(use[A-Z][A-Za-z0-9_]*)/, # export default function useFoo
34
- /^export\s+(?:const|let|var)\s+([A-Z][A-Za-z0-9_]*)\s*=/, # export const Foo =
35
- /^export\s+(?:const|let|var)\s+(use[A-Z][A-Za-z0-9_]*)\s*=/, # export const useFoo =
36
- /^export\s+(?:const|let|var)\s+([a-z][A-Za-z0-9_]{2,})\s*=\s*(?:function|\()/, # export const formatDate =
37
- /^export\s+function\s+([A-Z][A-Za-z0-9_]*)\s*\(/, # export function Foo(
38
- /^export\s+function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/, # export function useFoo(
39
- /^export\s+class\s+([A-Z][A-Za-z0-9_]*)\s*(?:extends|\{)/ # export class Foo
29
+ /^export\s+default\s+(?:function|class)\s+([A-Z][A-Za-z0-9_]*)/,
30
+ /^export\s+default\s+(?:function|class)\s+(use[A-Z][A-Za-z0-9_]*)/,
31
+ /^export\s+(?:const|let|var)\s+([A-Z][A-Za-z0-9_]*)\s*=/,
32
+ /^export\s+(?:const|let|var)\s+(use[A-Z][A-Za-z0-9_]*)\s*=/,
33
+ /^export\s+function\s+([A-Z][A-Za-z0-9_]*)\s*\(/,
34
+ /^export\s+function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/,
35
+ /^export\s+class\s+([A-Z][A-Za-z0-9_]*)\s*(?:extends|\{)/
40
36
  ].freeze
41
37
 
42
38
  # Patterns to detect usage in controller files.
@@ -66,7 +62,7 @@ module ReactManifest
66
62
 
67
63
  # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/PerceivedComplexity
68
64
  def scan(classification)
69
- warnings = []
65
+ warnings = Set.new
70
66
  symbol_index = {}
71
67
  external_file_paths = {} # file_path => relative_require_path for external_roots files
72
68
 
@@ -79,7 +75,7 @@ module ReactManifest
79
75
  symbols = extract_definitions(file_path)
80
76
  symbols.each do |sym|
81
77
  if symbol_index.key?(sym)
82
- warnings << "Duplicate symbol '#{sym}' in #{relative} (already from #{symbol_index[sym]})"
78
+ warnings.add("Duplicate symbol '#{sym}' in #{relative} (already from #{symbol_index[sym]})")
83
79
  else
84
80
  symbol_index[sym] = relative
85
81
  end
@@ -131,10 +127,9 @@ module ReactManifest
131
127
  files = js_files_in(ctrl[:path])
132
128
  used = Set.new
133
129
 
134
- warnings << "Controller dir '#{ctrl[:name]}' has no JS/JSX files" if files.empty? && @config.verbose?
130
+ warnings.add("Controller dir '#{ctrl[:name]}' has no JS/JSX files") if files.empty? && @config.verbose?
135
131
 
136
132
  files.each do |file_path|
137
- validate_naming(file_path, ctrl[:name], warnings)
138
133
  content = read_controller_file(file_path, warnings)
139
134
  next unless content
140
135
 
@@ -150,7 +145,7 @@ module ReactManifest
150
145
  Result.new(
151
146
  symbol_index: symbol_index,
152
147
  controller_usages: controller_usages,
153
- warnings: warnings,
148
+ warnings: warnings.to_a,
154
149
  shared_violations: shared_violations,
155
150
  external_violations: external_violations
156
151
  )
@@ -195,15 +190,6 @@ module ReactManifest
195
190
  rel.sub(/\.js\.jsx$/, "").sub(/\.jsx$/, "").sub(/\.js$/, "")
196
191
  end
197
192
 
198
- def validate_naming(file_path, ctrl_name, warnings)
199
- basename = File.basename(file_path, ".*").sub(/\.js$/, "")
200
- # Expected: <controller>_index, <controller>_show, <controller>_form, etc.
201
- return if basename.start_with?("#{ctrl_name}_") || basename == ctrl_name
202
-
203
- warnings << "File '#{File.basename(file_path)}' in '#{ctrl_name}' does not follow " \
204
- "'#{ctrl_name}_<action>.js.jsx' naming convention"
205
- end
206
-
207
193
  def detect_shared_violations(shared_file_paths, controller_symbol_index, warnings)
208
194
  violations = []
209
195
  shared_file_paths.each do |file_path, relative|
@@ -225,9 +211,9 @@ module ReactManifest
225
211
  info = controller_symbol_index[sym]
226
212
  violations << { shared_file: relative, symbol: sym,
227
213
  controller: info[:controller], app_file: info[:file] }
228
- warnings << "Shared file '#{relative}' uses app-dir symbol '#{sym}' " \
229
- "(from ux/app/#{info[:controller]}). " \
230
- "Move '#{sym}' to a shared dir or the shared file will be incomplete."
214
+ warnings.add("Shared file '#{relative}' uses app-dir symbol '#{sym}' " \
215
+ "(from ux/app/#{info[:controller]}). " \
216
+ "Move '#{sym}' to a shared dir or the shared file will be incomplete.")
231
217
  end
232
218
  end
233
219
  end
@@ -255,9 +241,9 @@ module ReactManifest
255
241
  info = controller_symbol_index[sym]
256
242
  violations << { external_file: relative, symbol: sym,
257
243
  controller: info[:controller], app_file: info[:file] }
258
- warnings << "External file '#{relative}' uses app-dir symbol '#{sym}' " \
259
- "(from ux/app/#{info[:controller]}). " \
260
- "Move '#{sym}' into a shared ux dir to avoid duplicate runtime declarations."
244
+ warnings.add("External file '#{relative}' uses app-dir symbol '#{sym}' " \
245
+ "(from ux/app/#{info[:controller]}). " \
246
+ "Move '#{sym}' into a shared ux dir to avoid duplicate runtime declarations.")
261
247
  end
262
248
  end
263
249
  end
@@ -272,20 +258,17 @@ module ReactManifest
272
258
  end
273
259
 
274
260
  fanout.each do |file, count|
275
- if count > 3
276
- warnings << "High fan-out: '#{file}' is used by #{count} controllers " \
277
- "(consider ensuring it's in the shared bundle)"
278
- end
261
+ warnings.add("High fan-out: '#{file}' is used by #{count} controllers") if count > 3
279
262
  end
280
263
  end
281
264
 
282
265
  def read_controller_file(file_path, warnings)
283
266
  File.read(file_path, encoding: "utf-8")
284
267
  rescue Errno::ENOENT, Errno::EACCES => e
285
- warnings << "Skipping #{file_path}: #{e.message}"
268
+ warnings.add("Skipping #{file_path}: #{e.message}")
286
269
  nil
287
270
  rescue Encoding::InvalidByteSequenceError
288
- warnings << "Skipping #{file_path}: not valid UTF-8"
271
+ warnings.add("Skipping #{file_path}: not valid UTF-8")
289
272
  nil
290
273
  end
291
274
 
@@ -1,3 +1,3 @@
1
1
  module ReactManifest
2
- VERSION = "0.2.22".freeze
2
+ VERSION = "0.2.24".freeze
3
3
  end
@@ -5,11 +5,10 @@ module ReactManifest
5
5
  # <%= react_bundle_tag %>
6
6
  #
7
7
  # Resolves which ux_*.js bundles to include based on controller_path:
8
- # 1. Always includes config.shared_bundle (e.g. "ux_shared")
9
- # 2. Always appends config.always_include (e.g. ["ux_main"])
10
- # 3. Appends "ux_<controller_path>" if that bundle file exists
11
- # 4. For namespaced controllers (admin/users): checks ux_admin_users, then ux_admin
12
- # 5. Returns "" for pure ERB/HAML pages with no matching bundle, or when
8
+ # 1. Appends config.always_include (e.g. ["ux_main"])
9
+ # 2. Appends "ux_<controller_path>" if that bundle file exists
10
+ # 3. For namespaced controllers (admin/users): checks ux_admin_users, then ux_admin
11
+ # 4. Returns "" for pure ERB/HAML pages with no matching bundle, or when
13
12
  # called outside a controller context (mailers, engines, etc.)
14
13
  module ViewHelpers
15
14
  def react_bundle_tag(**html_options)
@@ -22,10 +21,14 @@ module ReactManifest
22
21
  return "".html_safe if bundles.empty?
23
22
 
24
23
  # Record emitted bundles so react_component doesn't re-emit them.
25
- emitted = (@_react_manifest_emitted_bundles ||= [])
26
- bundles.each { |b| emitted << b unless emitted_bundle?(emitted, b) }
24
+ emitted = emitted_bundles
25
+ fresh_bundles = bundles.reject { |b| emitted_bundle?(emitted, b) }
26
+ return "".html_safe if fresh_bundles.empty?
27
27
 
28
- asset_names = bundles.map { |bundle| "#{bundle}.js" }
28
+ fresh_bundles.each { |b| emitted << b }
29
+ mark_bundle_tag_rendered
30
+
31
+ asset_names = fresh_bundles.map { |bundle| "#{bundle}.js" }
29
32
  javascript_include_tag(*asset_names, extname: false, **html_options)
30
33
  end
31
34
 
@@ -35,12 +38,13 @@ module ReactManifest
35
38
  # This avoids strict dependence on controller_path -> bundle naming alignment.
36
39
  def react_component(*args, **kwargs, &block)
37
40
  html = super
41
+ return html if bundle_tag_rendered?
38
42
 
39
43
  component_name = args.first
40
44
  bundles = ReactManifest.resolve_bundles_for_component_direct(component_name)
41
45
  return html if bundles.empty?
42
46
 
43
- emitted = (@_react_manifest_emitted_bundles ||= [])
47
+ emitted = emitted_bundles
44
48
 
45
49
  new_tags = bundles.filter_map do |bundle|
46
50
  next if emitted_bundle?(emitted, bundle)
@@ -64,5 +68,27 @@ module ReactManifest
64
68
  def canonical_bundle_name(bundle)
65
69
  bundle.to_s.split("/").last
66
70
  end
71
+
72
+ def emitted_bundles
73
+ # ActionView can instantiate multiple helper contexts during one request.
74
+ # Store emitted bundles in request env so layout + template helpers dedupe.
75
+ if respond_to?(:request, true) && request
76
+ request.env["react_manifest.emitted_bundles"] ||= []
77
+ else
78
+ @emitted_bundles ||= []
79
+ end
80
+ end
81
+
82
+ def mark_bundle_tag_rendered
83
+ return unless respond_to?(:request, true) && request
84
+
85
+ request.env["react_manifest.bundle_tag_rendered"] = true
86
+ end
87
+
88
+ def bundle_tag_rendered?
89
+ return false unless respond_to?(:request, true) && request
90
+
91
+ request.env["react_manifest.bundle_tag_rendered"] == true
92
+ end
67
93
  end
68
94
  end
@@ -130,12 +130,6 @@ namespace :react_manifest do
130
130
  scan_result = scanner.scan(classification)
131
131
  dep_map = ReactManifest::DependencyMap.new(scan_result)
132
132
  dep_map.print_report
133
-
134
- unless scan_result.warnings.empty?
135
- puts "Warnings (#{scan_result.warnings.size}):"
136
- scan_result.warnings.each { |w| puts " ⚠ #{w}" }
137
- puts
138
- end
139
133
  end
140
134
 
141
135
  desc "Analyze application*.js files — show what migrate_application would change"
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.22
4
+ version: 0.2.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan