react-manifest-rails 0.2.19 → 0.2.21

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: d392f2b6bf13451773d9e12b51a3b1cf1048c407058cb60cbdb2db48cfa79acc
4
- data.tar.gz: ff2acfbd5ddc4b0b1e048fea664211ed62d04890fcdbcb555324f4ce3bbfa0bd
3
+ metadata.gz: a68f022a75a440c68e741e931c5453e6c96c126380356757825a984de94e2143
4
+ data.tar.gz: 52d20e65e685a1b82d833ee650677c91a6fc5faa9e7b32c1075d85a2e6f53fa7
5
5
  SHA512:
6
- metadata.gz: 2df51348498e3778af7048c4a7d3090d1223ab0683e4bac4a0443dff6ad2b4ec47eca9bb50a492b35a012cb5ce126521839540a58171cddd67651c0da50e56e8
7
- data.tar.gz: b7f9300e03f28a274b33f62c3500add16249aa0e4d52607d6c359527ca36c3f53ba00d69d840021c954ec87bcf7b6aa36682122349ada050f0f74e7d11a481a0
6
+ metadata.gz: bfd3abb4dde7e8d661df2623b42345a6f5a191f6b119ce25d8913ab094261ea76592ea435709a9fd80d320703673deb073b4df2a8de90a2fb76d6cf43532272b
7
+ data.tar.gz: cef2fd10f820ebf0a1583772fa7236e34f5c5d2744e076f682111d374cf220c265b2625d02a6f88d0274baaaa8e215eb55cfec353e528d85ac82327ef466dc6f
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ 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
+ ## [Unreleased]
9
+
10
+ ### Fixed
11
+ - `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
+ - 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
+ - View helper bundle deduplication now canonicalizes bundle names (e.g. `ux_shared` vs `ux_manifests/ux_shared`) to avoid re-emitting equivalent script tags.
14
+
8
15
  ## [0.2.10] - 2026-04-16
9
16
 
10
17
  ### Fixed
data/README.md CHANGED
@@ -84,7 +84,9 @@ In development:
84
84
  Generation is **directory-based** — deterministic and conservative by design.
85
85
 
86
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>/`
87
+ - `ux_<controller>.js`: every file under `ux/app/<controller>/`, plus transitive controller dependencies inferred from component usage across `ux/app/*`
88
+
89
+ `ux_shared.js` is loaded as a separate script by `react_bundle_tag` and `react_component` helpers. It is intentionally not inlined into every `ux_<controller>.js` manifest to avoid duplicating shared code across controller bundles.
88
90
 
89
91
  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.
90
92
 
@@ -141,6 +143,7 @@ end
141
143
  - **`exclude_paths`**: excludes files whose path contains any listed segment. Not based on `application.js`.
142
144
  - **`dry_run`**: also honoured by `DRY_RUN=1` environment variable at runtime.
143
145
  - **`extensions`**: add `ts` and `tsx` to enable TypeScript source detection.
146
+ - **`external_roots` / `external_providers`**: do not point these at files already inside shared `ux/` dirs (`components/`, `hooks/`, `lib/`, etc.). The generator now skips those overlaps to prevent duplicate declarations in browser runtime.
144
147
 
145
148
  ## Commands
146
149
 
@@ -168,6 +171,22 @@ bundle exec rails react_manifest:watch
168
171
  bundle exec rails react_manifest:clean
