Module: ReactManifest

Defined in:
lib/react_manifest.rb,
lib/react_manifest/logging.rb,
lib/react_manifest/railtie.rb,
lib/react_manifest/scanner.rb,
lib/react_manifest/version.rb,
lib/react_manifest/watcher.rb,
lib/react_manifest/reporter.rb,
lib/react_manifest/generator.rb,
lib/react_manifest/path_utils.rb,
lib/react_manifest/view_helpers.rb,
lib/react_manifest/ast_extractor.rb,
lib/react_manifest/configuration.rb,
lib/react_manifest/dependency_map.rb,
lib/react_manifest/layout_patcher.rb,
lib/react_manifest/tree_classifier.rb,
lib/react_manifest/symbol_extractor.rb,
lib/react_manifest/gitignore_patcher.rb,
lib/react_manifest/application_analyzer.rb,
lib/react_manifest/application_migrator.rb,
lib/react_manifest/sprockets_manifest_patcher.rb

Overview

rubocop:disable Metrics/ModuleLength

Defined Under Namespace

Modules: AstExtractor, Logging, PathUtils, SymbolExtractor, ViewHelpers, Watcher Classes: ApplicationAnalyzer, ApplicationMigrator, Configuration, DependencyMap, Generator, GitignorePatcher, LayoutPatcher, Railtie, Reporter, Scanner, SprocketsManifestPatcher, TreeClassifier

Constant Summary collapse

VERSION =
"0.3.0".freeze

Class Method Summary collapse

Class Method Details

.configurationObject



25
26
27
# File 'lib/react_manifest.rb', line 25

def configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



29
30
31
# File 'lib/react_manifest.rb', line 29

def configure
  yield configuration
end

.invalidate_component_maps!Object

Drop the cached component-symbol -> bundle map used by resolve_bundle_for_component / resolve_bundles_for_component(_direct). Unlike reset!, this does NOT touch the current Configuration — safe to call whenever ux/ files change (e.g. from the file watcher) so newly added controllers/components are picked up without losing app config.



44
45
46
# File 'lib/react_manifest.rb', line 44

def invalidate_component_maps!
  @component_maps_cache = nil
end

.reconcile_gitignore!(config = configuration) ⇒ Object

Ensure the manifest dir is gitignored (and .keep present). Honors config.manage_gitignore. Returns true if it appended the .gitignore entry. Best-effort: a filesystem error is logged and swallowed (never breaks boot).



78
79
80
81
82
83
84
85
# File 'lib/react_manifest.rb', line 78

def reconcile_gitignore!(config = configuration)
  return false unless config.manage_gitignore?

  GitignorePatcher.new(config).reconcile!
rescue StandardError => e
  Rails.logger&.warn("[ReactManifest] gitignore reconcile skipped: #{e.message}")
  false
end

.reset!Object



33
34
35
36
37
# File 'lib/react_manifest.rb', line 33

def reset!
  @configuration = nil
  @component_maps_cache = nil
  Scanner.clear_cache!
end

.resolve_bundle_for_component(component_name) ⇒ Object

Resolve a controller bundle from a React component symbol.

This is primarily used to support react-rails react_component calls, where the requested component name is known and may not align 1:1 with controller_path-derived bundle names.



92
93
94
# File 'lib/react_manifest.rb', line 92

def resolve_bundle_for_component(component_name)
  resolve_bundles_for_component_direct(component_name).last
end

.resolve_bundles(ctrl_name) ⇒ Object

Returns the ordered list of bundle logical names for a given controller. Used by the react_bundle_tag view helper.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/react_manifest.rb', line 50

def resolve_bundles(ctrl_name)
  config  = configuration
  bundles = []

  # 1. Shared bundle always first
  shared = resolve_bundle_reference(config, config.shared_bundle)
  bundles << shared if shared

  # 2. always_include bundles (e.g. ux_main)
  append_always_include(config, bundles)

  # 3. Controller-specific bundle
  # Try fully-namespaced first: admin/users → ux_admin_users
  # Then drop segments: ux_admin
  controller_candidates(ctrl_name).each do |candidate|
    resolved = resolve_bundle_reference(config, candidate)
    if resolved && !bundles.include?(resolved)
      bundles << resolved
      break
    end
  end

  bundles
end

.resolve_bundles_for_component(component_name) ⇒ Object

Resolve all controller bundles needed for a React component symbol. Includes transitive controller-bundle dependencies inferred from component symbol usage across ux/app/* directories.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/react_manifest.rb', line 130

def resolve_bundles_for_component(component_name)
  name = component_name.to_s
  return [] if name.empty?

  config = configuration
  maps = component_maps(config)
  root_bundle = maps[:symbol_to_bundle][name]
  return [] unless root_bundle

  ordered = []
  visiting = Set.new
  visited = Set.new

  walk = lambda do |bundle_name|
    return if visited.include?(bundle_name) || visiting.include?(bundle_name)

    visiting << bundle_name
    maps[:bundle_dependencies].fetch(bundle_name, Set.new).each { |dep| walk.call(dep) }
    visiting.delete(bundle_name)

    visited << bundle_name
    ordered << bundle_name
  end

  walk.call(root_bundle)

  ordered.filter_map { |bundle_name| resolve_bundle_reference(config, bundle_name) }
end

.resolve_bundles_for_component_direct(component_name) ⇒ Object

Resolve the direct bundle list needed for a React component symbol. Returns shared first (if present), then the component's owning bundle.

Unlike resolve_bundles_for_component, this does not include transitive controller dependencies because generated controller manifests already inline those files via Sprockets require directives.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/react_manifest.rb', line 102

def resolve_bundles_for_component_direct(component_name)
  name = component_name.to_s
  return [] if name.empty?

  config = configuration
  maps = component_maps(config)
  root_bundle = maps[:symbol_to_bundle][name]
  return [] unless root_bundle

  bundles = []
  shared = resolve_bundle_reference(config, config.shared_bundle)
  bundles << shared if shared

  # always_include bundles are delivered on every page. react_component does
  # NOT go through resolve_bundles, so it must emit them here too — this is
  # what makes always_include symbols available without inlining them into
  # controller manifests (which would double-declare, see Generator).
  append_always_include(config, bundles)

  root = resolve_bundle_reference(config, root_bundle)
  bundles << root if root && !bundles.include?(root)

  bundles
end