Skip to content

Commit

Permalink
feat: add dump + dump viewer app (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 authored Apr 9, 2024
1 parent 964cbfb commit 7b8f8fc
Show file tree
Hide file tree
Showing 21 changed files with 1,923 additions and 28 deletions.
6 changes: 5 additions & 1 deletion examples/fastify/bin/start.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { hot } from 'hot-hook'

await hot.init({ root: import.meta.filename })
await hot.init({
root: import.meta.filename,
boundaries: ['../src/services/**.ts'],
})

await import('../src/index.js')
1 change: 1 addition & 0 deletions examples/fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@hot-hook/runner": "workspace:*",
"@types/node": "^20.11.17",
"hot-hook": "workspace:*",
"@hot-hook/dump-viewer": "workspace:*",
"pino-pretty": "^11.0.0",
"tsx": "^3.12.2"
},
Expand Down
6 changes: 6 additions & 0 deletions examples/fastify/src/helpers/posts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function uppercasePostTitles(posts: any[]) {
return posts.map((post) => ({
...post,
title: post.title.toUpperCase(),
}))
}
13 changes: 12 additions & 1 deletion examples/fastify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,21 @@ const fastify = Fastify({
})

fastify.get('/', async (request, reply) => {
const { PostsService } = await import('./posts_service.js')
const { PostsService } = await import('./services/posts_service.js', { with: { hot: 'true' } })
return new PostsService().getPosts()
})

/**
* This route is totally optional and can be used to visualize
* your dependency graph in a browser.
*/
fastify.get('/dump-viewer', async (request, reply) => {
const { dumpViewer } = await import('@hot-hook/dump-viewer')

reply.header('Content-Type', 'text/html; charset=utf-8')
return dumpViewer()
})

const start = async () => {
try {
await fastify.listen({ port: 3000 })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { uppercasePostTitles } from '../helpers/posts'

export class PostsService {
/**
* Try updating the return value of this method and refreshing the page.
* You will always get the latest version of the code without
* restarting the whole server.
*/
getPosts() {
return [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' },
{ id: 3, title: 'Post 3' },
]
return uppercasePostTitles([
{ id: 1, title: 'post 1' },
{ id: 2, title: 'post 2' },
{ id: 3, title: 'post 3' },
])
}
}
16 changes: 16 additions & 0 deletions packages/dump_viewer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hot Hook - Dump Viewer</title>
<script>
window.__HOT_HOOK_DUMP__ = $__hot_hook_placeholder__
</script>
</head>
<body class="h-full m-0">
<div class="h-full" id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
40 changes: 40 additions & 0 deletions packages/dump_viewer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@hot-hook/dump-viewer",
"version": "0.0.0",
"private": true,
"type": "module",
"files": [
"build"
],
"exports": {
".": "./build/dump_viewer.js"
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build && tsc -p tsconfig.node.json",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"lint": "eslint ."
},
"devDependencies": {
"@preact/preset-vite": "^2.8.2",
"hot-hook": "workspace:*",
"typescript": "^5.2.2",
"unocss": "^0.59.0",
"vite": "^5.2.0",
"vite-plugin-singlefile": "^2.0.1"
},
"dependencies": {
"@unocss/reset": "^0.59.0",
"preact": "^10.19.6",
"vis-data": "^7.1.9",
"vis-network": "^9.1.9"
},
"peerDependencies": {
"hot-hook": "workspace:*"
},
"eslintConfig": {
"extends": "@adonisjs/eslint-config/package"
},
"prettier": "@adonisjs/prettier-config"
}
1 change: 1 addition & 0 deletions packages/dump_viewer/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 111 additions & 0 deletions packages/dump_viewer/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { DataSet } from 'vis-data'
import { useEffect } from 'preact/hooks'
import { Edge, Network, Node } from 'vis-network'

export function App() {
async function initGraph() {
const data: HotHookDump = import.meta.env.DEV
? // eslint-disable-next-line unicorn/no-await-expression-member
(await import('./fixtures/dump.json')).default
: window.__HOT_HOOK_DUMP__
let nodes: Node[] = []
let edges: Edge[] = []

const paths = new Map()

data.forEach((item, index) => paths.set(item.path, index + 1))
data.forEach((item, index) => {
const hasOnlyNodeModulesDependency = item.dependencies.every((dep) =>
dep.includes('node_modules')
)

const isRoot = item.dependents.length === 0

const id = index + 1

if (item.path.includes('node_modules') && hasOnlyNodeModulesDependency) {
return
}

let color = 'lightgray'
if (item.boundary) {
color = '#fea800'
} else if (isRoot) {
color = 'violet'
} else if (item.reloadable) {
color = '#72cc0a'
}

nodes.push({
shape: 'box',
id: id,
label: item.path,
margin: { top: 13, bottom: 13, left: 13, right: 13 },
borderWidth: 3,
shadow: true,
font: { color: 'black' },
shapeProperties: { borderRadius: 10 },
color: color,
})

item.dependencies.forEach((dep) => {
const toId = paths.get(dep)
if (toId) {
edges.push({ from: id, to: toId, arrows: 'to' })
} else {
console.log('Missing node for dependency', dep)
}
})
})

const nodesDataSet = new DataSet(nodes)
const edgesDataSet = new DataSet(edges)
const dataVis = { nodes: nodesDataSet, edges: edgesDataSet }

const container = document.getElementById('network')!
new Network(container, dataVis, {
interaction: { hover: true },
layout: {},
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -8000,
centralGravity: 0.3,
springLength: 95,
springConstant: 0.04,
damping: 0.09,
avoidOverlap: 0,
},
},
})
}

