Repository: phoenixframework/phoenix
Pull request #5527 (2023-07-21 18:00:15)

Ensure sourcemaps are digested with expected hash/extension
[Sourcemaps][0] are usually generated alongside the asset they describe, and due to this many tools assume that for an asset like app-{hash}.js the sourcemap will be found at app-{hash}.js.map. This change ensures that digested sourcemaps will have paths matching their corresponding assets.

[0]: https://web.dev/source-maps/

Closes #5523 

mcrumm: The code's not very pretty but I wanted to validate the concept before I refactored the file mapping :)

Patch:
diff --git a/lib/phoenix/digester.ex b/lib/phoenix/digester.ex
index cd8361277..26a93210d 100644
--- a/lib/phoenix/digester.ex
+++ b/lib/phoenix/digester.ex
@@ -23,6 +23,7 @@ defmodule Phoenix.Digester do
       File.mkdir_p!(output_path)
 
       files = filter_files(input_path)
+      files = fixup_sourcemaps(files)
       latest = generate_latest(files)
       digests = load_compile_digests(output_path)
       digested_files = Enum.map(files, &digested_contents(&1, latest, with_vsn?))
@@ -45,6 +46,30 @@ defmodule Phoenix.Digester do
     |> Enum.map(&map_file(&1, input_path))
   end
 
+  defp fixup_sourcemaps(files) when is_list(files) do
+    Enum.map(files, &maybe_fixup_sourcemap(&1, files))
+  end
+
+  defp maybe_fixup_sourcemap(sourcemap, files) do
+    if Path.extname(sourcemap.filename) == ".map" do
+      fixup_sourcemap(sourcemap, files)
+    else
+      sourcemap
+    end
+  end
+
+  defp fixup_sourcemap(%{} = sourcemap, files) do
+    asset_path = Path.rootname(sourcemap.absolute_path, ".map")
+    asset = Enum.find(files, fn file -> file.absolute_path == asset_path end)
+
+    if asset do
+      new_digested_filename = asset.digested_filename <> ".map"
+      %{sourcemap | digest: asset.digest, digested_filename: new_digested_filename}
+    else
+      sourcemap
+    end
+  end
+
   defp generate_latest(files) do
     Map.new(
       files,


Test patch:
diff --git a/test/phoenix/digester_test.exs b/test/phoenix/digester_test.exs
index c4c71494e..b50f1ab0a 100644
--- a/test/phoenix/digester_test.exs
+++ b/test/phoenix/digester_test.exs
@@ -276,7 +276,7 @@ defmodule Phoenix.DigesterTest do
 
       digested_js_map_filename =
         assets_files(@output_path)
-        |> Enum.find(&(&1 =~ ~r"app.js-#{@hash_regex}.map"))
+        |> Enum.find(&(&1 =~ ~r"app-#{@hash_regex}.js.map"))
 
       digested_js_filename =
         assets_files(@output_path)
@@ -297,7 +297,7 @@ defmodule Phoenix.DigesterTest do
 
       digested_js_map_filename =
         assets_files(@output_path)
-        |> Enum.find(&(&1 =~ ~r"app.js-#{@hash_regex}.map"))
+        |> Enum.find(&(&1 =~ ~r"app-#{@hash_regex}.js.map"))
 
       digested_js_filename =
         assets_files(@output_path)
@@ -324,6 +324,15 @@ defmodule Phoenix.DigesterTest do
       refute undigested_css =~ ~r"/phoenix-#{@hash_regex}\.png"
       refute undigested_css =~ ~r"\.\./images/relative-#{@hash_regex}\.png"
     end
+
+    test "digested sourcemaps and their asset share the same hash" do
+      input_path = "test/fixtures/digest/priv/static/"
+      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)
+
+      json = Path.join(@output_path, "cache_manifest.json") |> json_read!()
+
+      assert json["latest"]["app.js.map"] == json["latest"]["app.js"] <> ".map"
+    end
   end
 
   describe "clean" do