From 63b66e72e0bd6fbb5c76413f3f1e8b7359777d9f Mon Sep 17 00:00:00 2001 From: wajeht <58354193+wajeht@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:19:38 -0600 Subject: [PATCH] feat(app): Add hot reload functionality for HTML files and views folder --- .vscode/settings.json | 2 ++ src/app.ts | 16 +++++------- src/reload.ts | 57 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 src/reload.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f70cdd3..bfb3239 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,12 +8,14 @@ "dont", "Hasher", "healthz", + "IIFE", "knexfile", "laravel", "mailhot", "Millis", "multistream", "Neue", + "nocheck", "normies", "pino", "resave", diff --git a/src/app.ts b/src/app.ts index 2659b0a..0318189 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,13 +8,13 @@ import { } from './middleware'; import ejs from 'ejs'; import cors from 'cors'; -import path from 'node:path'; import express from 'express'; import flash from 'connect-flash'; import { router } from './router'; import { appConfig } from './config'; import compression from 'compression'; import expressLayouts from 'express-ejs-layouts'; +import { reload } from './reload'; const app = express(); @@ -36,13 +36,7 @@ app.use(express.json({ limit: '100kb' })); app.use(express.urlencoded({ extended: true, limit: '100kb' })); -app.use( - express.static(path.join(process.cwd(), 'public'), { - maxAge: '30d', - etag: true, - lastModified: true, - }), -); +app.use(express.static('./public', { maxAge: '30d', etag: true, lastModified: true })); app.engine('html', ejs.renderFile); @@ -50,14 +44,16 @@ app.set('view engine', 'html'); app.set('view cache', appConfig.env === 'production'); -app.set('views', path.join(process.cwd(), 'src', 'views', 'pages')); +app.set('views', './src/views/pages'); -app.set('layout', path.join(process.cwd(), 'src', 'views', 'layouts', 'public.html')); +app.set('layout', '../layouts/public.html'); app.use(expressLayouts); app.use(appLocalStateMiddleware); +reload({ app, watch: [{ path: './src/views/pages', extensions: ['.html'] }] }); + app.use(router); app.use(notFoundMiddleware()); diff --git a/src/reload.ts b/src/reload.ts new file mode 100644 index 0000000..9746511 --- /dev/null +++ b/src/reload.ts @@ -0,0 +1,57 @@ +// @ts-nocheck + +import fs from 'fs'; + +export function reload({ app, watch, options = {} }) { + if (process.env.NODE_ENV === 'production') return; + + const pollInterval = options.pollInterval || 50; + const quiet = options.quiet || false; + let changeDetected = false; + + watch.forEach(({ path: dir, extensions }) => { + const extensionsSet = new Set(extensions); + + fs.watch(dir, { recursive: true }, (_, filename) => { + if (filename && extensionsSet.has(filename.slice(filename.lastIndexOf('.')))) { + if (!quiet) console.log('File changed:', filename); + changeDetected = true; + } + }); + }); + + app.get('/wait-for-reload', (req, res) => { + const timer = setInterval(() => { + if (changeDetected) { + changeDetected = false; + clearInterval(timer); + res.send(); // Empty 200 OK + } + }, pollInterval); + + req.on('close', () => clearInterval(timer)); + }); + + const clientScript = ` + `; + + app.use((req, res, next) => { + const originalRender = res.render; + res.render = function (view, options, callback) { + originalRender.call(this, view, options, (err, html) => { + if (err) return callback ? callback(err) : next(err); + res.send(html.replace('', clientScript + '')); + }); + }; + next(); + }); +}