Skip to content

Commit

Permalink
Override ViewComponent::Base#sidecar_files instead of the compiler.
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeer committed Sep 30, 2024
1 parent cfa2af4 commit 7278a97
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ALPINE_RUBY_VERSION=3.2.2
RAILS_VERSION=7.0.8
VIEW_COMPONENT_VERSION=2.66.0
VIEW_COMPONENT_VERSION=2.74.0
SOLR_PORT=8983
SOLR_URL=http://solr:8983/solr/blacklight-core
SOLR_VERSION=latest
4 changes: 2 additions & 2 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ jobs:
run: bundle exec rake ci
env:
ENGINE_CART_RAILS_OPTIONS: '--skip-keeps --skip-test'
test_vc3:
test_vc2:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: ['3.2']
env:
RAILS_VERSION: 7.0.8
VIEW_COMPONENT_VERSION: ${{ matrix.view_component_version }}
VIEW_COMPONENT_VERSION: "~> 2.74"
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
Expand Down
2 changes: 1 addition & 1 deletion blacklight.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Gem::Specification.new do |s|
s.add_dependency "deprecation"
s.add_dependency "i18n", '>= 1.7.0' # added named parameters
s.add_dependency "ostruct", '>= 0.3.2'
s.add_dependency "view_component", '>= 2.66', '< 4'
s.add_dependency "view_component", '>= 2.74', '< 4'
s.add_dependency 'hashdiff'
s.add_dependency "zeitwerk"

Expand Down
57 changes: 10 additions & 47 deletions lib/blacklight/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,20 @@
module Blacklight
class Component < ViewComponent::Base
class << self
# Workaround for https://github.com/ViewComponent/view_component/issues/1565
def config
@config ||= ViewComponent::Config.defaults.merge(ViewComponent::Base.config)
end
alias upstream_sidecar_files sidecar_files

# rubocop:disable Naming/MemoizedInstanceVariableName
def compiler
@__vc_compiler ||= EngineCompiler.new(self)
def reset_compiler!
@__vc_compiler = nil
end
# rubocop:enable Naming/MemoizedInstanceVariableName

alias sidecar_files _sidecar_files unless ViewComponent::Base.respond_to? :sidecar_files
end

EXCLUDE_VARIABLES = [
:@lookup_context, :@view_renderer, :@view_flow, :@view_context,
:@tag_builder, :@current_template,
:@__vc_set_slots, :@__vc_original_view_context,
:@__vc_variant, :@__vc_content_evaluated,
:@__vc_render_in_block, :@__vc_content, :@__vc_helpers
].freeze

def inspect
# Exclude variables added by render_in
render_variables = instance_variables - EXCLUDE_VARIABLES
fields = render_variables.map { |ivar| "#{ivar}:#{instance_variable_get(ivar).inspect}" }.join(', ')
"#<#{self.class.name}:#{object_id} #{fields}>"
end

class EngineCompiler < ::ViewComponent::Compiler
# ViewComponent::Compiler locates and caches templates from sidecar files to the component source file.
# While this is sensible in a Rails application, it prevents component templates defined in an Engine
# from being overridden by an installing application without subclassing the component, which may also
# require modifying any partials rendering the component. This subclass of compiler overrides the template
# location algorithm to take the sidecar file names from the Engine, but look to see if a file of the
# same name existing in the installing application (ie, under Rails.root). If the latter exists, this
# compiler will cache that template instead of the engine-defined file; if not, the compiler will fall
# back to the engine-defined file.
def templates
@templates ||= begin
extensions = ActionView::Template.template_handler_extensions

component_class.sidecar_files(extensions).each_with_object([]) do |path, memo|
pieces = File.basename(path).split(".")
app_path = "#{Rails.root}/#{path.slice(path.index(component_class.view_component_path)..-1)}"
def sidecar_files(*args, **kwargs)
upstream_sidecar_files(*args, **kwargs).map do |path|
app_path = Rails.root.join(path.slice(path.index(view_component_path)..-1).to_s).to_s

memo << {
path: File.exist?(app_path) ? app_path : path,
variant: pieces.second.split("+").second&.to_sym,
handler: pieces.last
}
if File.exist?(app_path)
app_path
else
path
end
end
end
Expand Down
59 changes: 32 additions & 27 deletions spec/lib/blacklight/component_spec.rb
Original file line number Diff line number Diff line change
@@ -1,43 +1,48 @@
# frozen_string_literal: true

RSpec.describe Blacklight::Component do
let(:component_class) { Blacklight::DocumentTitleComponent }
RSpec.describe Blacklight::Component, type: :component do
let(:component_class) { Blacklight::System::ModalComponent }

context "subclassed" do
it "returns our Compiler implementation" do
expect(component_class.ancestors).to include described_class
expect(component_class.compiler).to be_a Blacklight::Component::EngineCompiler
before do
component_class.reset_compiler!
ViewComponent::CompileCache.invalidate!

component_class.class_eval do
undef :call if method_defined?(:call)
end
end

describe Blacklight::Component::EngineCompiler do
subject(:compiler) { described_class.new(component_class) }
after do
component_class.reset_compiler!
ViewComponent::CompileCache.invalidate!

let(:original_compiler) { ViewComponent::Compiler.new(component_class) }
let(:original_path) { original_compiler.send(:templates).first[:path] }
let(:resolved_path) { compiler.templates.first[:path] }
component_class.class_eval do
undef :call if method_defined?(:call)
end
end

context "without overrides" do
it "links to engine template" do
expect(resolved_path).not_to include(".internal_test_app")
expect(resolved_path).to eql(original_path)
end
context "without overrides" do
it "renders the engine template" do
render_inline(component_class.new)
expect(page).to have_css('.modal-header')
end
end

context "with overrides" do
let(:path_match) do
Regexp.new(Regexp.escape(File.join(".internal_test_app", component_class.view_component_path)))
context "with overrides" do
around do |ex|
FileUtils.mkdir_p(Rails.root.join('app/components/blacklight/system'))
Rails.root.join("app/components/blacklight/system/modal_component.html.erb").open("w") do |f|
f.puts '<div class="custom-modal">Overridden</div>'
end

before do
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with(path_match).and_return(true)
end
ex.run
ensure
Rails.root.join('app/components/blacklight/system/modal_component.html.erb').unlink
end

it "links to application template" do
expect(resolved_path).to include(".internal_test_app")
expect(resolved_path).not_to eql(original_path)
end
it "renders to application template" do
render_inline(component_class.new)
expect(page).to have_css('.custom-modal')
end
end
end

0 comments on commit 7278a97

Please sign in to comment.