169
172
  ```
170
173
 
174
+ ## Releasing
175
+
176
+ The publish workflow runs on tag pushes (`v*`), not on branch pushes.
177
+
178
+ 1. Bump `ReactManifest::VERSION` in `lib/react_manifest/version.rb`.
179
+ 2. Run `bundle install` to refresh the local path spec version in `Gemfile.lock`.
180
+ 3. Commit and push to `main`/`master`.
181
+ 4. Create and push a matching annotated tag:
182
+
183
+ ```bash
184
+ git tag -a vX.Y.Z -m "Release vX.Y.Z"
185
+ git push origin vX.Y.Z
186
+ ```
187
+
188
+ This tag push triggers `.github/workflows/release.yml`, which runs tests, creates a GitHub Release, and publishes to RubyGems.
189
+
171
190
  ## Troubleshooting
172
191
 
173
192
  ### `AssetNotPrecompiledError` for `ux_*.js`
@@ -42,7 +42,7 @@ 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)
45
+ controller_context = build_controller_context(classification.controller_dirs, classification.shared_dirs)
46
46
 
47
47
  # Phase 1: build all content in memory — no I/O.
48
48
  manifests = []
@@ -99,12 +99,13 @@ module ReactManifest
99
99
  { filename: "#{ctrl[:bundle_name]}.js", content: "#{lines.join("\n")}\n" }
100
100
  end
101
101
 
102
- def build_controller_context(controller_dirs)
102
+ def build_controller_context(controller_dirs, shared_dirs)
103
103
  bundle_files = {}
104
104
  symbol_to_bundle = {}
105
105
  external_symbol_to_require = {}
106
106
  dependencies = Hash.new { |h, k| h[k] = Set.new }
107
107
  external_requires = Hash.new { |h, k| h[k] = Set.new }
108
+ shared_require_paths = shared_require_path_set(shared_dirs)
108
109
 
109
110
  # Index controller-defined symbols for cross-app detection
110
111
  controller_dirs.each do |ctrl|
@@ -126,6 +127,11 @@ module ReactManifest
126
127
  abs_root = abs_external_root(root_path)
127
128
  external_js_files_in(abs_root).each do |file_path|
128
129
  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
+
129
135
  extract_defined_symbols(file_path).each do |sym|
130
136
  external_symbol_to_require[sym] ||= req_path
131
137
  end
@@ -134,6 +140,12 @@ module ReactManifest
134
140
 
135
141
  # Explicit external_providers win over scanned roots on symbol conflicts
136
142
  @config.external_providers.each do |sym, req_path|
143
+ if shared_require_paths.include?(normalize_require_path(req_path))
144
+ warn "[ReactManifest] Skipping external provider '#{sym}' because it is already " \
145
+ "provided by shared bundle: #{req_path}"
146
+ next
147
+ end
148
+
137
149
  external_symbol_to_require[sym] = req_path
138
150
  end
139
151
 
@@ -348,6 +360,18 @@ module ReactManifest
348
360
  Rails.root.join(path).to_s
349
361
  end
350
362
 
363
+ def shared_require_path_set(shared_dirs)
364
+ shared_dirs.each_with_object(Set.new) do |shared_dir, paths|
365
+ js_files_in(shared_dir[:path]).each do |file_path|
366
+ paths << normalize_require_path(relative_require_path(file_path))
367
+ end
368
+ end
369
+ end
370
+
371
+ def normalize_require_path(path)
372
+ path.to_s.sub(/\.js\.jsx$/, "").sub(/\.jsx$/, "").sub(/\.js$/, "")
373
+ end
374
+
351
375
  def auto_generated?(path)
352
376
  # Avoid TOCTOU: don't check existence separately — just attempt the read
353
377
  # and treat a missing/unreadable file as not auto-generated.
@@ -1,3 +1,3 @@
1
1
  module ReactManifest
2
- VERSION = "0.2.19".freeze
2
+ VERSION = "0.2.21".freeze
3
3
  end
@@ -23,7 +23,7 @@ module ReactManifest
23
23
 
24
24
  # Record emitted bundles so react_component doesn't re-emit them.
25
25
  emitted = (@_react_manifest_emitted_bundles ||= [])
26
- bundles.each { |b| emitted << b unless emitted.include?(b) }
26
+ bundles.each { |b| emitted << b unless emitted_bundle?(emitted, b) }
27
27
 
28
28
  asset_names = bundles.map { |bundle| "#{bundle}.js" }
29
29
  javascript_include_tag(*asset_names, extname: false, **html_options)
@@ -37,13 +37,13 @@ module ReactManifest
37
37
  html = super
38
38
 
39
39
  component_name = args.first
40
- bundles = ReactManifest.resolve_bundles_for_component(component_name)
40
+ bundles = ReactManifest.resolve_bundles_for_component_direct(component_name)
41
41
  return html if bundles.empty?
42
42
 
43
43
  emitted = (@_react_manifest_emitted_bundles ||= [])
44
44
 
45
45
  new_tags = bundles.filter_map do |bundle|
46
- next if emitted.include?(bundle)
46
+ next if emitted_bundle?(emitted, bundle)
47
47
 
48
48
  emitted << bundle
49
49
  javascript_include_tag("#{bundle}.js", extname: false)
@@ -53,5 +53,16 @@ module ReactManifest
53
53
 
54
54
  safe_join(new_tags + [html])
55
55
  end
56
+
57
+ private
58
+
59
+ def emitted_bundle?(emitted, bundle)
60
+ canonical = canonical_bundle_name(bundle)
61
+ emitted.any? { |existing| canonical_bundle_name(existing) == canonical }
62
+ end
63
+
64
+ def canonical_bundle_name(bundle)
65
+ bundle.to_s.split("/").last
66
+ end
56
67
  end
57
68
  end
@@ -15,6 +15,7 @@ require "react_manifest/watcher"
15
15
  require "react_manifest/reporter"
16
16
  require "react_manifest/view_helpers"
17
17
 
18
+ # rubocop:disable Metrics/ModuleLength
18
19
  module ReactManifest
19
20
  class << self
20
21
  def configuration
@@ -65,7 +66,32 @@ module ReactManifest
65
66
  # where the requested component name is known and may not align 1:1 with
66
67
  # controller_path-derived bundle names.
67
68
  def resolve_bundle_for_component(component_name)
68
- resolve_bundles_for_component(component_name).last
69
+ resolve_bundles_for_component_direct(component_name).last
70
+ end
71
+
72
+ # Resolve the direct bundle list needed for a React component symbol.
73
+ # Returns shared first (if present), then the component's owning bundle.
74
+ #
75
+ # Unlike resolve_bundles_for_component, this does not include transitive
76
+ # controller dependencies because generated controller manifests already
77
+ # inline those files via Sprockets require directives.
78
+ def resolve_bundles_for_component_direct(component_name)
79
+ name = component_name.to_s
80
+ return [] if name.empty?
81
+
82
+ config = configuration
83
+ maps = component_maps(config)
84
+ root_bundle = maps[:symbol_to_bundle][name]
85
+ return [] unless root_bundle
86
+
87
+ bundles = []
88
+ shared = resolve_bundle_reference(config, config.shared_bundle)
89
+ bundles << shared if shared
90
+
91
+ root = resolve_bundle_reference(config, root_bundle)
92
+ bundles << root if root && !bundles.include?(root)
93
+
94
+ bundles
69
95
  end
70
96
 
71
97
  # Resolve all controller bundles needed for a React component symbol.
@@ -220,3 +246,4 @@ module ReactManifest
220
246
  end
221
247
  end
222
248
  end
249
+ # rubocop:enable Metrics/ModuleLength
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.19
4
+ version: 0.2.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan