From de0c1d368ca067b764e6d0e5e75bfcf041227257 Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Tue, 6 Dec 2022 11:34:51 +0100 Subject: [PATCH] Add source map for sprockets --- app/assets/config/manifest.js | 2 + lib/tasks/sourcemap.rake | 87 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 lib/tasks/sourcemap.rake diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index b16e53d6..6577e8cc 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,5 @@ //= link_tree ../images //= link_directory ../javascripts .js +//= link_directory ../javascripts .js.map //= link_directory ../stylesheets .css +//= link_directory ../stylesheets .css.map diff --git a/lib/tasks/sourcemap.rake b/lib/tasks/sourcemap.rake new file mode 100644 index 00000000..a5a7964c --- /dev/null +++ b/lib/tasks/sourcemap.rake @@ -0,0 +1,87 @@ +# 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 + write_asset(file, "#{file.read}\n#{mapping_string}", manifest) + end + + 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