Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tame-radios-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nodesecure/server": minor
---

Implement custom middleware and optional dataFilePath
50 changes: 50 additions & 0 deletions esbuild.common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Import Node.js Dependencies
import path from "node:path";
import fs from "node:fs/promises";

// Import Third-party Dependencies
import esbuild from "esbuild";
import {
getBuildConfiguration
} from "@nodesecure/documentation-ui/node";

// CONSTANTS
export const PUBLIC_DIR = path.join(import.meta.dirname, "public");
export const OUTPUT_DIR = path.join(import.meta.dirname, "dist");
export const NODE_MODULES_DIR = path.join(import.meta.dirname, "node_modules");
export const IMAGES_DIR = path.join(PUBLIC_DIR, "img");

export function getSharedBuildOptions(): esbuild.BuildOptions {
return {
entryPoints: [
path.join(PUBLIC_DIR, "main.js"),
path.join(PUBLIC_DIR, "main.css"),
path.join(NODE_MODULES_DIR, "highlight.js", "styles", "github.css"),
...getBuildConfiguration().entryPoints
],
loader: {
".jpg": "file",
".png": "file",
".woff": "file",
".woff2": "file",
".eot": "file",
".ttf": "file",
".svg": "file"
},
platform: "browser",
bundle: true,
sourcemap: true,
treeShaking: true,
outdir: OUTPUT_DIR
};
}

export async function copyStaticAssets(): Promise<void> {
const imagesFiles = await fs.readdir(IMAGES_DIR);

await Promise.all([
...imagesFiles
.map((name) => fs.copyFile(path.join(IMAGES_DIR, name), path.join(OUTPUT_DIR, name))),
fs.copyFile(path.join(PUBLIC_DIR, "favicon.ico"), path.join(OUTPUT_DIR, "favicon.ico"))
]);
}
44 changes: 0 additions & 44 deletions esbuild.config.js

This file was deleted.

11 changes: 11 additions & 0 deletions esbuild.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Import Third-party Dependencies
import esbuild from "esbuild";

// Import Internal Dependencies
import {
getSharedBuildOptions,
copyStaticAssets
} from "./esbuild.common.ts";

await esbuild.build(getSharedBuildOptions());
await copyStaticAssets();
158 changes: 56 additions & 102 deletions esbuild.dev.config.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
// Import Node.js Dependencies
import fsAsync from "node:fs/promises";
import fs from "node:fs/promises";
import http from "node:http";
import path from "node:path";

// Import Third-party Dependencies
import {
getBuildConfiguration
} from "@nodesecure/documentation-ui/node";
import * as i18n from "@nodesecure/i18n";
import chokidar from "chokidar";
import esbuild from "esbuild";
import open from "open";
import sirv from "sirv";
import { PayloadCache } from "@nodesecure/cache";
import {
WebSocketServerInstanciator,
logger,
ViewBuilder,
getApiRouter,
context as als, type AsyncStoreContext
buildServer
} from "@nodesecure/server";

// Import Internal Dependencies
import english from "./i18n/english.js";
import french from "./i18n/french.js";
import {
PUBLIC_DIR,
OUTPUT_DIR,
getSharedBuildOptions,
copyStaticAssets
} from "./esbuild.common.ts";

// CONSTANTS
const kPublicDir = path.join(import.meta.dirname, "public");
const kOutDir = path.join(import.meta.dirname, "dist");
const kImagesDir = path.join(kPublicDir, "img");
const kNodeModulesDir = path.join(import.meta.dirname, "node_modules");
const kComponentsDir = path.join(kPublicDir, "components");
const kComponentsDir = path.join(PUBLIC_DIR, "components");
const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json");
const kDevPort = Number(process.env.DEV_PORT ?? 8080);

Expand All @@ -39,69 +34,60 @@ await Promise.all([
i18n.extendFromSystemPath(path.join(import.meta.dirname, "i18n"))
]);

const imagesFiles = await fsAsync.readdir(kImagesDir);

await Promise.all([
...imagesFiles
.map((name) => fsAsync.copyFile(path.join(kImagesDir, name), path.join(kOutDir, name))),
fsAsync.copyFile(path.join(kPublicDir, "favicon.ico"), path.join(kOutDir, "favicon.ico"))
]);

