Skip to content

Commit 2971aff

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

4 files changed

Lines changed: 192 additions & 28 deletions

File tree

esbuild.dev.config.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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 esbuild from "esbuild";
12+
import router from "find-my-way";
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 * as bundle from "./workspaces/server/src/endpoints/bundle.ts";
23+
import * as config from "./workspaces/server/src/endpoints/config.ts";
24+
import * as data from "./workspaces/server/src/endpoints/data.ts";
25+
import * as flags from "./workspaces/server/src/endpoints/flags.ts";
26+
import * as locali18n from "./workspaces/server/src/endpoints/i18n.ts";
27+
import * as npmDownloads from "./workspaces/server/src/endpoints/npm-downloads.ts";
28+
import * as scorecard from "./workspaces/server/src/endpoints/ossf-scorecard.ts";
29+
import * as report from "./workspaces/server/src/endpoints/report.ts";
30+
import * as root from "./workspaces/server/src/endpoints/root.ts";
31+
import * as search from "./workspaces/server/src/endpoints/search.ts";
32+
import { logger } from "./workspaces/server/src/logger.ts";
33+
import { WebSocketServerInstanciator } from "./workspaces/server/src/websocket/index.ts";
34+
35+
// CONSTANTS
36+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
37+
38+
const kPublicDir = path.join(__dirname, "public");
39+
const kOutDir = path.join(__dirname, "dist");
40+
const kImagesDir = path.join(kPublicDir, "img");
41+
const kNodeModulesDir = path.join(__dirname, "node_modules");
42+
const kComponentsDir = path.join(kPublicDir, "components");
43+
const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json");
44+
45+
const kDevPort = Number(process.env.DEV_PORT ?? 8080);
46+
47+
await i18n.getLocalLang();
48+
await i18n.extendFromSystemPath(path.join(__dirname, "i18n"));
49+
50+
const imagesFiles = await fsAsync.readdir(kImagesDir);
51+
52+
await Promise.all([
53+
...imagesFiles
54+
.map((name) => fsAsync.copyFile(path.join(kImagesDir, name), path.join(kOutDir, name))),
55+
fsAsync.copyFile(path.join(kPublicDir, "favicon.ico"), path.join(kOutDir, "favicon.ico"))
56+
]);
57+
58+
const buildContext = await esbuild.context({
59+
entryPoints: [
60+
path.join(kPublicDir, "main.js"),
61+
path.join(kPublicDir, "main.css"),
62+
path.join(kNodeModulesDir, "highlight.js", "styles", "github.css"),
63+
...getBuildConfiguration().entryPoints
64+
],
65+
66+
loader: {
67+
".jpg": "file",
68+
".png": "file",
69+
".woff": "file",
70+
".woff2": "file",
71+
".eot": "file",
72+
".ttf": "file",
73+
".svg": "file"
74+
},
75+
platform: "browser",
76+
bundle: true,
77+
sourcemap: true,
78+
treeShaking: true,
79+
outdir: kOutDir
80+
});
81+
82+
await buildContext.watch();
83+
84+
const { hosts: esbuildHosts, port: esbuildPort } = await buildContext.serve({
85+
servedir: kOutDir
86+
});
87+
88+
const dataFilePath = fs.existsSync(kDefaultPayloadPath) ? kDefaultPayloadPath : undefined;
89+
90+
if (dataFilePath === undefined) {
91+
cache.startFromZero = true;
92+
}
93+
94+
const store = {
95+
i18n: { english: { ui: english.ui }, french: { ui: french.ui } },
96+
viewBuilder: new ViewBuilder({
97+
autoReload: true,
98+
projectRootDir: __dirname,
99+
componentsDir: kComponentsDir
100+
}),
101+
dataFilePath
102+
};
103+
104+
const serving = sirv(kOutDir, { dev: true });
105+
106+
const apiRouter = router({
107+
ignoreTrailingSlash: true,
108+
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+
136+
// same as workspaces/server/src/endpoints/index.ts ---
137+
apiRouter.get("/", root.get);
138+
apiRouter.get("/data", data.get);
139+
apiRouter.get("/config", config.get);
140+
apiRouter.put("/config", config.save);
141+
apiRouter.get("/i18n", locali18n.get);
142+
apiRouter.get("/search/:packageName", search.get);
143+
apiRouter.get("/search-versions/:packageName", search.versions);
144+
apiRouter.get("/flags", flags.getAll);
145+
apiRouter.get("/flags/description/:title", flags.get);
146+
apiRouter.get("/bundle/:packageName", bundle.get);
147+
apiRouter.get("/bundle/:packageName/:version", bundle.get);
148+
apiRouter.get("/downloads/:packageName", npmDownloads.get);
149+
apiRouter.get("/scorecard/:org/:packageName", scorecard.get);
150+
apiRouter.post("/report", report.post);
151+
152+
http.createServer((req, res) => alsContext.run(store, () => apiRouter.lookup(req, res)))
153+
.listen(kDevPort, () => {
154+
console.log(`Dev server: http://localhost:${kDevPort}`);
155+
open(`http://localhost:${kDevPort}`);
156+
});
157+
158+
new WebSocketServerInstanciator({ cache, logger });
159+
160+
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:front:dev && npm run build:workspaces",
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: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -51,37 +51,36 @@ export async function start(
5151
cache.prefix = crypto.randomBytes(4).toString("hex");
5252
}
5353

54-
const httpServer = buildServer(dataFilePath, {
55-
port: httpPort,
56-
hotReload: enableDeveloperMode,
57-
runFromPayload,
58-
projectRootDir: kProjectRootDir,
59-
componentsDir: kComponentsDir,
60-
i18n: {
61-
english,
62-
french
63-
}
64-
});
65-
66-
httpServer.listen(httpPort, async() => {
67-
const link = `http://localhost:${httpServer.address().port}`;
68-
console.log(kleur.magenta().bold(await i18n.getToken("cli.http_server_started")), kleur.cyan().bold(link));
69-
70-
open(link);
71-
});
54+
if (enableDeveloperMode) {
55+
// todo: ping/warn if dev server is not up & running
56+
open("http://127.0.0.1:8080");
57+
}
58+
else {
59+
const httpServer = buildServer(dataFilePath, {
60+
port: httpPort,
61+
hotReload: enableDeveloperMode,
62+
runFromPayload,
63+
projectRootDir: kProjectRootDir,
64+
componentsDir: kComponentsDir,
65+
i18n: {
66+
english,
67+
french
68+
}
69+
});
7270

73-
new WebSocketServerInstanciator({
74-
cache,
75-
logger
76-
});
71+
new WebSocketServerInstanciator({
72+
cache,
73+
logger
74+
});
7775

78-
for (const eventName of ["SIGINT", "SIGTERM"]) {
79-
process.on(eventName, () => {
80-
httpServer.close();
76+
for (const eventName of ["SIGINT", "SIGTERM"]) {
77+
process.on(eventName, () => {
78+
httpServer.close();
8179

82-
console.log(kleur.red().bold(`${eventName} signal received.`));
83-
process.exit(0);
84-
});
80+
console.log(kleur.red().bold(`${eventName} signal received.`));
81+
process.exit(0);
82+
});
83+
}
8584
}
8685
}
8786

0 commit comments

Comments
 (0)