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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +20 -1
- data/lib/react_manifest/generator.rb +26 -2
- data/lib/react_manifest/version.rb +1 -1
- data/lib/react_manifest/view_helpers.rb +14 -3
- data/lib/react_manifest.rb +28 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a68f022a75a440c68e741e931c5453e6c96c126380356757825a984de94e2143
|
|
4
|
+
data.tar.gz: 52d20e65e685a1b82d833ee650677c91a6fc5faa9e7b32c1075d85a2e6f53fa7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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`:
|
|
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.
|
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
data/lib/react_manifest.rb
CHANGED
|
@@ -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
|
-
|
|
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
|