const buildContext = await esbuild.context({
entryPoints: [
path.join(kPublicDir, "main.js"),
path.join(kPublicDir, "main.css"),
path.join(kNodeModulesDir, "highlight.js", "styles", "github.css"),
...getBuildConfiguration().entryPoints
],

loader: {
".jpg": "file",
".png": "file",
".woff": "file",
".woff2": "file",
".eot": "file",
".ttf": "file",
".svg": "file"
},
platform: "browser",
bundle: true,
sourcemap: true,
treeShaking: true,
outdir: kOutDir
});
await copyStaticAssets();

const buildContext = await esbuild.context(
getSharedBuildOptions()
);
await buildContext.watch();

const { hosts: esbuildHosts, port: esbuildPort } = await buildContext.serve({
servedir: kOutDir
servedir: OUTPUT_DIR
});

const dataFilePath = await fsAsync.access(
const dataFilePath = await fs.access(
kDefaultPayloadPath
).then(() => kDefaultPayloadPath, () => undefined);
const cache = await new PayloadCache().load();

if (dataFilePath === undefined) {
cache.setCurrentSpec(null);
}
else {
const payloadStr = await fsAsync.readFile(dataFilePath, "utf-8");
const payload = JSON.parse(payloadStr);
await cache.save(payload, { useAsCurrent: true });
}

const store: AsyncStoreContext = {

const { httpServer, cache, viewBuilder } = await buildServer(dataFilePath, {
projectRootDir: import.meta.dirname,
componentsDir: kComponentsDir,
runFromPayload: dataFilePath !== undefined,
i18n: {
english: { ui: english.ui },
french: { ui: french.ui }
},
viewBuilder: new ViewBuilder({
projectRootDir: import.meta.dirname,
componentsDir: kComponentsDir
}),
cache
};
middleware: (req, res, next) => {
if (req.url === "/esbuild") {
const proxyReq = http.request(
{
hostname: esbuildHosts[0],
port: esbuildPort,
path: req.url,
method: req.method,
headers: req.headers
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
proxyRes.pipe(res);
}
);

proxyReq.on("error", (err) => {
console.error(`[proxy/esbuild] ${err.message}`);
res.writeHead(502);
res.end("Bad Gateway");
});

req.pipe(proxyReq);

return;
}

next();
}
});

const htmlWatcher = chokidar.watch(kComponentsDir, {
persistent: false,
awaitWriteFinish: true,
Expand All @@ -110,45 +96,13 @@ const htmlWatcher = chokidar.watch(kComponentsDir, {

htmlWatcher.on("change", async(filePath) => {
await buildContext.rebuild().catch(console.error);
store.viewBuilder.freeCache(filePath);
viewBuilder.freeCache(filePath);
});

const serving = sirv(kOutDir, { dev: true });

function defaultRoute(req: http.IncomingMessage, res: http.ServerResponse) {
if (req.url === "/esbuild") {
const proxyReq = http.request(
{ hostname: esbuildHosts[0], port: esbuildPort, path: req.url, method: req.method, headers: req.headers },
(proxyRes) => {
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
proxyRes.pipe(res);
}
);

proxyReq.on("error", (err) => {
console.error(`[proxy/esbuild] ${err.message}`);
res.writeHead(502);
res.end("Bad Gateway");
});

req.pipe(proxyReq);

return;
}

serving(req, res, () => {
res.writeHead(404);
res.end("Not Found");
});
}

const apiRouter = getApiRouter(defaultRoute);

http.createServer((req, res) => als.run(store, () => apiRouter.lookup(req, res)))
.listen(kDevPort, () => {
console.log(`Dev server: http://localhost:${kDevPort}`);
open(`http://localhost:${kDevPort}`);
});
httpServer.listen(kDevPort, () => {
console.log(`Dev server: http://localhost:${kDevPort}`);
open(`http://localhost:${kDevPort}`);
});

new WebSocketServerInstanciator({ cache, logger });

Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
"lint:css": "stylelint \"**/*.css\" \"public/**/*.js\"",
"lint-fix": "npm run lint -- --fix",
"prepublishOnly": "rimraf ./dist && npm run build && pkg-ok",
"dev": "npm run build:workspaces && node ./esbuild.dev.config.ts",
"build": "npm run build:front && npm run build:workspaces",
"build:dev": "npm run build:workspaces && npm run build:front:dev",
"build:front": "node ./esbuild.config.js",
"build:front:dev": "node --experimental-strip-types ./esbuild.dev.config.ts",
"build:front": "node ./esbuild.config.ts",
"build:workspaces": "npm run build --ws --if-present",
"test": "npm run test:cli && npm run lint && npm run lint:css",
"test:cli": "node --no-warnings --test test/**/*.test.js",
Expand Down
Loading
Loading