mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-12-02 11:59:17 -05:00
e2fb5310d8
When developing plugins it's useful to symlink the to the plugin directory from the discourse directory, since that means the two are separate git repos. However, Dir.glob doesn't by default traverse symlinks. This change means that the SASS compilation caching detects when any of a plugin's files have changed.
135 lines
3.9 KiB
Ruby
135 lines
3.9 KiB
Ruby
require_dependency 'sass/discourse_sass_compiler'
|
|
require_dependency 'distributed_cache'
|
|
|
|
class DiscourseStylesheets
|
|
|
|
CACHE_PATH = 'uploads/stylesheet-cache'
|
|
MANIFEST_DIR = "#{Rails.root}/tmp/cache/assets/#{Rails.env}"
|
|
MANIFEST_FULL_PATH = "#{MANIFEST_DIR}/stylesheet-manifest"
|
|
|
|
@lock = Mutex.new
|
|
|
|
def self.cache
|
|
return {} if Rails.env.development?
|
|
@cache ||= DistributedCache.new("discourse_stylesheet")
|
|
end
|
|
|
|
def self.stylesheet_link_tag(target = :desktop)
|
|
tag = cache[target]
|
|
|
|
return tag.dup.html_safe if tag
|
|
|
|
@lock.synchronize do
|
|
builder = self.new(target)
|
|
builder.compile unless File.exists?(builder.stylesheet_fullpath)
|
|
builder.ensure_digestless_file
|
|
tag = %[<link href="#{Rails.env.production? ? builder.stylesheet_relpath : builder.stylesheet_relpath_no_digest + '?body=1'}" media="all" rel="stylesheet" />]
|
|
|
|
cache[target] = tag
|
|
|
|
tag.dup.html_safe
|
|
end
|
|
end
|
|
|
|
def self.compile(target = :desktop, opts={})
|
|
@lock.synchronize do
|
|
FileUtils.rm(MANIFEST_FULL_PATH, force: true) if opts[:force] # Force a recompile, even in production env
|
|
builder = self.new(target)
|
|
builder.compile
|
|
builder.stylesheet_filename
|
|
end
|
|
end
|
|
|
|
def self.last_file_updated
|
|
if Rails.env.production?
|
|
@last_file_updated ||= if File.exists?(MANIFEST_FULL_PATH)
|
|
File.readlines(MANIFEST_FULL_PATH, 'r')[0]
|
|
else
|
|
mtime = max_file_mtime
|
|
FileUtils.mkdir_p(MANIFEST_DIR)
|
|
File.open(MANIFEST_FULL_PATH, "w") { |f| f.print(mtime) }
|
|
mtime
|
|
end
|
|
else
|
|
max_file_mtime
|
|
end
|
|
end
|
|
|
|
def self.max_file_mtime
|
|
globs = ["#{Rails.root}/app/assets/stylesheets/**/*.*css"]
|
|
|
|
for path in (Discourse.plugins || []).map { |plugin| File.dirname(plugin.path) }
|
|
globs += [
|
|
"#{path}/plugin.rb",
|
|
"#{path}/**/*.*css",
|
|
]
|
|
end
|
|
|
|
globs.map do |pattern|
|
|
Dir.glob(pattern).map { |x| File.mtime(x) }.max
|
|
end.compact.max.to_i
|
|
end
|
|
|
|
|
|
|
|
def initialize(target = :desktop)
|
|
@target = target
|
|
end
|
|
|
|
def compile
|
|
scss = File.read("#{Rails.root}/app/assets/stylesheets/#{@target}.scss")
|
|
css = begin
|
|
DiscourseSassCompiler.compile(scss, @target)
|
|
rescue Sass::SyntaxError => e
|
|
Rails.logger.error "Stylesheet failed to compile for '#{@target}'! Recompiling without plugins and theming."
|
|
Rails.logger.error e.sass_backtrace_str("#{@target} stylesheet")
|
|
DiscourseSassCompiler.compile(scss + DiscourseSassCompiler.error_as_css(e, "#{@target} stylesheet"), @target, safe: true)
|
|
end
|
|
FileUtils.mkdir_p(cache_fullpath)
|
|
File.open(stylesheet_fullpath, "w") do |f|
|
|
f.puts css
|
|
end
|
|
css
|
|
end
|
|
|
|
def ensure_digestless_file
|
|
# file without digest is only for auto-reloading css in dev env
|
|
unless Rails.env.production? || (File.exist?(stylesheet_fullpath_no_digest) && File.mtime(stylesheet_fullpath) == File.mtime(stylesheet_fullpath_no_digest))
|
|
FileUtils.cp(stylesheet_fullpath, stylesheet_fullpath_no_digest)
|
|
end
|
|
end
|
|
|
|
def cache_fullpath
|
|
"#{Rails.root}/public/#{CACHE_PATH}"
|
|
end
|
|
|
|
def stylesheet_fullpath
|
|
"#{cache_fullpath}/#{stylesheet_filename}"
|
|
end
|
|
def stylesheet_fullpath_no_digest
|
|
"#{cache_fullpath}/#{stylesheet_filename_no_digest}"
|
|
end
|
|
|
|
def stylesheet_relpath
|
|
"/#{CACHE_PATH}/#{stylesheet_filename}"
|
|
end
|
|
def stylesheet_relpath_no_digest
|
|
"/#{CACHE_PATH}/#{stylesheet_filename_no_digest}"
|
|
end
|
|
|
|
def stylesheet_filename
|
|
"#{@target}_#{digest}.css"
|
|
end
|
|
def stylesheet_filename_no_digest
|
|
"#{@target}.css"
|
|
end
|
|
|
|
# digest encodes the things that trigger a recompile
|
|
def digest
|
|
@digest ||= begin
|
|
theme = (cs = ColorScheme.enabled) ? "#{cs.id}-#{cs.version}" : 0
|
|
category_updated = Category.last_updated_at
|
|
Digest::SHA1.hexdigest("#{RailsMultisite::ConnectionManagement.current_db}-#{theme}-#{DiscourseStylesheets.last_file_updated}-#{category_updated}")
|
|
end
|
|
end
|
|
end
|