So I have a file not found problem. I have an engine that works in development mode in the engines test/dummy app, the engine allows the editing of sass variables and stores them in a theme table, the variables are used by a sass partial such as _banner.scss containing variables used in the main stylesheet such as $banner_color which is then imported into the main stylesheet which in turn is precompiled using an initializer in the engine.rb file and inclusion in the app/config/engine_name_manifest.js.
The files are all available in development with the local dummy app but not in the eventual host app due to the assets being compiled.
I have a rake task that takes the data, updates the relevant partial e.g. _banner.scss with the data from the theme table but of course the partials are not not available in a host app as the engine has already compiled them. I'm looking for a solution that will allow me to edit the raw, uncompiled stylesheets then recompile them. Obviously my Capistrano deploy script will need to reapply the stylesheet changes every deployment but that is just a rake task call. What approach should I take? Should I find a way to copy the css files to the host app in an engine initializer? Should I use a different approach entirely, I have started looking at propshaft but that is a massive step to replace sass rails and I'm not sure how that would help
The engine
require "deface"
require 'ccs_cms_admin_dashboard'
require 'ccs_cms_custom_page'
require 'ccs_cms_core'
require 'css_menu'
#require 'tinymce-rails'
require 'delayed_job_active_record'
require 'daemons'
require 'sprockets/railtie'
require 'sassc-rails'
module CcsCms
module PublicTheme
class Engine < ::Rails::Engine
isolate_namespace CcsCms::PublicTheme
paths["app/views"] << "app/views/ccs_cms/public_theme"
initializer "ccs_cms.assets.precompile" do |app|
app.config.assets.precompile = %w( public_theme_manifest.js )
end
initializer :append_migrations do |app|
unless app.root.to_s.match?(root.to_s)
config.paths['db/migrate'].expanded.each do |p|
app.config.paths['db/migrate'] << p
end
end
end
initializer :active_job_setup do |app|
app.config.active_job.queue_adapter = :delayed_job
end
config.to_prepare do
Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
request: false,
view_specs: false,
helper_specs: false,
controller_specs: false,
routing_specs: false
g.fixture_replacement :factory_bot
g.factory_bot dir: 'spec/factories'
end
end
end
end
The Css class that writes the css
class Css
def get_stylesheet_path
Rails.root.join("app/assets/stylesheets/ccs_cms/public_theme")
end
def write_css(theme)
update_css_files_for(theme.banner, '_public_banner.scss', BANNER_ARRAY, BANNER_FIELD_MAP)
update_css_files_for(theme.banner.font, '_public_banner_font.scss', BANNER_FONT_ARRAY, BANNER_FONT_FIELD_MAP)
end
private
def update_css_files_for(model_record_to_use, css_file, array_to_use, field_map)
amended_css = amend_css_for(model_record_to_use, css_file, array_to_use, field_map)
create_css_files_for(css_file, amended_css)
end
def amend_css_for(model_record_to_use, file_name, array_to_use, field_map)
original_css_array = IO.readlines("#{get_stylesheet_path}/#{file_name}")
new_array = []
original_css_array.each do |line|
new_line = line
array_to_use.each do |ma|
if line.start_with?(ma)
field_name = field_map[ma.to_sym]
new_line = ma ": #{model_record_to_use[field_name.to_sym]};"
#puts("@@@@ original line: #{line}, ma: #{ma}, Field name: #{field_name}, value: #{theme[field_name]}")
break
end
end
new_array << new_line
end
new_array
end
# ---- File and I/O Handling ---- #
def create_css_files_for(file_name, css_array)
File.open("#{get_stylesheet_path}/#{file_name}", "w") do |file|
file.puts css_array
end
end
end
CodePudding user response:
Thanks for clarifying. If I understood correctly here my take on it.
partials are not not available in a host app as the engine has already compiled them
Partials are still there, precompilation just outputs *.{css/js}
files into public/assets/
that are declared in app/assets/config/manifest.js
.
To get to engines files, instead of Rails.root
use:
CcsCms::PublicTheme::Engine.root
In Css
class, for example:
def get_stylesheet_path
CcsCms::PublicTheme::Engine.root.join("app/assets/stylesheets/ccs_cms/public_theme")
end
To support changing theme engines. Theme root can be set in an engine initializer to something like Rails.configuration.theme_root
and used in the main app.
Because your theme is also configurable, I think it's better to read theme's original sass files but not modify them, copy them into a tmp folder and update with values from theme table, then output a theme.css
in the main app with sass.
https://sass-lang.com/documentation/cli/dart-sass
# Compiles all Sass
$ sass tmp/theme/application.scss:app/stylesheets/theme.css
Then let Rails take over the precompilation process.
Another option is to have one sass configuration file and only update this file. That way there is no dependency on the file structure of any particular theme.
import 'configuration' // sass variables with values from theme table
import 'banner' // uses sass variables only
...
Also just use css variables, if that's an option, and avoid all of the above complexity; no precompilation, no redeploys when theme table changes.
Update for css variables.
Just so we're on the same page. I meant these css variables: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties. If Internet Explorer is not a priority for you, this is the best solution. The setup is something like this:
<!-- app/views/layouts/application.erb -->
<!-- NOTE: with turbo this loads only once; full page refresh is needed when @theme changes -->
<head>
<style>
:root { --text-color: <%= @theme.text_color %>; }
</style>
<%= stylesheet_link_tag 'application' %>
</head>
/* app/assets/stylesheets/application.css */
p { color: var(--text-color); }
Possible fix to avoid amending css files. Use erb
interpolation inside the sass files. No need to amend every time a theme configuration is changed. In development compiles on the fly. In production it has to be precompiled again when theme configuration is changed; no amending.
// _banner.scss.erb
p { color: <%= Theme.text_color %>; }
You could even use amend_css_for
function to insert literal erb code and save some time. For example
new_line = ma ": <%= Theme.#{model_name}.#{field_name} %>;"
Finally, if you don't want to touch engine files and because these files are not part of the main/host app (as in literally two separate folders in the filesystem). You have to make a copy when amending; read from CcsCms::PublicTheme::Engine.root
write to Rails.root
.
def get_stylesheet_path
CcsCms::PublicTheme::Engine.root.join("app/assets/stylesheets/ccs_cms/public_theme")
end
# but save to main app
def create_css_files_for(file_name, css_array)
File.open("#{Rails.root.join("app/assets/stylesheets/ccs_cms/public_theme")}/#{file_name}", "w") do |file|
file.puts css_array
end
end