From 3432b17272cf60ec07466a54d02baca988527229 Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Sat, 27 Apr 2024 17:52:59 +0100 Subject: [PATCH 1/2] feat: docs/** content hot reload --- app/[[...path]]/layout.tsx | 9 +++++- package.json | 6 ++-- src/components/hotReload.tsx | 45 ++++++++++++++++++++++++++++++ src/hotReloadWatcher.mjs | 53 ++++++++++++++++++++++++++++++++++++ yarn.lock | 9 +++++- 5 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/components/hotReload.tsx create mode 100644 src/hotReloadWatcher.mjs diff --git a/app/[[...path]]/layout.tsx b/app/[[...path]]/layout.tsx index 8ae69238850df..6a5c549cf30e3 100644 --- a/app/[[...path]]/layout.tsx +++ b/app/[[...path]]/layout.tsx @@ -2,6 +2,8 @@ import 'prism-sentry/index.css'; import type {Metadata} from 'next'; +import {HotReload} from 'sentry-docs/components/hotReload'; + import 'sentry-docs/styles/screen.scss'; export const metadata: Metadata = { @@ -9,5 +11,10 @@ export const metadata: Metadata = { }; export default function DocsLayout({children}: {children: React.ReactNode}) { - return
{children}
; + return ( +
+ {children} + +
+ ); } diff --git a/package.json b/package.json index 39418a2186257..fb49356dc9ed3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/getsentry/sentry-docs/issues" }, "scripts": { - "dev": "concurrently \"yarn sidecar\" \"next dev\"", + "dev": "concurrently \"yarn sidecar\" \"node ./src/hotReloadWatcher.mjs\" \"next dev\"", "build": "prisma generate && next build", "start": "next start", "migrate:dev": "dotenv -e .env.development -- yarn prisma migrate reset", @@ -97,6 +97,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/ws": "^8.5.10", "autoprefixer": "^10.4.17", "concurrently": "^8.2.2", "dotenv-cli": "^7.4.1", @@ -111,7 +112,8 @@ "prisma": "^5.8.1", "tailwindcss": "^3.4.1", "ts-node": "^10.9.1", - "typescript": "^5" + "typescript": "^5", + "ws": "^8.16.0" }, "volta": { "node": "20.11.0", diff --git a/src/components/hotReload.tsx b/src/components/hotReload.tsx new file mode 100644 index 0000000000000..f0bd875340c91 --- /dev/null +++ b/src/components/hotReload.tsx @@ -0,0 +1,45 @@ +'use client'; +import {useEffect} from 'react'; +import {useRouter} from 'next/navigation'; + +function HotReload_() { + const router = useRouter(); + let ws: WebSocket; + const connect = () => { + if (ws) { + return; + } + ws = new WebSocket('ws://localhost:8080'); + ws.onopen = function open() { + // do nothing + }; + ws.onmessage = function incoming(msg) { + if (msg.data === 'reload') { + // eslint-disable-next-line no-console + console.info('[REFRESHING]'); + router.refresh(); + } + }; + ws.onerror = function error(...err) { + // eslint-disable-next-line no-console + console.error('Hot reload ws error', err); + }; + ws.onclose = function close() {}; + }; + + useEffect( + () => { + connect(); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return null; +} +const noop = () => null; + +/** + * Hot reloads the page when notified by the server of a change under /docs/* + */ +export const HotReload = process.env.NODE_ENV === 'development' ? HotReload_ : noop; diff --git a/src/hotReloadWatcher.mjs b/src/hotReloadWatcher.mjs new file mode 100644 index 0000000000000..9ed283a243da7 --- /dev/null +++ b/src/hotReloadWatcher.mjs @@ -0,0 +1,53 @@ +import path from 'path'; +import {watch} from 'node:fs/promises'; +import {WebSocketServer} from 'ws'; + +const watchedConent = new Set(['.mdx', '.md', '.png', '.jpg', '.jpeg', '.gif', '.svg']); + +export const throttle = (fn, delay) => { + let last = 0; + return (...args) => { + const now = Date.now(); + if (now - last < delay) { + return; + } + last = now; + return fn(...args); + }; +}; + +const wss = new WebSocketServer({port: 8080}); +console.info('⚡️ Hot reload watcher listening on ws://localhost:8080'); + +wss.on('connection', async function onConnect(ws) { + ws.on('error', err => { + console.log('ws error', err); + }); + ws.on('message', function incoming(_msg) { + // no reason for the client to send messages for now + }); + + const ac = new AbortController(); + const {signal} = ac; + ws.on('close', () => ac.abort()); + + // avoid fileystem chatter when you save a file + const sendReload = throttle(() => ws.send('reload'), 10); + + try { + const watcher = watch(path.join(import.meta.dirname, '..', 'docs'), { + signal, + recursive: true, + }); + for await (const event of watcher) { + if (watchedConent.has(path.extname(event.filename))) { + sendReload(); + } + } + } catch (err) { + if (err.name === 'AbortError') { + return; + } + throw err; + } +}); diff --git a/yarn.lock b/yarn.lock index d089621b712fa..c26d308a59152 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3382,6 +3382,13 @@ resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== +"@types/ws@^8.5.10": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -11491,7 +11498,7 @@ ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.11.0: +ws@^8.11.0, ws@^8.16.0: version "8.16.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== From 483ca89d1a3e16599e3de07537dbf6926022f166 Mon Sep 17 00:00:00 2001 From: Abdellah Hariti Date: Mon, 29 Apr 2024 16:39:29 +0100 Subject: [PATCH 2/2] fix typo --- src/hotReloadWatcher.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotReloadWatcher.mjs b/src/hotReloadWatcher.mjs index 9ed283a243da7..95ececbd89e87 100644 --- a/src/hotReloadWatcher.mjs +++ b/src/hotReloadWatcher.mjs @@ -2,7 +2,7 @@ import path from 'path'; import {watch} from 'node:fs/promises'; import {WebSocketServer} from 'ws'; -const watchedConent = new Set(['.mdx', '.md', '.png', '.jpg', '.jpeg', '.gif', '.svg']); +const watchedContent = new Set(['.mdx', '.md', '.png', '.jpg', '.jpeg', '.gif', '.svg']); export const throttle = (fn, delay) => { let last = 0; @@ -40,7 +40,7 @@ wss.on('connection', async function onConnect(ws) { recursive: true, }); for await (const event of watcher) { - if (watchedConent.has(path.extname(event.filename))) { + if (watchedContent.has(path.extname(event.filename))) { sendReload(); } }