react-manifest-rails 0.2.15 → 0.2.16
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/lib/react_manifest/configuration.rb +23 -0
- data/lib/react_manifest/generator.rb +54 -11
- data/lib/react_manifest/scanner.rb +57 -15
- data/lib/react_manifest/version.rb +1 -1
- data/lib/react_manifest.rb +12 -7
- 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: fe8b2c7c5aa432ddc840c97ed52cefc7672d5d46ee6abc07e37cf20d9f6032ab
|
|
4
|
+
data.tar.gz: 16448f82cee7e775111cc883820c14ff37065382452f90acd2b76abf33f807c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 841fadadd182db285f3528d571bc0210178233d6da3854da67c3dc70b159110a08744e98bf52547c7f6358a3eed09d5a2ee78fed91103cdd5afa3707b0929f9b
|
|
7
|
+
data.tar.gz: 8352ee157c4288263bee93f86bcf01d718ef1794990f2dc76b441a9e0911b50311d12b9404eb9949f3353a2646dc350dc4a1b24e32a96b4802d6f64122dadbc1
|
|
@@ -54,6 +54,27 @@ module ReactManifest
|
|
|
54
54
|
# Independent from Rails.logger output.
|
|
55
55
|
attr_accessor :stdout_logging
|
|
56
56
|
|
|
57
|
+
# Explicit symbol-to-require-path mapping for external globals.
|
|
58
|
+
# Use for third-party libraries that export a PascalCase or camelCase symbol
|
|
59
|
+
# not located inside ux_root (e.g. MiniSearch, Chart.js wrappers).
|
|
60
|
+
#
|
|
61
|
+
# Keys are exact symbol names (case-sensitive); values are the Sprockets
|
|
62
|
+
# require path that should be emitted into the controller manifest.
|
|
63
|
+
#
|
|
64
|
+
# Example:
|
|
65
|
+
# config.external_providers = { "MiniSearch" => "mini-search", "axios" => "vendor/axios" }
|
|
66
|
+
attr_accessor :external_providers
|
|
67
|
+
|
|
68
|
+
# Extra root directories (absolute or relative to Rails.root) to scan for
|
|
69
|
+
# symbol definitions outside of ux_root. Symbols found here are added to
|
|
70
|
+
# the shared symbol index and will trigger an include when used by a
|
|
71
|
+
# controller. Empty by default; files in these dirs are subject to the
|
|
72
|
+
# same +exclude_paths+ and +extensions+ filters.
|
|
73
|
+
#
|
|
74
|
+
# Example:
|
|
75
|
+
# config.external_roots = ["app/assets/javascripts/vendor_components"]
|
|
76
|
+
attr_accessor :external_roots
|
|
77
|
+
|
|
57
78
|
def initialize
|
|
58
79
|
@ux_root = "app/assets/javascripts/ux"
|
|
59
80
|
@app_dir = "app"
|
|
@@ -68,6 +89,8 @@ module ReactManifest
|
|
|
68
89
|
@dry_run = false
|
|
69
90
|
@verbose = false
|
|
70
91
|
@stdout_logging = true
|
|
92
|
+
@external_providers = {}
|
|
93
|
+
@external_roots = []
|
|
71
94
|
end
|
|
72
95
|
|
|
73
96
|
def dry_run?
|
|
@@ -84,10 +84,11 @@ module ReactManifest
|
|
|
84
84
|
def build_controller(ctrl, controller_context)
|
|
85
85
|
lines = header_lines
|
|
86
86
|
dep_requires = controller_dependency_requires(ctrl[:bundle_name], controller_context)
|
|
87
|
+
ext_reqs = controller_context[:external_requires].fetch(ctrl[:bundle_name], Set.new).to_a.sort
|
|
87
88
|
|
|
88
89
|
files = js_files_in(ctrl[:path])
|
|
89
90
|
own_requires = files.map { |f| relative_require_path(f) }
|
|
90
|
-
all_requires = (dep_requires + own_requires).uniq
|
|
91
|
+
all_requires = (dep_requires + ext_reqs + own_requires).uniq
|
|
91
92
|
|
|
92
93
|
if all_requires.empty?
|
|
93
94
|
lines << "// (no JSX files found in #{ctrl[:name]}/)"
|
|
@@ -101,8 +102,11 @@ module ReactManifest
|
|
|
101
102
|
def build_controller_context(controller_dirs)
|
|
102
103
|
bundle_files = {}
|
|
103
104
|
symbol_to_bundle = {}
|
|
105
|
+
external_symbol_to_require = {}
|
|
104
106
|
dependencies = Hash.new { |h, k| h[k] = Set.new }
|
|
107
|
+
external_requires = Hash.new { |h, k| h[k] = Set.new }
|
|
105
108
|
|
|
109
|
+
# Index controller-defined symbols for cross-app detection
|
|
106
110
|
controller_dirs.each do |ctrl|
|
|
107
111
|
bundle_name = ctrl[:bundle_name]
|
|
108
112
|
files = js_files_in(ctrl[:path])
|
|
@@ -117,20 +121,39 @@ module ReactManifest
|
|
|
117
121
|
end
|
|
118
122
|
end
|
|
119
123
|
|
|
124
|
+
# Index symbols from external_roots dirs
|
|
125
|
+
@config.external_roots.each do |root_path|
|
|
126
|
+
abs_root = abs_external_root(root_path)
|
|
127
|
+
external_js_files_in(abs_root).each do |file_path|
|
|
128
|
+
req_path = relative_require_path(file_path)
|
|
129
|
+
extract_defined_symbols(file_path).each do |sym|
|
|
130
|
+
external_symbol_to_require[sym] ||= req_path
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Explicit external_providers win over scanned roots on symbol conflicts
|
|
136
|
+
@config.external_providers.each do |sym, req_path|
|
|
137
|
+
external_symbol_to_require[sym] = req_path
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Compute per-bundle cross-app and external dependencies
|
|
120
141
|
bundle_files.each do |bundle_name, files|
|
|
121
142
|
files.each do |file_path|
|
|
122
143
|
extract_used_component_symbols(file_path).each do |sym|
|
|
123
144
|
dep_bundle = symbol_to_bundle[sym]
|
|
124
|
-
|
|
145
|
+
dependencies[bundle_name] << dep_bundle if dep_bundle && dep_bundle != bundle_name
|
|
125
146
|
|
|
126
|
-
|
|
147
|
+
req_path = external_symbol_to_require[sym]
|
|
148
|
+
external_requires[bundle_name] << req_path if req_path
|
|
127
149
|
end
|
|
128
150
|
end
|
|
129
151
|
end
|
|
130
152
|
|
|
131
153
|
{
|
|
132
154
|
bundle_files: bundle_files,
|
|
133
|
-
dependencies: dependencies
|
|
155
|
+
dependencies: dependencies,
|
|
156
|
+
external_requires: external_requires
|
|
134
157
|
}
|
|
135
158
|
end
|
|
136
159
|
|
|
@@ -290,14 +313,19 @@ module ReactManifest
|
|
|
290
313
|
|
|
291
314
|
def extract_used_component_symbols(file_path)
|
|
292
315
|
content = File.read(file_path, encoding: "utf-8")
|
|
293
|
-
symbols = []
|
|
294
316
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
# Collect locally-defined symbols to avoid self-reference false positives
|
|
318
|
+
local_syms = Set.new
|
|
319
|
+
ReactManifest::Scanner::DEFINITION_PATTERNS.each do |pattern|
|
|
320
|
+
content.scan(pattern) { |m| local_syms << m[0] }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
symbols = []
|
|
324
|
+
content.scan(ReactManifest::Scanner::PASCAL_TOKEN_PATTERN) do |m|
|
|
325
|
+
symbols << m[0] unless local_syms.include?(m[0])
|
|
326
|
+
end
|
|
327
|
+
content.scan(ReactManifest::Scanner::HOOK_TOKEN_PATTERN) do |m|
|
|
328
|
+
symbols << m[0] unless local_syms.include?(m[0])
|
|
301
329
|
end
|
|
302
330
|
|
|
303
331
|
symbols.uniq
|
|
@@ -305,6 +333,21 @@ module ReactManifest
|
|
|
305
333
|
[]
|
|
306
334
|
end
|
|
307
335
|
|
|
336
|
+
def external_js_files_in(dir)
|
|
337
|
+
return [] unless Dir.exist?(dir)
|
|
338
|
+
|
|
339
|
+
Dir.glob(File.join(dir, "**", @config.extensions_glob))
|
|
340
|
+
.reject { |f| File.directory?(f) }
|
|
341
|
+
.reject { |f| excluded_path?(f) }
|
|
342
|
+
.sort
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def abs_external_root(path)
|
|
346
|
+
return path if Pathname.new(path).absolute?
|
|
347
|
+
|
|
348
|
+
Rails.root.join(path).to_s
|
|
349
|
+
end
|
|
350
|
+
|
|
308
351
|
def auto_generated?(path)
|
|
309
352
|
# Avoid TOCTOU: don't check existence separately — just attempt the read
|
|
310
353
|
# and treat a missing/unreadable file as not auto-generated.
|
|
@@ -38,15 +38,13 @@ module ReactManifest
|
|
|
38
38
|
/^export\s+class\s+([A-Z][A-Za-z0-9_]*)\s*(?:extends|\{)/ # export class Foo
|
|
39
39
|
].freeze
|
|
40
40
|
|
|
41
|
-
# Patterns to detect usage in controller files
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ARRAY_COMPONENT_LIST_PATTERN = /\[\s*([A-Z][A-Za-z0-9_]*(?:\s*,\s*[A-Z][A-Za-z0-9_]*)*\s*,?)\s*\]/
|
|
47
|
-
HOOK_CALL_PATTERN = /\b(use[A-Z][A-Za-z0-9_]*)\s*\(/
|
|
41
|
+
# Patterns to detect usage in controller files.
|
|
42
|
+
# Token-based patterns match any identifier occurrence regardless of syntax
|
|
43
|
+
# context (JSX, constructor, assignment, array, function argument, etc.).
|
|
44
|
+
PASCAL_TOKEN_PATTERN = /\b([A-Z][A-Za-z0-9_]*)\b/
|
|
45
|
+
HOOK_TOKEN_PATTERN = /\b(use[A-Z][A-Za-z0-9_]*)\b/
|
|
48
46
|
# Lib calls matched against known lib symbols to reduce false positives
|
|
49
|
-
LIB_CALL_PATTERN
|
|
47
|
+
LIB_CALL_PATTERN = /\b([a-z][A-Za-z0-9_]{2,})\s*\(/
|
|
50
48
|
|
|
51
49
|
# Common JS built-ins to exclude from lib-call matching
|
|
52
50
|
JS_BUILTINS = %w[
|
|
@@ -68,7 +66,7 @@ module ReactManifest
|
|
|
68
66
|
warnings = []
|
|
69
67
|
symbol_index = {}
|
|
70
68
|
|
|
71
|
-
# Phase
|
|
69
|
+
# Phase 1a: index symbols from shared dirs
|
|
72
70
|
classification.shared_dirs.each do |shared_dir|
|
|
73
71
|
js_files_in(shared_dir[:path]).each do |file_path|
|
|
74
72
|
relative = relative_require_path(file_path)
|
|
@@ -83,6 +81,23 @@ module ReactManifest
|
|
|
83
81
|
end
|
|
84
82
|
end
|
|
85
83
|
|
|
84
|
+
# Phase 1b: index symbols from external_roots dirs
|
|
85
|
+
@config.external_roots.each do |root_path|
|
|
86
|
+
abs_root = abs_external_root(root_path)
|
|
87
|
+
js_files_in(abs_root).each do |file_path|
|
|
88
|
+
relative = relative_require_path(file_path)
|
|
89
|
+
symbols = extract_definitions(file_path)
|
|
90
|
+
symbols.each do |sym|
|
|
91
|
+
symbol_index[sym] ||= relative
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Phase 1c: add explicit external_providers (highest precedence — wins on conflict)
|
|
97
|
+
@config.external_providers.each do |sym, require_path|
|
|
98
|
+
symbol_index[sym] = require_path
|
|
99
|
+
end
|
|
100
|
+
|
|
86
101
|
$stdout.puts "[ReactManifest] Shared symbol index: #{symbol_index.size} symbols indexed" if @config.verbose?
|
|
87
102
|
|
|
88
103
|
# Phase 2: scan controller dirs for usage
|
|
@@ -190,16 +205,37 @@ module ReactManifest
|
|
|
190
205
|
def extract_used_shared_paths(content, symbol_index)
|
|
191
206
|
used = Set.new
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
208
|
+
# Collect locally-defined symbols so we don't count a file as "using"
|
|
209
|
+
# its own exports (avoid self-referencing false positives).
|
|
210
|
+
local_syms = Set.new
|
|
211
|
+
DEFINITION_PATTERNS.each do |pattern|
|
|
212
|
+
content.scan(pattern) { |m| local_syms << m[0] }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# PascalCase token scan: catches JSX elements, constructors (new Foo()),
|
|
216
|
+
# prop values, array entries, function arguments, assignments, etc.
|
|
217
|
+
content.scan(PASCAL_TOKEN_PATTERN) do |match|
|
|
218
|
+
sym = match[0]
|
|
219
|
+
next if local_syms.include?(sym)
|
|
220
|
+
next unless symbol_index.key?(sym)
|
|
221
|
+
|
|
222
|
+
used << symbol_index[sym]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Hook token scan: catches useFoo(...) and bare useFoo references.
|
|
226
|
+
content.scan(HOOK_TOKEN_PATTERN) do |match|
|
|
227
|
+
sym = match[0]
|
|
228
|
+
next if local_syms.include?(sym)
|
|
229
|
+
next unless symbol_index.key?(sym)
|
|
199
230
|
|
|
231
|
+
used << symbol_index[sym]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Lib call scan (lowercase): already filtered to symbol_index keys.
|
|
200
235
|
content.scan(LIB_CALL_PATTERN) do |match|
|
|
201
236
|
sym = match[0]
|
|
202
237
|
next if JS_BUILTINS.include?(sym)
|
|
238
|
+
next if local_syms.include?(sym)
|
|
203
239
|
next unless symbol_index.key?(sym)
|
|
204
240
|
|
|
205
241
|
used << symbol_index[sym]
|
|
@@ -227,5 +263,11 @@ module ReactManifest
|
|
|
227
263
|
end
|
|
228
264
|
end
|
|
229
265
|
end
|
|
266
|
+
|
|
267
|
+
def abs_external_root(path)
|
|
268
|
+
return path if Pathname.new(path).absolute?
|
|
269
|
+
|
|
270
|
+
Rails.root.join(path).to_s
|
|
271
|
+
end
|
|
230
272
|
end
|
|
231
273
|
end
|
data/lib/react_manifest.rb
CHANGED
|
@@ -165,14 +165,19 @@ module ReactManifest
|
|
|
165
165
|
|
|
166
166
|
def extract_used_component_symbols(file_path)
|
|
167
167
|
content = File.read(file_path, encoding: "utf-8")
|
|
168
|
-
symbols = []
|
|
169
168
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
# Collect locally-defined symbols to avoid self-reference false positives
|
|
170
|
+
local_syms = Set.new
|
|
171
|
+
ReactManifest::Scanner::DEFINITION_PATTERNS.each do |pattern|
|
|
172
|
+
content.scan(pattern) { |m| local_syms << m[0] }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
symbols = []
|
|
176
|
+
content.scan(ReactManifest::Scanner::PASCAL_TOKEN_PATTERN) do |m|
|
|
177
|
+
symbols << m[0] unless local_syms.include?(m[0])
|
|
178
|
+
end
|
|
179
|
+
content.scan(ReactManifest::Scanner::HOOK_TOKEN_PATTERN) do |m|
|
|
180
|
+
symbols << m[0] unless local_syms.include?(m[0])
|
|
176
181
|
end
|
|
177
182
|
|
|
178
183
|
symbols.uniq
|