Skip to content

Commit 65afd66

Browse files
author
Raulo Erwan.
committed
tech: enable watch mode & esbuild server in dev mode
1 parent 3ec1738 commit 65afd66

8 files changed

Lines changed: 178 additions & 35 deletions

File tree

bin/index.js

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

esbuild.dev.config.ts

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

66
// Import Third-party Dependencies
7-
import open from "open";
8-
import * as SemVer from "semver";
97
import * as i18n from "@nodesecure/i18n";
108
import {
9+
buildServer,
1110
cache,
1211
logger,
13-
buildServer,
1412
WebSocketServerInstanciator
1513
} from "@nodesecure/server";
14+
import open from "open";
15+
import * as SemVer from "semver";
1616

1717
// Import Internal Dependencies
1818
import english from "../../i18n/english.js";
@@ -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: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// Import Node.js Dependencies
2-
import path from "node:path";
32
import fs from "node:fs/promises";
3+
import path from "node:path";
44

55
// Import Third-party Dependencies
6-
import zup from "zup";
76
import * as i18n from "@nodesecure/i18n";
8-
import chokidar from "chokidar";
97
import { globStream } from "glob";
8+
import zup from "zup";
109

1110
// Import Internal Dependencies
1211
import { logger } from "./logger.ts";
@@ -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: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// Import Node.js Dependencies
22
import fs from "node:fs";
3-
import path from "node:path";
43
import http from "node:http";
4+
import path from "node:path";
55

66
// Import Third-party Dependencies
77
import sirv from "sirv";
88

99
// Import Internal Dependencies
10-
import { getApiRouter } from "./endpoints/index.ts";
11-
import { ViewBuilder } from "./ViewBuilder.class.ts";
1210
import {
1311
context,
1412
type AsyncStoreContext,
1513
type NestedStringRecord
1614
} from "./ALS.ts";
1715
import { cache } from "./cache.ts";
16+
import { getApiRouter } from "./endpoints/index.ts";
17+
import { ViewBuilder } from "./ViewBuilder.class.ts";
1818

1919
export interface BuildServerOptions {
2020
hotReload?: boolean;
@@ -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
});
@@ -72,8 +70,8 @@ export function buildServer(
7270
}
7371

7472
export { WebSocketServerInstanciator } from "./websocket/index.ts";
75-
export { logger } from "./logger.ts";
76-
export * as config from "./config.ts";
73+
74+
export { getApiRouter } from "./endpoints/index.ts";
7775

7876
export {
7977
cache

0 commit comments

Comments
 (0)