Skip to content
Merged
90 changes: 0 additions & 90 deletions examples/basic/frontend/ssr-worker-dev.mjs

This file was deleted.

30 changes: 11 additions & 19 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"log"
"os"
"time"

"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/logger"
Expand All @@ -16,27 +17,18 @@ func main() {

dev := os.Getenv("ZEN_ENV") != "production"

ssrCommand := []string{
"node",
"./frontend/ssr-worker-dev.mjs",
cfg := zen.Config{
Dev: dev,
ViteURL: "http://localhost:5173",
RenderURL: "http://localhost:5173/__zen/render",
ClientDist: "./frontend/dist/client",
Manifest: "./frontend/dist/client/.vite/manifest.json",
DefaultTitle: "Zen Basic Example",
RenderTimeout: 5 * time.Second,
}

if !dev {
ssrCommand = []string{
"node",
"../../js/ssr-worker.mjs",
"--entry",
"./frontend/dist/server/entry-server.js",
}
}

cfg := zen.Config{
Dev: dev,
ViteURL: "http://localhost:5173",
SSRCommand: ssrCommand,
ClientDist: "./frontend/dist/client",
Manifest: "./frontend/dist/client/.vite/manifest.json",
DefaultTitle: "Zen Basic Example",
cfg.RenderURL = "http://127.0.0.1:4174/__zen/render"
}

renderer, err := zen.New(cfg)
Expand All @@ -52,7 +44,7 @@ func main() {
app.Get("/", func(c fiber.Ctx) error {
return renderer.Render(c, "Home", map[string]any{
"title": "Zen Basic Example",
"body": "Fiber route, Preact page, Vite build. No ceremony.",
"body": "Fiber route, Preact page, Vite renderer. No pipe slop.",
}, zen.WithTitle("Home"))
})

Expand Down
6 changes: 4 additions & 2 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"private": true,
"type": "module",
"scripts": {
"dev": "pnpm --dir frontend dev & go run .",
"dev": "pnpm dev:renderer & go run .",
"dev:renderer": "node ../../js/dev-renderer.mjs --root ./frontend --entry /src/entry-server.tsx --host 127.0.0.1 --port 5173",
"build": "pnpm --dir frontend build && go build -o ./bin/basic .",
"start": "ZEN_ENV=production ./bin/basic"
"start": "ZEN_ENV=production ./bin/basic",
"start:renderer": "node ../../js/prod-renderer.mjs --entry ./frontend/dist/server/entry-server.js --host 127.0.0.1 --port 4174"
}
}
102 changes: 102 additions & 0 deletions js/dev-renderer.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import http from "node:http";
import { createServer as createViteServer } from "vite";
import {
createHealthResponse,
isHealthRequest,
isRenderRequest,
readJSON,
writeJSON,
writeRendererError
} from "./renderer-shared.mjs";

function parseArgs(argv) {
const args = {
root: process.cwd(),
entry: "/src/entry-server.tsx",
host: "127.0.0.1",
port: 5173
};

for (let i = 0; i < argv.length; i++) {
const item = argv[i];

if (item === "--root") {
args.root = argv[++i] ?? process.cwd();
continue;
}

if (item === "--entry") {
args.entry = argv[++i] ?? "/src/entry-server.tsx";
continue;
}

if (item === "--host") {
args.host = argv[++i] ?? "127.0.0.1";
continue;
}

if (item === "--port") {
args.port = Number(argv[++i] ?? "5173");
continue;
}
}

if (!Number.isInteger(args.port) || args.port <= 0) {
throw new Error("port must be a positive integer");
}

return args;
}

async function main() {
const args = parseArgs(process.argv.slice(2));

const vite = await createViteServer({
root: args.root,
server: {
hmr: false,
middlewareMode: true
},
appType: "custom"
});

const server = http.createServer(async (req, res) => {
if (isHealthRequest(req)) {
writeJSON(res, 200, createHealthResponse("dev"));
return;
}

if (isRenderRequest(req)) {
try {
const body = await readJSON(req);
const mod = await vite.ssrLoadModule(args.entry);

if (typeof mod.render !== "function") {
throw new Error("SSR entry must export render(request)");
}

const result = await mod.render(body);
writeJSON(res, 200, result);
} catch (error) {
vite.ssrFixStacktrace(error);

writeRendererError(res, 500, error, {
includeStack: true
});
}

return;
}

vite.middlewares(req, res);
});

server.listen(args.port, args.host, () => {
process.stdout.write(`Zen dev renderer listening on http://${args.host}:${args.port}\n`);
});
}

main().catch((error) => {
process.stderr.write((error && error.stack ? error.stack : String(error)) + "\n");
process.exit(1);
});
101 changes: 101 additions & 0 deletions js/dev-renderer.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import test from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";

const here = dirname(fileURLToPath(import.meta.url));
const serverPath = join(here, "dev-renderer.mjs");

async function createViteFixture() {
const root = await mkdtemp(join(tmpdir(), "zen-vite-fixture-"));
const src = join(root, "src");

await mkdir(src, {
recursive: true
});

await writeFile(join(root, "package.json"), JSON.stringify({
type: "module",
dependencies: {
vite: "^7.0.0"
}
}));

await writeFile(join(src, "entry-server.js"), `
export async function render(request) {
return {
html: '<main data-page="' + request.page + '">' + request.props.title + '</main>',
head: ''
};
}
`);

return root;
}

async function waitForHealth(port) {
const url = `http://127.0.0.1:${port}/__zen/health`;

for (let i = 0; i < 50; i++) {
try {
const res = await fetch(url);
if (res.ok) {
return;
}
} catch {
await new Promise((resolve) => setTimeout(resolve, 25));
}
}

throw new Error(`renderer did not become healthy on port ${port}`);
}

test("dev renderer renders through vite", async () => {
const root = await createViteFixture();
const port = 4781;

const child = spawn(process.execPath, [
serverPath,
"--root",
root,
"--entry",
"/src/entry-server.js",
"--host",
"127.0.0.1",
"--port",
String(port)
], {
stdio: ["ignore", "pipe", "pipe"]
});

try {
await waitForHealth(port);

const res = await fetch(`http://127.0.0.1:${port}/__zen/render`, {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
url: "/",
page: "Home",
props: {
title: "Hello"
}
})
});

const body = await res.json();

assert.equal(res.status, 200);
assert.equal(body.html, `<main data-page="Home">Hello</main>`);
} finally {
child.kill();
await once(child, "exit");
}
});
Loading