Skip to content

Commit

Permalink
docs: visualize components (#24)
Browse files Browse the repository at this point in the history
* refactor: rename pages

* feat: auto global nav bar generation

* feat: initial setup

* chore: use outdent

* feat: components view

* feat: export all in `mod.ts`

* test: update snapshot

* docs: use actual code

* feat: auto anchor links

* docs: post full source code

* style: fmt
  • Loading branch information
scarf005 authored Feb 23, 2024
1 parent 5480f58 commit cc82938
Show file tree
Hide file tree
Showing 20 changed files with 282 additions and 72 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
- name: Build site
run: |
deno run -A doc/main.ts
deno run -A doc/components.ts
deno task build
- name: Setup Pages
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
data.json
*/assets/data/
dist
_site
7 changes: 5 additions & 2 deletions _config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import lume from "lume/mod.ts"
import codeHighlight from "lume/plugins/code_highlight.ts"
import { codeAutoLink } from "./auto_link.ts"

const site = lume({ src: "doc" })
const site = lume({ src: "doc", prettyUrls: false })

site.copy("assets", "assets")

site.use(codeHighlight())
site
.use(codeHighlight())
.use(codeAutoLink())

export default site
12 changes: 12 additions & 0 deletions auto_link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Site from "lume/core/site.ts"

const urlRe = /(<span.*>.*)(https:.+)(<\/.*>)/g
const replace = (x: string) => x.replace(urlRe, `$1<a href="$2">$2</a>$3`)

export const codeAutoLink = (replaceFn = replace) => (site: Site) =>
site.process([".html"], (pages) => {
pages.forEach((page) => {
if (typeof page.content !== "string") return
page.content = replaceFn(page.content)
})
})
10 changes: 8 additions & 2 deletions deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"exclude": ["render/assets", "_site", "__snapshots__"],
"exclude": [
"doc/assets/data",
"render/assets",
"_site",
"__snapshots__"
],
"tasks": {
"lume": "echo \"import 'lume/cli.ts'\" | deno run --unstable -A -",
"build": "deno task lume",
Expand All @@ -22,6 +27,7 @@
"types": ["./render/main.d.ts", "lume/types.ts"]
},
"imports": {
"lume/": "https://deno.land/x/[email protected]/"
"lume/": "https://deno.land/x/[email protected]/",
"https://raw.githubusercontent.com/daangn/stackgraph/main/": "./"
}
}
4 changes: 4 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions doc/_includes/base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
export default ({ content, title }: Lume.Data, {}: Lume.Helpers) => {
// console.log("pages:", search.pages())
declare global {
namespace Lume {
export interface Data {
head?: string
}
}
}
export default (
{ content, title, search, head }: Lume.Data,
{}: Lume.Helpers,
) => {
const nav = search.pages()
.map((x) => /*html*/ `<a href="${x.url}">${x.title}</a>`).join("\n")

return /*html*/ `
<!DOCTYPE html>
Expand All @@ -9,15 +20,14 @@ export default ({ content, title }: Lume.Data, {}: Lume.Helpers) => {
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/assets/style.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/github.min.css" />
${head ?? ""}
</head>
<body>
<header>
<h1>
<a href="https://github.com/daangn/stackgraph">StackGraph</a>
</h1>
<nav>
<a href="/">Home</a>
</nav>
<nav>${nav}</nav>
</header>
<hr />
${content}
Expand Down
51 changes: 51 additions & 0 deletions doc/assets/components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// deno-lint-ignore-file

import ForceGraph from "https://esm.sh/[email protected]"

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)
})
2 changes: 1 addition & 1 deletion doc/assets/demo.js → doc/assets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ForceGraph from "https://esm.sh/[email protected]"
const graphDom = document.querySelector("div#graph")
if (!graphDom) throw new Error("graph dom not found")

const data = await fetch("./assets/data.json").then((res) => res.json())
const data = await fetch("./assets/data/index.json").then((res) => res.json())
const imports = Object.fromEntries(
Object.entries(Object.groupBy(data.imports, (x) => x.source))
.map((
Expand Down
10 changes: 8 additions & 2 deletions doc/assets/style.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
body {
padding-inline: 1em;
max-width: 1200px;
margin-inline: auto;
}
Expand All @@ -17,12 +18,17 @@ code {
}

#graph {
height: 60vh;
background-color: #f7fafc;
}

nav {
display: flex;
gap: 0.8em;
flex-wrap: wrap;
}

nav a {
font-size: 1.5em;
font-size: 1.2em;
}

h2,
Expand Down
64 changes: 64 additions & 0 deletions doc/components.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Stream } from "https://deno.land/x/[email protected]/stream/mod.ts"
import { escapeHtml } from "https://deno.land/x/[email protected]/mod.ts"
import { exampleSrc } from "../graph/_example_project.ts"
import outdent from "https://deno.land/x/[email protected]/mod.ts"

export const title = "예제: 컴포넌트 관계도"

export const codeBlock = (content: string, lang = "language-ts") => /*html*/ `
<pre><code class="${lang} hljs">${escapeHtml(content)}</code></pre>
`

const fileViewer = Stream.fromObject(exampleSrc)
.map(([path, content]) => /*html*/ `
<section>
<h2>${path}</h2>
${codeBlock(content)}
</section>
`)
.join({ start: "<article>", sep: "\n", end: "</article>" })

export const head = /*html*/ `
<style>
article {
gap: 1rem;
display: grid;
align-items: center;
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
}
section {
height: 100%;
}
</style>
`

const code = () => Deno.readTextFile(import.meta.dirname + "/components.ts")
const script = () =>
Deno.readTextFile(import.meta.dirname + "/assets/components.js")
export default async () => /*html*/ `
<div id="graph" style="height:40vh" ></div>
${fileViewer}
<hr>
<section>
<h2>main.ts</h2>
${codeBlock(await code())}
</section>
<hr>
<section>
<h2>components.js</h2>
${codeBlock(await script(), "language-js")}
</section>
<section>
<h2>index.html</h2>
${
codeBlock(
outdent /*html*/`
<div id="graph" style="height:40vh" ></div>
<script type="module" src="./assets/components.js" ></script>
`,
"language-html",
)
}
</section>
<script type="module" src="./assets/components.js" ></script>
`
43 changes: 43 additions & 0 deletions doc/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type {
Project,
SourceFile,
} from "https://deno.land/x/[email protected]/mod.ts"

import { Stream } from "https://deno.land/x/[email protected]/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"

// 1. ts-morph 프로젝트 생성 (https://ts-morph.com/setup/)
const project: Project = inMemoryProject()

// 2. 프로젝트에 소스 파일 추가 (https://ts-morph.com/setup/adding-source-files)
const files: Record<string, SourceFile> = withSrc(project)(exampleSrc)

// 3. 모든 선언 가져오기
const decls = Stream.fromObjectValues(files).flatMap(getAllDecls).toArray()

// 4. 그래프 생성하기
const graph = getGraph(decls)

// 5. 그래프 노드와 링크를 JSON으로 저장하기
// (https://github.com/vasturiano/force-graph/blob/master/example/basic/index.html)
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),
)
6 changes: 3 additions & 3 deletions doc/index.page.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export const title = "StackGraph"
export const title = "StackGraph란"

const readme = () =>
Deno.readTextFile(import.meta.dirname + "/../README.md")
.then((text) => text.split("\n").slice(3).join("\n"))

export default async (_: Lume.Data, { md }: Lume.Helpers) => /*html*/ `
<div id="graph"></div>
<div id="graph" style="height:60vh" ></div>
<p>(StackGraph 저장소의 모든 변수 관계도, 유색 선은 의존 관계, 무색 선은 디렉터리/파일 트리)</p>
${md(await readme())}
<script type="module" src="./assets/demo.js"></script>
<script type="module" src="./assets/index.js"></script>
`
4 changes: 2 additions & 2 deletions doc/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const linkNode = <const T extends { id: string; path: string; line: string }>(
}
}

const colorNode = <const T extends { path: string }>(node: T) => ({
export const colorNode = <const T extends { path: string }>(node: T) => ({
...node,
...colors(hashRGB(node.path)),
})
Expand Down Expand Up @@ -119,7 +119,7 @@ if (import.meta.main) {
.toArray()

await Deno.writeTextFile(
import.meta.dirname + "/assets/data.json",
import.meta.dirname + "/assets/data/index.json",
JSON.stringify(
{
links: dirLinks,
Expand Down
Loading

0 comments on commit cc82938

Please sign in to comment.