a.ts
export const a = "a"
export const aaa = () => a
b.tsx
import { a, aaa } from "./a.ts"
export { a as exportedA }
export const Comp = () => <div>{a}</div>
export const CompAAA = () => <div>{aaa()}</div>
export const Unrelated = () => <div>Unrelated</div>
c.tsx
import { Comp } from "./b.tsx"
export const a = "1234"
export const Page = () => <div><Comp/></div>
d.tsx
import { exportedA, Unrelated } from "./b.tsx"
import { Page } from "./c.tsx"
export const InnerImport = () => <>
<Page/>
<Unrelated/>
</>
export const AliasedImport = () => <div>{exportedA}</div>
main.ts
import type {
Project,
SourceFile,
} from "https://deno.land/x/ts_morph@21.0.1/mod.ts"
import { Stream } from "https://deno.land/x/rimbu@1.2.0/stream/mod.ts"
import {
getAllDecls,
getGraph,
parseVSCodeURI,
} from "https://raw.githubusercontent.com/daangn/stackgraph/main/graph/mod.ts"
import { exampleSrc } from "../graph/_example_project.ts"
import { colorNode } from "./main.ts"
import { inMemoryProject, withSrc } from "../graph/_project.ts"
const project: Project = inMemoryProject()
const files: Record<string, SourceFile> = withSrc(project)(exampleSrc)
const decls = Stream.fromObjectValues(files).flatMap(getAllDecls).toArray()
const graph = getGraph(decls)
const nodes = graph.streamNodes().map(parseVSCodeURI).map(colorNode).toArray()
const links = graph.streamConnections()
.map(([source, target]) => ({
source: parseVSCodeURI(source).uri,
target: parseVSCodeURI(target).uri,
}))
.toArray()
await Deno.writeTextFile(
import.meta.dirname + "/assets/data/components.json",
JSON.stringify({ links, nodes }, null, 2),
)
components.js
import ForceGraph from "https://esm.sh/force-graph@1.43.4"
const graphDom = document.querySelector("#graph")
if (!graphDom) throw new Error("Root dom not found")
const data = await fetch("./assets/data/components.json")
.then((res) => res.json())
const Graph = ForceGraph()(graphDom)
.graphData(data)
.nodeId("uri")
.nodeLabel("fullPath")
.linkDirectionalArrowLength(4)
.nodeCanvasObject((node, ctx, globalScale) => {
const label = node.name
const fontSize = 16 / globalScale
ctx.font = `${fontSize}px Sans-Serif`
const bgWidth = ctx.measureText(label).width + fontSize * 0.2
const bgHeight = fontSize * 1.2
ctx.fillStyle = node.color
ctx.fillRect(node.x - bgWidth / 2, node.y - bgHeight / 2, bgWidth, bgHeight)
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillStyle = node.textColor
ctx.fillText(label, node.x, node.y)
node.bgWidth = bgWidth
node.bgHeight = bgHeight
})
.nodePointerAreaPaint((node, color, ctx) => {
ctx.fillStyle = color
ctx.fillRect(
node.x - node.bgWidth / 2,
node.y - node.bgHeight / 2,
node.bgWidth,
node.bgHeight,
)
})
Graph.width(graphDom.clientWidth).height(graphDom.clientHeight)
globalThis.addEventListener("resize", () => {
Graph.width(graphDom.clientWidth).height(graphDom.clientHeight)
console.log(graphDom.clientWidth)
})
index.html
<div id="graph" style="height:40vh" ></div>
<script type="module" src="./assets/components.js" ></script>