defmodule NPM.BundleAnalysis do alias NPM.Package.Files @moduledoc """ Analyzes package bundle-friendliness. Checks for ESM support, tree-shaking capability, sideEffects field, and other indicators of bundle efficiency. """ @doc """ Scores a package's bundle-friendliness (0-100). """ @spec score(map()) :: non_neg_integer() def score(data) do points = 0 points = if NPM.TypeField.esm?(data), do: points + 25, else: points points = if NPM.SideEffects.tree_shakeable?(data), do: points + 25, else: points points = if is_map(data["exports"]), do: points + 20, else: points points = if is_binary(data["module"]), do: points + 15, else: points if Files.has_whitelist?(data), do: points + 15, else: points end @doc """ Categorizes bundle-friendliness. """ @spec grade(non_neg_integer()) :: String.t() def grade(score) when score >= 80, do: "excellent" def grade(score) when score >= 60, do: "good" def grade(score) when score >= 40, do: "fair" def grade(score) when score >= 20, do: "poor" def grade(_), do: "minimal" @doc """ Generates recommendations for improving bundle-friendliness. """ @spec recommendations(map()) :: [String.t()] def recommendations(data) do checks = [ {NPM.TypeField.esm?(data), ~s(Add "type": "module" for ESM support)}, {NPM.SideEffects.tree_shakeable?(data), ~s(Add "sideEffects": false for tree-shaking)}, {is_map(data["exports"]), ~s(Add "exports" field for subpath exports)}, {is_binary(data["module"]), ~s(Add "module" field pointing to ESM entry)} ] Enum.flat_map(checks, fn {true, _} -> [] {false, rec} -> [rec] end) end @doc """ Analyzes bundle-friendliness across packages. """ @spec analyze([{String.t(), map()}]) :: map() def analyze(packages) do scores = Enum.map(packages, fn {name, data} -> {name, score(data)} end) avg = if scores != [], do: scores |> Enum.map(&elem(&1, 1)) |> Enum.sum() |> div(length(scores)), else: 0 %{ average_score: avg, grade: grade(avg), best: scores |> Enum.sort_by(&elem(&1, 1), :desc) |> Enum.take(5), worst: scores |> Enum.sort_by(&elem(&1, 1)) |> Enum.take(5) } end end