Skip to content

Commit 1ea7f33

Browse files
committed
feat(app): Integrate express-templates-reload for dynamic template reloading
1 parent 3593d26 commit 1ea7f33

File tree

4 files changed

+23
-85
lines changed

4 files changed

+23
-85
lines changed

package-lock.json

Lines changed: 14 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"@typescript-eslint/eslint-plugin": "^7.16.1",
9494
"@typescript-eslint/parser": "^7.18.0",
9595
"@vitest/coverage-v8": "^2.1.5",
96+
"@wajeht/express-templates-reload": "^1.0.1",
9697
"eslint-config-prettier": "^9.1.0",
9798
"eslint-plugin-prettier": "^5.2.1",
9899
"husky": "^9.1.7",

src/app.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import {
99
import ejs from 'ejs';
1010
import cors from 'cors';
1111
import express from 'express';
12-
import { reload } from './utils';
1312
import flash from 'connect-flash';
1413
import { router } from './router';
1514
import { appConfig } from './config';
1615
import compression from 'compression';
1716
import expressLayouts from 'express-ejs-layouts';
17+
import { expressTemplatesReload as reload } from '@wajeht/express-templates-reload';
1818

1919
const app = express();
2020

@@ -52,7 +52,12 @@ app.use(expressLayouts);
5252

5353
app.use(appLocalStateMiddleware);
5454

55-
reload({ app, watch: [{ path: './src/views', extensions: ['.html'] }] });
55+
if (appConfig.env === 'development') {
56+
reload({
57+
app,
58+
watch: [{ path: './public/style.css' }, { path: './src/views', extensions: ['.html'] }],
59+
});
60+
}
5661

5762
app.use(router);
5863

src/utils.ts

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@ import path from 'node:path';
66
import jwt from 'jsonwebtoken';
77
import dayjsModule from 'dayjs';
88
import { Redis } from 'ioredis';
9+
import { Request } from 'express';
910
import { logger } from './logger';
1011
import fsp from 'node:fs/promises';
11-
import fs from 'node:fs';
1212
import utc from 'dayjs/plugin/utc';
1313
import nodemailer from 'nodemailer';
1414
import { db, redis } from './db/db';
1515
import { Queue, Worker, Job } from 'bullmq';
1616
import timezone from 'dayjs/plugin/timezone';
1717
import { appConfig, emailConfig, oauthConfig, sessionConfig } from './config';
1818
import { GithubUserEmail, GitHubOauthToken, ApiKeyPayload, User } from './types';
19-
import { Application, Request, Response, NextFunction } from 'express';
2019

2120
export function dayjs(date: string | Date = new Date()) {
2221
dayjsModule.extend(utc);
@@ -285,78 +284,3 @@ export const modifyUserSessionById = async (
285284
logger.error('No session found for user ID:', userId);
286285
return null;
287286
};
288-
289-
export function reload({
290-
app,
291-
watch,
292-
options = {},
293-
}: {
294-
app: Application;
295-
watch: { path: string; extensions: string[] }[];
296-
options?: { pollInterval?: number; quiet?: boolean };
297-
}): void {
298-
if (appConfig.env !== 'development') return;
299-
300-
const pollInterval = options.pollInterval || 50;
301-
const quiet = options.quiet || false;
302-
let changeDetected = false;
303-
const lastContents = new Map<string, string>();
304-
305-
watch.forEach(({ path: dir, extensions }) => {
306-
const extensionsSet = new Set(extensions);
307-
fs.watch(dir, { recursive: true }, (_: fs.WatchEventType, filename: string | null) => {
308-
if (filename && extensionsSet.has(filename.slice(filename.lastIndexOf('.')))) {
309-
try {
310-
const fullPath = path.join(dir, filename);
311-
const content = fs.readFileSync(fullPath, 'utf8');
312-
313-
if (content !== lastContents.get(fullPath)) {
314-
lastContents.set(fullPath, content);
315-
316-
if (!quiet) logger.info('[reload] File changed: %s', filename);
317-
changeDetected = true;
318-
}
319-
} catch {
320-
if (!quiet) logger.debug('[reload] Error reading file: %s', filename);
321-
}
322-
}
323-
});
324-
});
325-
326-
app.get('/wait-for-reload', (req: Request, res: Response) => {
327-
const timer = setInterval(() => {
328-
if (changeDetected) {
329-
changeDetected = false;
330-
clearInterval(timer);
331-
res.send();
332-
}
333-
}, pollInterval);
334-
335-
req.on('close', () => clearInterval(timer));
336-
});
337-
338-
const clientScript = `
339-
<script>
340-
(async function poll() {
341-
try {
342-
await fetch('/wait-for-reload');
343-
location.reload();
344-
} catch {
345-
location.reload();
346-
}
347-
})();
348-
</script>\n\t`;
349-
350-
app.use((req: Request, res: Response, next: NextFunction) => {
351-
const originalSend = res.send.bind(res);
352-
353-
res.send = function (body: any): Response {
354-
if (typeof body === 'string' && body.includes('</head>')) {
355-
body = body.replace('</head>', clientScript + '</head>');
356-
}
357-
return originalSend(body);
358-
};
359-
360-
next();
361-
});
362-
}

0 commit comments

Comments
 (0)