@@ -15,21 +15,26 @@ def self.components_base_dir
1515 # Initialize Component Framework
1616 #
1717 # @param application [Rails::Application] the application class
18- # @param assets_pipeline [bool] specifies whether Sprockets asset_pipeline should be configured for components
1918 # @param verbose [bool] allows to show components initialization log, default `false`
20- def self . initialize ( application , assets_pipeline : true , verbose : false )
19+ def self . initialize ( application , verbose : false )
2120 @verbose = verbose
2221
23- # patch Rails
24- require "component/framework/railtie"
25-
2622 log ( "Components Initialization Started" )
2723 log ( "Components Path: #{ components_base_dir } " )
2824
25+ # Do not use "_components" folder as a part of class namespace, so file like:
26+ # components/clients/_components/jon_doe/core/my_class.rb will define a class name:
27+ # Clients::JonDoe::MyClass
28+ Rails . autoloaders . main . collapse ( "components/*/_components" )
29+
30+ # Instruct autoloader not to load route files, they will be loaded by rails automatically
31+ Rails . autoloaders . main . ignore ( "#{ components_base_dir } /**/routes.rb" )
32+
2933 # add eager and autoload path that will allow to eager load and resolve components with namespaces.
30- application . config . paths . add components_base_dir . to_s , eager_load : true
34+ application . config . autoload_paths += [ components_base_dir . to_s ]
35+ application . config . eager_load_paths += [ components_base_dir . to_s ]
3136
32- log ( "Discovered Components: #{ get_component_names . join ( ", " ) } " )
37+ log ( "Discovered Components: #{ get_components . map { | c | c [ :name ] } . join ( ", " ) } " )
3338
3439 log ( "Register DB Migrations" )
3540 _get_component_migrations_paths . each { |path | application . config . paths [ "db/migrate" ] . push ( path ) }
@@ -39,9 +44,18 @@ def self.initialize(application, assets_pipeline: true, verbose: false)
3944
4045 log ( "Register Components Helpers" )
4146 application . config . paths [ "app/helpers" ] . unshift ( *_get_component_helpers_paths )
42-
43- if assets_pipeline
44- _initialize_assets_pipeline ( application )
47+ application . config . autoload_paths += _get_component_helpers_paths
48+
49+ # Register legacy directories under modules
50+ log ( "Register legacy directories under modules" )
51+ get_components . each do |component_info |
52+ # "_legacy_models" is a special sub directory under each component that is added to load paths so it can have
53+ # classes without namespaces. File in components/clients/_legacy_models/client.rb will define class "Client"
54+ legacy_dir_path = component_info [ :path ] + "/_legacy_models"
55+ if Dir . exist? ( legacy_dir_path )
56+ application . config . autoload_paths << legacy_dir_path
57+ application . config . eager_load_paths << legacy_dir_path
58+ end
4559 end
4660
4761 # Initialize components
@@ -52,7 +66,11 @@ def self.initialize(application, assets_pipeline: true, verbose: false)
5266 application . initializer :initialize_components , group : :all do |app |
5367 components = Component ::Framework . get_component_modules ( load_initializers : true )
5468 Component ::Framework . log ( "Initialize Components" )
55- components . each { |component | component . send ( "init" ) if component . respond_to? ( "init" ) }
69+ components . each do |component |
70+ if component . const_defined? ( :Initialize )
71+ component . const_get ( :Initialize ) . try ( :init )
72+ end
73+ end
5674 end
5775
5876 # Post initialization of components
@@ -63,20 +81,57 @@ def self.initialize(application, assets_pipeline: true, verbose: false)
6381 components = get_component_modules
6482
6583 log ( "Post-Initialize Components" )
66- components . each { |component | component . send ( "ready" ) if component . respond_to? ( "ready" ) }
84+ components . each do |component |
85+ if component . const_defined? ( :Initialize )
86+ component . const_get ( :Initialize ) . try ( :ready )
87+ end
88+ end
6789
6890 log ( "Components Initialization Done" )
6991 end
7092
93+ if Rails . env . development?
94+ # In development we use Autoreload Rails feature
95+ # it will "unload" all classes/constants on any code change
96+ # this leads to breaking all cross-component initialization logic (e.g. changes subscriptions will be lost)
97+ # so we reinitialize components in case autoreload happened
98+
99+ application . reloader . to_prepare do
100+ components = Component ::Framework . get_components . reject { |c | c [ :name ] == "tracing" }
101+
102+ initializers = components . filter_map do |component |
103+ next if !File . exist? ( "#{ component [ :path ] } /initialize.rb" ) # nothing to reload
104+
105+ component_module = Object . const_get ( component [ :name ] . camelize ) # get latest defined constant
106+ # "to_prepare" callback may run multiple times during single code reload,
107+ # we have to check whether we run already
108+ if component_module . const_defined? ( :Initialize )
109+ break # already initialized
110+ end
111+
112+ require "#{ component [ :path ] } /initialize.rb" # initializers are not managed by zeitwerk, we have to require
113+ component_module . const_get ( :Initialize )
114+ end
115+
116+ # Re-run initializers
117+ initializers &.each { |initializer | initializer . init if initializer . respond_to? ( :init ) }
118+ initializers &.each { |initializer | initializer . ready if initializer . respond_to? ( :ready ) }
119+ end
120+ end
121+
71122 log ( "Configuration Finished" )
72123 end
73124
74- # List of component names
75- #
76- # @return [Array<string>] List of component names
77- def self . get_component_names
78- Dir . entries ( components_base_dir )
79- . select { |entry | ( entry !="." && entry != ".." ) and File . directory? components_base_dir . join ( entry ) } . sort
125+
126+ # List of components
127+ def self . get_components
128+ directories = Dir [ "#{ components_base_dir } /*" ] + Dir [ "#{ components_base_dir } /**/_components/*" ]
129+ @get_components ||= directories . sort . map do |full_path |
130+ {
131+ name : full_path . split ( /\/ _?components\/ / ) . last ,
132+ path : full_path
133+ }
134+ end
80135 end
81136
82137
@@ -89,7 +144,7 @@ def self.get_component_modules(load_initializers: false)
89144 Component ::Framework . _load_components_initializers
90145 end
91146
92- get_component_names . map { |name | component_module_by_name ( name ) }
147+ get_components . map { |component | component_module_by_name ( component [ : name] ) }
93148 end
94149
95150
@@ -109,66 +164,18 @@ def self.component_module_by_name(name)
109164 private
110165
111166
112- def self . _initialize_assets_pipeline ( application )
113-
114- log ( "Register Components Assets" )
115- application . config . assets . paths += _get_component_assets_paths
116-
117-
118- # We need to override SassC configuration
119- # so we need to register after SassC app.config.assets.configure
120- application . initializer :setup_component_sass , group : :all , after : :setup_sass do |app |
121-
122- require "component/framework/directive_processor"
123-
124- app . config . assets . configure do |sprockets_env |
125- Component ::Framework . log ( "Register assets directive processors" )
126- sprockets_env . unregister_preprocessor "application/javascript" , Sprockets ::DirectiveProcessor
127- sprockets_env . unregister_preprocessor "text/css" , Sprockets ::DirectiveProcessor
128-
129- sprockets_env . register_preprocessor "application/javascript" , Component ::Framework ::DirectiveProcessor
130- sprockets_env . register_preprocessor "text/css" , Component ::Framework ::DirectiveProcessor
131-
132- if Component ::Framework . sass_present?
133- require "component/framework/sass_importer"
134- Component ::Framework . log ( "Add .scss `@import all-components` support" )
135- sprockets_env . register_transformer "text/scss" , "text/css" , Component ::Framework ::ScssTemplate . new
136- if sprockets_env . respond_to? ( :register_engine )
137- sprockets_env . register_engine ( ".scss" , Component ::Framework ::ScssTemplate , { silence_deprecation : true } )
138- else
139- sprockets_env . register_mime_type "text/scss" , extensions : [ ".scss" ] , charset : :css
140- sprockets_env . register_preprocessor "text/scss" , Component ::Framework ::ScssTemplate
141- end
142- end
143- end
144- end
145- end
146-
147-
148167 def self . _get_component_migrations_paths
149168 # All migrations paths under /components folder
150- Dir . glob ( components_base_dir . join ( "**/migrations" ) )
169+ get_components . filter_map do |component |
170+ path = component [ :path ] + "/migrations"
171+ Dir . exist? ( path ) ? path : nil
172+ end
151173 end
152174
153175
154176 def self . _get_component_helpers_paths
155177 # All helpers paths under /components folder
156- Dir . glob ( components_base_dir . join ( "**/helpers" ) )
157- end
158-
159-
160- def self . _get_component_assets_paths
161-
162- # All stylesheets paths under /components folder
163- styles = Dir . glob ( components_base_dir . join ( "**/assets/stylesheets" ) ) . sort
164-
165- # All javascripts paths under /components folder
166- scripts = Dir . glob ( components_base_dir . join ( "**/assets/javascripts" ) ) . sort
167-
168- # All images paths under /components folder
169- images = Dir . glob ( components_base_dir . join ( "**/assets/images" ) ) . sort
170-
171- return styles + scripts + images
178+ Dir . glob ( components_base_dir . join ( "**/helpers" ) ) . reject { |path | path . include? ( "test/" ) }
172179 end
173180
174181
@@ -180,20 +187,15 @@ def self._get_component_routing_paths
180187
181188 def self . _load_components_initializers
182189 # initializers are optional, so ignore the missing ones
183- get_component_names . each do |name |
184- begin
185- require_dependency ( components_base_dir . join ( "#{ name } /initialize" ) )
186- rescue LoadError
187- end
188- end
189- end
190+ get_components . each do |component_info |
191+ # Define module root
192+ Object . const_set ( component_info [ :name ] . camelize , Module . new )
190193
191-
192- def self . sass_present?
193- defined? ( SassC )
194+ initializer_path = component_info [ :path ] + "/initialize.rb"
195+ require initializer_path if File . exist? ( initializer_path )
196+ end
194197 end
195198
196-
197199 def self . log ( message )
198200 return unless @verbose
199201
0 commit comments