feat: add Prism and Assets
This commit is contained in:
1 parent
6b7850d756
commit
c149318d16
2 files changed
+118
No files matched your search
@@ -0,0 +1,68 @@
|
|||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
module Prism
|
||||||
|
module Assets
|
||||||
|
# Sprockets/Propshaft-lite precompile step: walks every file under
|
||||||
|
# `source`, content-addresses it (sha256, first 8 hex chars) into
|
||||||
|
# `name-digest.ext` under `output`, and writes a manifest.json mapping
|
||||||
|
# the original relative path ("css/application.css") to the
|
||||||
|
# fingerprinted one ("css/application-3f2a9c1e.css"). Re-running with
|
||||||
|
# unchanged file content reproduces the exact same fingerprinted name
|
||||||
|
# (pure function of the bytes, no timestamps involved), and any
|
||||||
|
# fingerprinted file left over from a PREVIOUS compile that no logical
|
||||||
|
# path points to anymore is deleted - the same cleanup
|
||||||
|
# `rake assets:clean` does in Rails, done automatically on every compile
|
||||||
|
# instead of as a separate step.
|
||||||
|
class Compiler
|
||||||
|
DIGEST_LENGTH = 8
|
||||||
|
|
||||||
|
def initialize(source:, output:)
|
||||||
|
@source = File.expand_path(source)
|
||||||
|
@output = File.expand_path(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile!
|
||||||
|
FileUtils.mkdir_p(@output)
|
||||||
|
previous = Manifest.load(manifest_path).to_h.values
|
||||||
|
manifest = Manifest.new
|
||||||
|
|
||||||
|
each_source_file do |absolute, logical_path|
|
||||||
|
fingerprinted = fingerprint(logical_path, absolute)
|
||||||
|
destination = File.join(@output, fingerprinted)
|
||||||
|
FileUtils.mkdir_p(File.dirname(destination))
|
||||||
|
FileUtils.cp(absolute, destination)
|
||||||
|
manifest[logical_path] = fingerprinted
|
||||||
|
end
|
||||||
|
|
||||||
|
stale = previous - manifest.to_h.values
|
||||||
|
stale.each { |path| FileUtils.rm_f(File.join(@output, path)) }
|
||||||
|
|
||||||
|
manifest.save(manifest_path)
|
||||||
|
manifest
|
||||||
|
end
|
||||||
|
|
||||||
|
def manifest_path
|
||||||
|
File.join(@output, 'manifest.json')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def each_source_file
|
||||||
|
return unless File.directory?(@source)
|
||||||
|
|
||||||
|
Dir.glob(File.join(@source, '**', '*')).sort.each do |absolute|
|
||||||
|
next unless File.file?(absolute)
|
||||||
|
|
||||||
|
yield absolute, absolute.delete_prefix("#{@source}/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fingerprint(logical_path, absolute)
|
||||||
|
digest = Digest::SHA256.file(absolute).hexdigest[0, DIGEST_LENGTH]
|
||||||
|
ext = File.extname(logical_path)
|
||||||
|
base = logical_path.delete_suffix(ext)
|
||||||
|
"#{base}-#{digest}#{ext}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
module Prism
|
||||||
|
module Assets
|
||||||
|
# Maps logical asset paths ("application.css") to fingerprinted paths
|
||||||
|
# ("application-3f2a9c1e.css") the way Sprockets/Propshaft's
|
||||||
|
# manifest.json does. Purely a lookup table plus JSON (de)serialization -
|
||||||
|
# Compiler is what actually populates one.
|
||||||
|
class Manifest
|
||||||
|
AssetNotFound = Class.new(StandardError)
|
||||||
|
|
||||||
|
def initialize(assets = {})
|
||||||
|
@assets = assets.transform_keys(&:to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :assets
|
||||||
|
|
||||||
|
def []=(logical_path, fingerprinted_path)
|
||||||
|
@assets[logical_path.to_s] = fingerprinted_path
|
||||||
|
end
|
||||||
|
|
||||||
|
# Raises when the manifest is authoritative (production-style,
|
||||||
|
# precompiled ahead of time) so a typo in a view never silently links
|
||||||
|
# to a 404 - the same tradeoff Rails makes with
|
||||||
|
# config.assets.unknown_asset_fallback = false.
|
||||||
|
def lookup(logical_path)
|
||||||
|
@assets.fetch(logical_path.to_s) do
|
||||||
|
raise AssetNotFound, "asset #{logical_path.inspect} is not in the manifest " \
|
||||||
|
"(known: #{@assets.keys.sort.join(', ')})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def key?(logical_path)
|
||||||
|
@assets.key?(logical_path.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_h
|
||||||
|
@assets.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
def save(path)
|
||||||
|
File.write(path, JSON.pretty_generate(@assets))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.load(path)
|
||||||
|
return new unless File.file?(path)
|
||||||
|
|
||||||
|
new(JSON.parse(File.read(path)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user