derpycoder
Made a Source Code Inspector, useful in big projects or large teams
I didn’t think I could do it, but after getting the hang of the Hooks, I have created a tool that I really needed in front-end web development space.
I can barely remember what I worked on today, let alone the file I worked on. So when I get thrown into a project or get asked to work on a web component that was built by my colleagues, I get lost in the labyrinthian code.
Here’s my best attempt at solving that issue:

The tooltip has 2 icons, 1 that leads to the source code, and the other will lead to the Storybook components page. Which I haven’t implemented yet.
Also, this is currently restricted to dev, but I wish to enable it for prod as well. We can enable the inspector like we enable latency sim, and clicking on the show source would open up GitHub/Storybook.
And it works everywhere, see:
P.S. Just stuck figuring out a way to add multiple JS Hook. (I found out a post on Elixir Forum, by someone who figured it out and even did lazy loaded Hook!)
P.P.S. It’s not a library, just changes made across several places. I can paste the whole thing if people want. Took me a while to get the popup to work, but it was worth it.
Inspired by: https://bit.dev (Toggle the inspect button in the top right to hover over each element and see their names and link!)
I suggested this to many people, I really want this to be part of LiveView, or Storybook, or both. (See: Add dev config for injecting HTML comments around function components)
P.S. Here’s the full source code:
Most Liked
chrismccord
Nice work! The goal in the linked issue is to add this to the HTML engine and have it inject whatever is needed to do this kind of thing. If you’d like to get involved with that issue, please let me know!
derpycoder
Here’s the code, so others can play around with it, till the Phoenix team adds it to Phoenix 2.0!
defmodule DerpyToolsWeb.Nav do
import Phoenix.LiveView
use Phoenix.Component
def on_mount(:default, _params, _session, socket) do
{:cont,
socket
|> attach_hook(:inspect_source, :handle_event, &handle_event/3)}
end
defp handle_event("inspect-source", %{"file" => file, "line" => line}, socket) do
System.cmd("code", ["--goto", "#{file}:#{line}"])
{:halt, socket}
end
defp handle_event(_, _, socket), do: {:cont, socket}
scope "/", DerpyToolsWeb do
pipe_through :browser
live_session :no_log_in_required,
on_mount: [DerpyToolsWeb.Nav] do
live "/", HomePageLive
...
end
end
Usage
<h2
id="test-div"
data-file={__ENV__.file}
data-line={__ENV__.line}
phx-hook={Mix.env() == :dev && "SourceInspector"}
class="..."
>
Hover over this!
</h2>
JavaScript Side
source_inspector.js
import { computePosition, flip, offset, arrow } from "../vendor/floating-ui";
const SourceInspector = {
mounted() {
if (!this.el.dataset) {
console.log("Please pass in file & line data attributes!");
return;
}
const globalTooltip = document.querySelector("#inspector-tooltip");
let tooltip = globalTooltip.cloneNode(true);
tooltip.setAttribute("id", `inspect-${this.el.id}`);
const inspectSourceBtn = tooltip.querySelector("#source-btn");
const arrowElement = tooltip.querySelector("#arrow");
this.el.addEventListener("mouseenter", () => {
const { file, line } = this.el.dataset;
this.el.appendChild(tooltip);
tooltip.classList.remove("hidden");
tooltip.classList.add("flex");
placeTooltip(this.el, tooltip, arrowElement);
this.el.classList.add(
"rounded-lg",
"outline",
"outline-offset-4",
"outline-pink-500"
);
inspectSourceBtn.setAttribute("phx-value-file", file);
inspectSourceBtn.setAttribute("phx-value-line", line);
});
this.el.addEventListener("mouseleave", (e) => {
handleMouseLeave(this.el, tooltip);
});
},
};
function handleMouseLeave(target, tooltip) {
target.classList.remove(
"rounded-lg",
"outline",
"outline-offset-4",
"outline-pink-500"
);
tooltip.classList.add("hidden");
tooltip.classList.remove("flex");
}
function placeTooltip(target, tooltip, arrowElement) {
computePosition(target, tooltip, {
placement: "top",
middleware: [
flip(),
offset(8),
arrow({
element: arrowElement,
}),
],
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
const { x: arrowX, y: arrowY } = middlewareData.arrow;
const staticSide = {
top: "bottom",
right: "left",
bottom: "top",
left: "right",
}[placement.split("-")[0]];
Object.assign(arrowElement.style, {
left: arrowX != null ? `${arrowX}px` : "",
top: arrowY != null ? `${arrowY}px` : "",
right: "",
bottom: "",
[staticSide]: "-10px",
});
});
}
export default SourceInspector;
app.js
import SourceInspector from "./source_inspector";
let liveSocket = new LiveSocket("/live", Socket, {
params: {
_csrf_token: csrfToken,
},
hooks: {
SourceInspector,
},
});
N.B. Don’t forget to add this in the environment variable:
export ELIXIR_EDITOR="code --goto __FILE__:__LINE__"
This way, the Beam instance will know to open the VS Code editor!
P.S. I used float-ui, which is the next iteration of Popper.js, for the tooltip.
Just download the ESM files from JS Delivr, i.e.
derpycoder
That, plus I have a major qualm with the current state of Web Development:
- There’s a lack of a tool, as universal and unchanging as a Guitar. (Why does Node.js exist? It’s just a time-sink to debug.
) - The wrapper-around-wrapper approach that’s nuking performance back to the 1980s. (Microsoft Teams and it’s slow as heck next version is being shoved down our throat as fast!
) - Code rot, in projects built with nearsighted frameworks. (My angular project, which I built 5 years back, doesn’t run anymore.
) - Complexity that has crept into the tooling. (Webpack at work, compiles our project which is written in an Interpreted language, at a snail’s pace.
)
I wanted to quit this field. But decided to stick around because of Elixir, Phoenix & Live View.
Hope it pans out.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










