Skip to content

Commit 08553bb

Browse files
author
Raulo Erwan.
committed
tech: enable watch mode & esbuild server in dev mode
1 parent beb2734 commit 08553bb

8 files changed

Lines changed: 165 additions & 25 deletions

File tree

‎bin/index.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ defaultScannerCommand("from <spec>")
6363
defaultScannerCommand("auto [spec]", { includeOutput: false, strategy: vulnera.strategies.GITHUB_ADVISORY })
6464
.describe(i18n.getTokenSync("cli.commands.auto.desc"))
6565
.option("-k, --keep", i18n.getTokenSync("cli.commands.auto.option_keep"), false)
66-
.option("-d, --developer", i18n.getTokenSync("cli.commands.open.option_developer"), false)
66+
.option("--developer", i18n.getTokenSync("cli.commands.open.option_developer"), false)
6767
.action(async(spec, options) => {
6868
checkNodeSecureToken();
6969
await commands.scanner.auto(spec, options);

‎esbuild.dev.config.ts‎

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Import Node.js Dependencies
2+
import fsAsync from "node:fs/promises";
3+
import http from "node:http";
4+
import path from "node:path";
5+
6+
// Import Third-party Dependencies
7+
import { getBuildConfiguration } from "@nodesecure/documentation-ui/node";
8+
import * as i18n from "@nodesecure/i18n";
9+
import chokidar from "chokidar";
10+
import esbuild from "esbuild";
11+
import open from "open";
12+
import sirv from "sirv";
13+
14+
// Import Internal Dependencies
15+
import english from "./i18n/english.js";
16+
import french from "./i18n/french.js";
17+
import { context as alsContext } from "./workspaces/server/src/ALS.ts";
18+
import { ViewBuilder } from "./workspaces/server/src/ViewBuilder.class.ts";
19+
import { cache } from "./workspaces/server/src/cache.ts";
20+
import { getApiRouter } from "./workspaces/server/src/endpoints/index.ts";
21+
import { logger } from "./workspaces/server/src/logger.ts";
22+
import { WebSocketServerInstanciator } from "./workspaces/server/src/websocket/index.ts";
23+
24+
// CONSTANTS
25+
const kPublicDir = path.join(import.meta.dirname, "public");
26+
const kOutDir = path.join(import.meta.dirname, "dist");
27+
const kImagesDir = path.join(kPublicDir, "img");
28+
const kNodeModulesDir = path.join(import.meta.dirname, "node_modules");
29+
const kComponentsDir = path.join(kPublicDir, "components");
30+
const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json");
31+
const kDevPort = Number(process.env.DEV_PORT ?? 8080);
32+
33+
await Promise.all([
34+
i18n.getLocalLang(),
35+
i18n.extendFromSystemPath(path.join(import.meta.dirname, "i18n"))
36+
]);
37+
38+
const imagesFiles = await fsAsync.readdir(kImagesDir);
39+
40+
await Promise.all([
41+
...imagesFiles
42+
.map((name) => fsAsync.copyFile(path.join(kImagesDir, name), path.join(kOutDir, name))),
43+
fsAsync.copyFile(path.join(kPublicDir, "favicon.ico"), path.join(kOutDir, "favicon.ico"))
44+
]);
45+
46+
const buildContext = await esbuild.context({
47+
entryPoints: [
48+
path.join(kPublicDir, "main.js"),
49+
path.join(kPublicDir, "main.css"),
50+
path.join(kNodeModulesDir, "highlight.js", "styles", "github.css"),
51+
...getBuildConfiguration().entryPoints
52+
],
53+
54+
loader: {
55+
".jpg": "file",
56+
".png": "file",
57+
".woff": "file",
58+
".woff2": "file",
59+
".eot": "file",
60+
".ttf": "file",
61+
".svg": "file"
62+
},
63+
platform: "browser",
64+
bundle: true,
65+
sourcemap: true,
66+
treeShaking: true,
67+
outdir: kOutDir
68+
});
69+
70+
await buildContext.watch();
71+
72+
const { hosts: esbuildHosts, port: esbuildPort } = await buildContext.serve({
73+
servedir: kOutDir
74+
});
75+
76+
const htmlWatcher = chokidar.watch(kComponentsDir, {
77+
persistent: false,
78+
awaitWriteFinish: true,
79+
ignored: (path, stats) => (stats?.isFile() ?? false) && !path.endsWith(".html")
80+
});
81+
82+
const dataFilePath = await fsAsync.access(kDefaultPayloadPath).then(() => kDefaultPayloadPath, () => undefined);
83+
84+
if (dataFilePath === undefined) {
85+
cache.startFromZero = true;
86+
}
87+
88+
const store = {
89+
i18n: { english: { ui: english.ui }, french: { ui: french.ui } },
90+
viewBuilder: new ViewBuilder({
91+
projectRootDir: import.meta.dirname,
92+
componentsDir: kComponentsDir
93+
}),
94+
dataFilePath
95+
};
96+
97+
htmlWatcher.on("change", async(filePath) => {
98+
await buildContext.rebuild().catch(console.error);
99+
store.viewBuilder.freeCache(filePath);
100+
});
101+
102+
const serving = sirv(kOutDir, { dev: true });
103+
104+
function defaultRoute(req: http.IncomingMessage, res: http.ServerResponse) {
105+
if (req.url === "/esbuild") {
106+
const proxyReq = http.request(
107+
{ hostname: esbuildHosts[0], port: esbuildPort, path: req.url, method: req.method, headers: req.headers },
108+
(proxyRes) => {
109+
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
110+
proxyRes.pipe(res);
111+
}
112+
);
113+
114+
proxyReq.on("error", (err) => {
115+
console.error(`[proxy/esbuild] ${err.message}`);
116+
res.writeHead(502);
117+
res.end("Bad Gateway");
118+
});
119+
120+
req.pipe(proxyReq);
121+
122+
return;
123+
}
124+
125+
serving(req, res, () => {
126+
res.writeHead(404);
127+
res.end("Not Found");
128+
});
129+
}
130+
131+
const apiRouter = getApiRouter(defaultRoute);
132+
133+
http.createServer((req, res) => alsContext.run(store, () => apiRouter.lookup(req, res)))
134+
.listen(kDevPort, () => {
135+
console.log(`Dev server: http://localhost:${kDevPort}`);
136+
open(`http://localhost:${kDevPort}`);
137+
});
138+
139+
new WebSocketServerInstanciator({ cache, logger });
140+
141+
console.log("Watching...");

‎package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"lint-fix": "npm run lint -- --fix",
1818
"prepublishOnly": "rimraf ./dist && npm run build && pkg-ok",
1919
"build": "npm run build:front && npm run build:workspaces",
20+
"build:dev": "npm run build:workspaces && npm run build:front:dev",
2021
"build:front": "node ./esbuild.config.js",
22+
"build:front:dev": "node --experimental-strip-types ./esbuild.dev.config.ts",
2123
"build:workspaces": "npm run build --ws --if-present",
2224
"test": "npm run test:cli && npm run lint && npm run lint:css",
2325
"test:cli": "node --no-warnings --test test/**/*.test.js",

‎public/main.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,6 @@ function onSettingsSaved(defaultConfig = null) {
291291
networkView.classList.remove("locked");
292292
});
293293
}
294+
295+
new EventSource("/esbuild").addEventListener("change", () => location.reload());
296+

