FIX: Support multi-file stylesheets in theme components (#7950)

FIX: Support multi-file stylesheets in theme components (#7950)

diff --git a/lib/stylesheet/importer.rb b/lib/stylesheet/importer.rb
index 473bec8..728731e 100644
--- a/lib/stylesheet/importer.rb
+++ b/lib/stylesheet/importer.rb
@@ -16,7 +16,7 @@ module Stylesheet
     end
 
     register_import "theme_field" do
-      Import.new("#{theme_dir}/theme_field.scss", source: @theme_field)
+      Import.new("#{theme_dir(@theme_id)}/theme_field.scss", source: @theme_field)
     end
 
     register_import "plugins" do
@@ -101,6 +101,7 @@ module Stylesheet
         # make up an id so other stuff does not bail out
         @theme_id = @theme.id || -1
       end
+      @importable_theme_fields = {}
     end
 
     def import_files(files)
@@ -140,15 +141,19 @@ module Stylesheet
       @theme == :nil ? nil : @theme
     end
 
-    def theme_dir
-      "theme_#{theme.id}"
+    def theme_dir(import_theme_id)
+      "theme_#{import_theme_id}"
     end
 
-    def importable_theme_fields
-      return {} unless theme
-      @importable_theme_fields ||= begin
+    def extract_theme_id(path)
+      path[/^theme_([0-9]+)\//, 1]
+    end
+
+    def importable_theme_fields(import_theme_id)
+      return {} unless theme && import_theme = Theme.find(import_theme_id)
+      @importable_theme_fields[import_theme_id] ||= begin
         hash = {}
-        @theme.theme_fields.where(target_id: Theme.targets[:extra_scss]).each do |field|
+        import_theme.theme_fields.where(target_id: Theme.targets[:extra_scss]).each do |field|
           hash[field.name] = field.value
         end
         hash
@@ -156,18 +161,18 @@ module Stylesheet
     end
 
     def match_theme_import(path, parent_path)
-      # Only allow importing theme stylesheets from within other theme stylesheets
-      return false unless theme && parent_path.start_with?("#{theme_dir}/")
+      # Only allow importing theme stylesheets from within stylesheets in the same theme
+      return false unless theme && import_theme_id = extract_theme_id(parent_path) # Could be a child theme
       parent_dir, _ = File.split(parent_path)
 
       # Could be relative to the importing file, or relative to the root of the theme directory
-      search_paths = [parent_dir, theme_dir].uniq
+      search_paths = [parent_dir, theme_dir(import_theme_id)].uniq
       search_paths.each do |search_path|
         resolved = Pathname.new("#{search_path}/#{path}").cleanpath.to_s # Remove unnecessary ./ and ../
-        next unless resolved.start_with?("#{theme_dir}/")
-        resolved.sub!("#{theme_dir}/", "")
-        if importable_theme_fields.keys.include?(resolved)
-          return resolved
+        next unless resolved.start_with?("#{theme_dir(import_theme_id)}/")
+        resolved_within_theme = resolved.sub(/^theme_[0-9]+\//, "")
+        if importable_theme_fields(import_theme_id).keys.include?(resolved_within_theme)
+          return resolved, importable_theme_fields(import_theme_id)[resolved_within_theme]
         end
       end
       false
@@ -189,8 +194,8 @@ module Stylesheet
         end
       elsif callback = Importer.special_imports[asset]
         instance_eval(&callback)
-      elsif resolved = match_theme_import(asset, parent_path)
-        Import.new("#{theme_dir}/#{resolved}", source: importable_theme_fields[resolved])
+      elsif (path, source = match_theme_import(asset, parent_path))
+        Import.new(path, source: source)
       else
         Import.new(asset + ".scss")
       end
diff --git a/spec/components/stylesheet/importer_spec.rb b/spec/components/stylesheet/importer_spec.rb
index 8f0d8b5..f143bd0 100644
--- a/spec/components/stylesheet/importer_spec.rb
+++ b/spec/components/stylesheet/importer_spec.rb
@@ -63,11 +63,20 @@ describe Stylesheet::Importer do
 
   context "extra_scss" do
     let(:scss) { "body { background: red}" }
+    let(:child_scss) { "body { background: green}" }
+
     let(:theme) { Fabricate(:theme).tap { |t|
       t.set_field(target: :extra_scss, name: "my_files/magic", value: scss)
       t.save!
     }}
 
+    let(:child_theme) { Fabricate(:theme).tap { |t|
+      t.component = true
+      t.set_field(target: :extra_scss, name: "my_files/moremagic", value: child_scss)
+      t.save!
+      theme.add_child_theme!(t)
+    }}
+
     let(:importer) { described_class.new(theme: theme) }
 
     it "should be able to import correctly" do
@@ -105,6 +114,13 @@ describe Stylesheet::Importer do
           "./magic",
           "theme_#{theme.id}/my_files/myfile.scss"
         ).source).to eq(scss)
+
+      # Import within a child theme
+      expect(
+        importer.imports(
+          "my_files/moremagic",
+          "theme_#{child_theme.id}/theme_field.scss"
+        ).source).to eq(child_scss)
     end
 
   end

GitHub sha: 933d2798

1 Like

This commit has been mentioned on Discourse Meta. There might be relevant details there: