Generate static site (html + js + css)? #254
-
|
The example seems to assume we want to run Deno on server side. Is it possible to generate static site which can be for instance deployed to github pages? |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 3 replies
-
|
Not easily, no. You could write your main fresh app and then create a secondary program that scrapes your main app during a build process and emits static files. I haven't tried this, but it should work. Mandatory shilling: have you tried hosting your fresh app on Deno Deploy? It's free for most projects :) |
Beta Was this translation helpful? Give feedback.
-
|
One significant downside of no SSG in Fresh is that it makes it far less compatible with @tauri-apps for easy creation of desktop apps. From their Discord:
|
Beta Was this translation helpful? Give feedback.
-
|
I am integrating Lume to work alongside fresh (link to repo). Here is how it is designed to be working so far. Keen for suggestions on improvements. |
Beta Was this translation helpful? Give feedback.
-
|
this might help to build a static website coding in html + js/ts with https://github.com/mindon/packup |
Beta Was this translation helpful? Give feedback.
-
|
jamstack.org lists 335 different frameworks you can use to make static sites. IMO the whole advantage of Fresh is that it allows you to generate server pages dynamically (like Rails), and also provide the minimum amount of JS for interactivity (like jQuery) in an expression space you can make sense of (like React). |
Beta Was this translation helpful? Give feedback.
-
|
If you REALLY wanted to.... And answering the question on how to create a static export that can be deployed and hosted on any web server that can serve HTML/CSS/JS static assets. Create a interface Route {
path: string;
filename: string;
}
const config = {
outDir: "_fresh/dist",
routesDir: "routes",
clientAssetsDir: "_fresh/client",
staticDir: "static",
serverPort: 8000,
serverStartupDelayMs: 3000,
};
const copyDir = async (src: string, dest: string): Promise<void> => {
await Deno.mkdir(dest, { recursive: true });
for await (const entry of Deno.readDir(src)) {
const srcPath = `${src}/${entry.name}`;
const destPath = `${dest}/${entry.name}`;
if (entry.isDirectory) {
await copyDir(srcPath, destPath);
} else {
await Deno.copyFile(srcPath, destPath);
}
}
};
const shouldSkipEntry = (name: string): boolean =>
name.startsWith("_") || name.startsWith(".") || name === "api";
const getRouteFromFile = (
entryName: string,
baseRoute: string,
): Route | null => {
const isIndex = entryName === "index.tsx" || entryName === "index.ts";
const name = entryName.replace(/\.(tsx|ts)$/, "");
if (isIndex) {
if (baseRoute) {
const path = baseRoute.replace(/^routes/, "").replace(/^([^/])/, "/$1");
return { path, filename: `${path.slice(1)}/index.html` };
}
return { path: "/", filename: "index.html" };
}
const path = `${baseRoute}/${name}`.replace(/^routes/, "").replace(
/^([^/])/,
"/$1",
);
return { path, filename: `${path.slice(1)}.html` };
};
const discoverRoutes = async (
dir: string,
baseRoute = "",
): Promise<Route[]> => {
const routes: Route[] = [];
try {
for await (const entry of Deno.readDir(dir)) {
if (shouldSkipEntry(entry.name)) continue;
const fullPath = `${dir}/${entry.name}`;
const newBaseRoute = baseRoute
? `${baseRoute}/${entry.name}`
: entry.name;
if (entry.isDirectory) {
const subRoutes = await discoverRoutes(fullPath, newBaseRoute);
routes.push(...subRoutes);
} else if (entry.isFile && /\.(tsx|ts)$/.test(entry.name)) {
const route = getRouteFromFile(entry.name, baseRoute);
if (route) routes.push(route);
}
}
} catch (error) {
if (!(error instanceof Deno.errors.NotFound)) throw error;
}
return routes;
};
const copyClientAssets = async (): Promise<void> => {
await copyDir(`${config.clientAssetsDir}/assets`, `${config.outDir}/assets`);
try {
await copyDir(`${config.clientAssetsDir}/.vite`, `${config.outDir}/.vite`);
} catch {
// .vite folder might not exist
}
};
const copyStaticFiles = async (): Promise<void> => {
try {
for await (const entry of Deno.readDir(config.staticDir)) {
const srcPath = `${config.staticDir}/${entry.name}`;
const destPath = `${config.outDir}/${entry.name}`;
if (entry.isDirectory) {
await copyDir(srcPath, destPath);
} else {
await Deno.copyFile(srcPath, destPath);
}
}
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.log("No static files to copy.");
} else {
throw error;
}
}
};
const writeHtmlFile = async (route: Route, html: string): Promise<void> => {
const filePath = `${config.outDir}/${route.filename}`;
const dirPath = filePath.substring(0, filePath.lastIndexOf("/"));
if (dirPath && dirPath !== config.outDir) {
await Deno.mkdir(dirPath, { recursive: true });
}
await Deno.writeTextFile(filePath, html);
};
const renderRoute = async (route: Route): Promise<void> => {
const url = `http://localhost:${config.serverPort}${route.path}`;
console.log(`Rendering ${route.path}...`);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const html = await response.text();
await writeHtmlFile(route, html);
console.log(`✓ Exported ${route.filename}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`✗ Failed to export ${route.path}: ${message}`);
}
};
const startServer = async (): Promise<Deno.ChildProcess> => {
console.log("\nStarting temporary server to render pages...");
const server = new Deno.Command("deno", {
args: ["task", "start"],
stdout: "null",
stderr: "null",
}).spawn();
await new Promise((resolve) =>
setTimeout(resolve, config.serverStartupDelayMs)
);
return server;
};
const shutdownServer = async (server: Deno.ChildProcess): Promise<void> => {
server.kill("SIGTERM");
await server.status;
};
const main = async (): Promise<void> => {
console.log("Exporting static site...");
await Deno.mkdir(config.outDir, { recursive: true });
console.log("Copying client assets...");
await copyClientAssets();
console.log("Copying static files...");
await copyStaticFiles();
console.log("Discovering routes...");
const routes = await discoverRoutes(config.routesDir);
console.log(`Found ${routes.length} route(s):`);
routes.forEach((r) => console.log(` ${r.path} -> ${r.filename}`));
const server = await startServer();
await Promise.all(routes.map(renderRoute));
await shutdownServer(server);
console.log(`✓ Static site exported to ${config.outDir}/`);
};
if (import.meta.main) {
main().catch((error) => {
console.error("Export failed:", error);
Deno.exit(1);
});
}In your deno.json add a new task Run the task: Test the app by dragging the generated |
Beta Was this translation helpful? Give feedback.
Not easily, no. You could write your main fresh app and then create a secondary program that scrapes your main app during a build process and emits static files. I haven't tried this, but it should work.
Mandatory shilling: have you tried hosting your fresh app on Deno Deploy? It's free for most projects :)