‎src/commands/http.js‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import open from "open";
88
import * as SemVer from "semver";
99
import * as i18n from "@nodesecure/i18n";
1010
import {
11+
buildServer,
1112
cache,
1213
logger,
13-
buildServer,
1414
WebSocketServerInstanciator
1515
} from "@nodesecure/server";
1616

@@ -51,9 +51,15 @@ export async function start(
5151
cache.prefix = crypto.randomBytes(4).toString("hex");
5252
}
5353

54+
if (enableDeveloperMode) {
55+
const link = "http://127.0.0.1:8080";
56+
console.log(kleur.magenta().bold(await i18n.getToken("cli.http_server_started")), kleur.cyan().bold(link));
57+
open(link);
58+
59+
return;
60+
}
5461
const httpServer = buildServer(dataFilePath, {
5562
port: httpPort,
56-
hotReload: enableDeveloperMode,
5763
runFromPayload,
5864
projectRootDir: kProjectRootDir,
5965
componentsDir: kComponentsDir,

‎workspaces/server/src/ViewBuilder.class.ts‎

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import fs from "node:fs/promises";
55
// Import Third-party Dependencies
66
import zup from "zup";
77
import * as i18n from "@nodesecure/i18n";
8-
import chokidar from "chokidar";
98
import { globStream } from "glob";
109

1110
// Import Internal Dependencies
@@ -24,31 +23,15 @@ export class ViewBuilder {
2423

2524
constructor(options: ViewBuilderOptions) {
2625
const {
27-
autoReload = false,
2826
projectRootDir,
2927
componentsDir
3028
} = options;
3129

3230
this.projectRootDir = projectRootDir;
3331
this.componentsDir = componentsDir;
34-
35-
if (autoReload) {
36-
this.#enableWatcher();
37-
}
38-
}
39-
40-
async #enableWatcher() {
41-
logger.info("[ViewBuilder] autoReload is enabled");
42-
43-
const watcher = chokidar.watch(this.componentsDir, {
44-
persistent: false,
45-
awaitWriteFinish: true,
46-
ignored: (path, stats) => (stats?.isFile() ?? false) && !path.endsWith(".html")
47-
});
48-
watcher.on("change", (filePath) => this.#freeCache(filePath));
4932
}
5033

51-
async #freeCache(
34+
freeCache(
5235
filePath: string
5336
) {
5437
logger.info("[ViewBuilder] the cache has been released");

‎workspaces/server/src/endpoints/index.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Import Node.js Dependencies
2+
import http from "node:http";
3+
14
// Import Third-party Dependencies
25
import router from "find-my-way";
36

@@ -13,9 +16,12 @@ import * as scorecard from "./ossf-scorecard.ts";
1316
import * as locali18n from "./i18n.ts";
1417
import * as report from "./report.ts";
1518

16-
export function getApiRouter() {
19+
type DefaultRoute = (req: http.IncomingMessage, res: http.ServerResponse) => void;
20+
21+
export function getApiRouter(defaultRoute?: DefaultRoute) {
1722
const apiRouter = router({
18-
ignoreTrailingSlash: true
23+
ignoreTrailingSlash: true,
24+
defaultRoute
1925
});
2026

2127
apiRouter.get("/", root.get);

‎workspaces/server/src/index.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@ export function buildServer(
3232
options: BuildServerOptions
3333
) {
3434
const {
35-
hotReload = true,
3635
runFromPayload = true,
3736
projectRootDir,
3837
componentsDir,
3938
i18n
4039
} = options;
4140

4241
const viewBuilder = new ViewBuilder({
43-
autoReload: hotReload,
4442
projectRootDir,
4543
componentsDir
4644
});
@@ -74,6 +72,7 @@ export function buildServer(
7472
export { WebSocketServerInstanciator } from "./websocket/index.ts";
7573
export { logger } from "./logger.ts";
7674
export * as config from "./config.ts";
75+
export { getApiRouter } from "./endpoints/index.ts";
7776

7877
export {
7978
cache

0 commit comments

Comments
 (0)