Skip to content

Commit c799412

Browse files
authored
[BMDEV-558] Rails 8 with Zeitwerk, dropped sprockets/assets support (#5)
1 parent 4873d57 commit c799412

File tree

8 files changed

+88
-175
lines changed

8 files changed

+88
-175
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
/spec/reports/
88
/tmp/
99
*.gem
10+
.vscode/

Gemfile

Lines changed: 0 additions & 2 deletions
This file was deleted.

component-framework.gemspec

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,5 @@ Gem::Specification.new do |spec|
1515

1616
spec.files = Dir["README.md", "lib/**/*.rb", "LICENSE.txt"]
1717

18-
spec.add_development_dependency "bundler", "~> 1.16"
19-
spec.add_development_dependency "rake", "~> 10.0"
20-
spec.add_development_dependency "railties", "~> 4.0"
21-
18+
spec.add_development_dependency "rails", "~> 8.0"
2219
end

lib/component/framework.rb

Lines changed: 85 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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

lib/component/framework/directive_processor.rb

Lines changed: 0 additions & 29 deletions
This file was deleted.

lib/component/framework/railtie.rb

Lines changed: 0 additions & 17 deletions
This file was deleted.

lib/component/framework/sass_importer.rb

Lines changed: 0 additions & 39 deletions
This file was deleted.

lib/component/framework/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Component
22
module Framework
3-
VERSION = "0.4.7"
3+
VERSION = "1.0.0"
44
end
55
end

0 commit comments

Comments
 (0)