Files
codeocean/lib/tasks/sourcemap.rake
Sebastian Serth 66921d92bb Fix assets:precompile to be idempotent again
Each execution regenerates the manifest, so that we also need to rewrite the hashes.
2022-12-06 20:21:58 +01:00

100 lines
3.4 KiB
Ruby

# frozen_string_literal: true
# Adapted from https://github.com/rails/sprockets/issues/502#issuecomment-1030634236
SOURCE_EXTENSIONS = %w[.css .js].freeze
namespace :assets do
def append_sourcemap
manifest_path = Dir[Rails.public_path.join('assets/.sprockets-manifest-*.json').to_s].max {|a, b| File.ctime(a) <=> File.ctime(b) }
return unless manifest_path
manifest_json = JSON.parse(File.read(manifest_path))
assets = manifest_json['assets']
manifest = manifest_json['files']
assets.each do |name, digested|
ext = File.extname(name)
next unless SOURCE_EXTENSIONS.include? ext
# Get the source map file from the manifest
map_digested = assets["#{name}.map"]
next unless map_digested
# Parse the source map file
source_map = JSON.parse(File.read(Rails.public_path.join('assets', map_digested).to_s))
# Replace the source map file name with the digested one
if source_map['sections'].present?
source_map['sections'].map! do |section|
section['map']['sources'].map! {|source| assets[source] || source }
section
end
elsif source_map['sources'].present?
source_map['sources'].map! {|source| assets[source] || source }
end
# Write the source map file
asset_source = Rails.public_path.join('assets', map_digested).to_s
write_asset(asset_source, source_map.to_json, manifest)
# Construct the source map link
file = Rails.root.join("public/assets/#{digested}")
mapping_string = "sourceMappingURL=#{map_digested}"
mapping_string = case ext
when '.css' then "/*# #{mapping_string} */"
when '.js' then "//# #{mapping_string}"
end
# Read the source map file and append the source map link
existing_file_content = file.readlines.map(&:strip)
if existing_file_content.blank? || existing_file_content[-1] == mapping_string
# Just "restore" the integrity hash
mtime = Sprockets::PathUtils.stat(file)&.mtime
manifest[File.basename(file)].merge!(changed_manifest(existing_file_content.join("\n"), mtime))
else
# Append the source map link to the file
new_content = existing_file_content + [mapping_string]
write_asset(file, new_content.join("\n"), manifest)
end
end
# We need to write the manifest file again to include the new integrity hashes
manifest_json['files'] = manifest
File.write(manifest_path, manifest_json.to_json)
end
def write_asset(filename, content, manifest)
File.write(filename, content)
mtime = Sprockets::PathUtils.stat(filename)&.mtime
compress_asset(filename, content, mtime)
manifest[File.basename(filename)].merge!(changed_manifest(content, mtime))
end
def compress_asset(source_filename, content, mtime)
target = "#{source_filename}.gz"
File.open(target, 'wb') do |file|
Sprockets::Utils::Gzip::ZlibArchiver.call(file, content, mtime)
end
end
def changed_manifest(content, mtime)
digest = Sprockets::DigestUtils.digest(content)
hexdigest = Sprockets::DigestUtils.pack_hexdigest(digest)
{
'mtime' => mtime,
'size' => content.bytesize,
'digest' => hexdigest,
'integrity' => Sprockets::DigestUtils.hexdigest_integrity_uri(hexdigest),
}
end
task precompile: :environment do
append_sourcemap
end
end