useEffect(() => {
initGraph()
}, [])

return (
<>
<div class="z-1 absolute top-0 left-0 border-2 border-[#353535] px-4 pb-3 pt-2 bg-[#3f3f3f] m-4 rounded-lg text-sm">
<p class="font-bold text-lg">Legend</p>
<div class="flex items-center mt-2">
<div class="w-4 h-4 bg-violet mr-2"></div>
<span>Root entrypoint</span>
</div>
<div class="flex items-center">
<div class="w-4 h-4 bg-[#fea800] mr-2"></div>
<span>Boundary file</span>
</div>
<div class="flex items-center">
<div class="w-4 h-4 bg-[#72cc0a] mr-2"></div>
<span>Reloadable</span>
</div>
<div class="flex items-center">
<div class="w-4 h-4 bg-[#D3D3D3] mr-2"></div>
<span>Not-reloadable</span>
</div>
</div>
<div id="network" class="h-full" />
</>
)
}
22 changes: 22 additions & 0 deletions packages/dump_viewer/src/dump_viewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { join } from 'node:path'
import { readFile } from 'node:fs/promises'

/**
* Returns the HTML content of the Hot Hook Dump Viewer
*/
export async function dumpViewer() {
/**
* Dump the dependency tree
*/
const { hot } = await import('hot-hook')
const dump = await hot.dump()

/**
* Load the HTML content and replace the placeholder with the dump
*/
const htmlLocation = join(import.meta.dirname, 'index.html')
let html = await readFile(htmlLocation, 'utf8')
html = html.replace('$__hot_hook_placeholder__', JSON.stringify(dump))

return html
}
5 changes: 5 additions & 0 deletions packages/dump_viewer/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type HotHookDump = typeof import('./fixtures/dump.json')

interface Window {
__HOT_HOOK_DUMP__: HokHookDump
}
1 change: 1 addition & 0 deletions packages/dump_viewer/src/fixtures/dump.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions packages/dump_viewer/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
7 changes: 7 additions & 0 deletions packages/dump_viewer/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { render } from 'preact'
import { App } from './app.tsx'
import '@unocss/reset/tailwind-compat.css'
import 'virtual:uno.css'
import './index.css'

render(<App />, document.getElementById('app')!)
30 changes: 30 additions & 0 deletions packages/dump_viewer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"paths": {
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
},
"types": ["vite/client"],

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
11 changes: 11 additions & 0 deletions packages/dump_viewer/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"skipLibCheck": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./build",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["./src/dump_viewer.ts"]
}
10 changes: 10 additions & 0 deletions packages/dump_viewer/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'
import unocss from 'unocss/vite'
import { viteSingleFile } from 'vite-plugin-singlefile'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact(), unocss(), viteSingleFile()],
build: { outDir: 'build', emptyOutDir: true },
})
16 changes: 16 additions & 0 deletions packages/hot_hook/src/dependency_tree.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { dirname, relative } from 'node:path'

/**
* Represent a file node in the dependency tree.
*/
Expand Down Expand Up @@ -179,4 +181,18 @@ export default class DependencyTree {
const result = checkPathToRoot(node)
return result
}

dump() {
const rootDirname = dirname(this.#tree.path)
const isNodeModule = (path: string) => path.includes('node_modules')

return Array.from(this.#pathMap.values()).map((node) => ({
path: relative(rootDirname, node.path),
boundary: node.reloadable,
reloadable: isNodeModule(node.path) ? false : this.isReloadable(node.path),
version: node.version,
dependencies: Array.from(node.dependencies).map((n) => relative(rootDirname, n.path)),
dependents: Array.from(node.dependents).map((n) => relative(rootDirname, n.path)),
}))
}
}
Loading

0 comments on commit 7b8f8fc

Please sign in to comment.