defuss-ssg 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Usage
23
23
  Simply generate a static site from a content directory to an output directory with full defuss-MDX (GFM + Frontmatter) support:
24
24
 
25
25
  ```bash
26
- npx defuss-ssg build ./folder
26
+ bunx defuss-ssg build ./folder
27
27
  ```
28
28
 
29
29
  Or install globally or locally in a project:
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { v as validateProjectDir, b as build, s as serve } from './serve-CMNixAKF.mjs';
2
+ import { v as validateProjectDir, b as build, s as serve } from './serve-BZhZ9J90.mjs';
3
3
  import { join, dirname, resolve } from 'node:path';
4
4
  import { existsSync, readFileSync } from 'node:fs';
5
5
  import { spawn } from 'node:child_process';
package/dist/index.cjs CHANGED
@@ -83,14 +83,21 @@ const autoHydratePlugin = {
83
83
  ;(async function(){
84
84
 
85
85
  const props = ${JSON.stringify(props || {})};
86
- const { hydrate } = await import("/components/runtime.js");
87
- const expports = (await import("${clientSrcFile}"));
86
+ const cacheBust = "?v=" + Date.now();
87
+ console.log("[hydrate:${id}] Starting hydration, cacheBust=" + cacheBust);
88
+ console.log("[hydrate:${id}] Importing runtime from /components/runtime.js" + cacheBust);
89
+ const { hydrate } = await import("/components/runtime.js" + cacheBust);
90
+ console.log("[hydrate:${id}] Importing component from ${clientSrcFile}" + cacheBust);
91
+ const expports = (await import("${clientSrcFile}" + cacheBust));
92
+
93
+ console.log("[hydrate:${id}] Module keys:", Object.keys(expports));
88
94
 
89
95
  if (!expports || typeof expports.default !== "function") {
90
96
  console.error("Hydration error: No default export function found in", "${clientSrcFile}");
91
97
  return;
92
98
  }
93
99
  const Component = expports.default;
100
+ console.log("[hydrate:${id}] Component:", Component.name || "(anonymous)");
94
101
 
95
102
  let roots = null;
96
103
  try {
@@ -105,7 +112,11 @@ const autoHydratePlugin = {
105
112
  } else {
106
113
  console.error("Hydration error: Component MUST return a single root element, not an array of elements! (no fragments allowed)");
107
114
  }
115
+
116
+ console.log("[hydrate:${id}] Rendered VDOM roots:", roots.length, JSON.stringify(roots).slice(0, 200));
117
+
108
118
  const wrapper = document.querySelector('div[data-hydrate-id="${id}"]');
119
+ console.log("[hydrate:${id}] Wrapper found:", !!wrapper, wrapper?.childNodes?.length, "children");
109
120
 
110
121
  // remove <script> itself
111
122
  document.getElementById("${id}")?.remove();
@@ -119,6 +130,7 @@ const autoHydratePlugin = {
119
130
  }
120
131
  // unwrap children
121
132
  wrapper.replaceWith(...wrapper.childNodes);
133
+ console.log("[hydrate:${id}] Hydration complete, wrapper unwrapped");
122
134
  } else {
123
135
  console.warn("No wrapper found for hydration id ${id}");
124
136
  }
@@ -203,6 +215,289 @@ const validateProjectDir = (projectDir) => {
203
215
  return { code: "OK", message: "Project directory is valid." };
204
216
  };
205
217
 
218
+ const HTTP_METHODS = [
219
+ "GET",
220
+ "POST",
221
+ "PUT",
222
+ "DELETE",
223
+ "PATCH",
224
+ "HEAD",
225
+ "OPTIONS",
226
+ "ALL"
227
+ ];
228
+ const createRedirect = (url, status = 302) => new Response(null, {
229
+ status,
230
+ headers: { Location: url }
231
+ });
232
+ const endpointFileToRoute = (filePath, pagesDir) => {
233
+ let route = node_path.relative(pagesDir, filePath).replace(/\\/g, "/").replace(/\.(ts|js)$/, "");
234
+ if (!route.startsWith("/")) {
235
+ route = `/${route}`;
236
+ }
237
+ return route;
238
+ };
239
+ const routeToExpressPattern = (route) => route.replace(/\[([^\]]+)\]/g, ":$1");
240
+ const extractParamNames = (route) => Array.from(route.matchAll(/\[([^\]]+)\]/g), (m) => m[1]);
241
+ const discoverEndpointSourceFiles = async (pagesDir) => {
242
+ if (!node_fs.existsSync(pagesDir)) return [];
243
+ return glob.async(node_path.join(pagesDir, "**/*.{ts,js}"), {
244
+ ignore: ["**/*.d.ts"]
245
+ });
246
+ };
247
+ const compileEndpoints = async (sourceFiles, pagesDir, outDir, debug = false) => {
248
+ if (sourceFiles.length === 0) return /* @__PURE__ */ new Map();
249
+ if (debug) {
250
+ console.log(`Compiling ${sourceFiles.length} endpoint source file(s)\u2026`);
251
+ }
252
+ console.time("[endpoints] esbuild-compile");
253
+ await esbuild.build({
254
+ entryPoints: sourceFiles,
255
+ format: "esm",
256
+ bundle: true,
257
+ platform: "node",
258
+ target: ["esnext"],
259
+ outdir: outDir,
260
+ outbase: pagesDir,
261
+ outExtension: { ".js": ".mjs" }
262
+ });
263
+ console.timeEnd("[endpoints] esbuild-compile");
264
+ const mapping = /* @__PURE__ */ new Map();
265
+ for (const src of sourceFiles) {
266
+ const rel = node_path.relative(pagesDir, src).replace(/\.(ts|js)$/, ".mjs");
267
+ mapping.set(src, node_path.join(outDir, rel));
268
+ }
269
+ return mapping;
270
+ };
271
+ const loadEndpointModule = async (filePath) => {
272
+ const code = await promises.readFile(filePath, "utf-8");
273
+ const encoded = Buffer.from(code).toString("base64");
274
+ const dataUrl = `data:text/javascript;base64,${encoded}`;
275
+ return import(dataUrl);
276
+ };
277
+ const resolveEndpoints = async (pagesDir, endpointsDir, debug = false) => {
278
+ const sourceFiles = await discoverEndpointSourceFiles(pagesDir);
279
+ if (sourceFiles.length === 0) return [];
280
+ console.time("[endpoints] compile-all");
281
+ const mapping = await compileEndpoints(sourceFiles, pagesDir, endpointsDir, debug);
282
+ console.timeEnd("[endpoints] compile-all");
283
+ const endpoints = [];
284
+ console.time("[endpoints] load-modules");
285
+ for (const [sourceFile, compiledFile] of mapping) {
286
+ const routePattern = endpointFileToRoute(sourceFile, pagesDir);
287
+ const paramNames = extractParamNames(routePattern);
288
+ const isDynamic = paramNames.length > 0;
289
+ if (debug) {
290
+ console.log(
291
+ `Endpoint: ${sourceFile} \u2192 ${compiledFile} \u2192 ${routePattern}` + (isDynamic ? ` (params: ${paramNames.join(", ")})` : "")
292
+ );
293
+ }
294
+ const module = await loadEndpointModule(compiledFile);
295
+ const prerender = module.prerender === true;
296
+ endpoints.push({
297
+ sourceFile,
298
+ compiledFile,
299
+ routePattern,
300
+ module,
301
+ isDynamic,
302
+ paramNames,
303
+ prerender
304
+ });
305
+ }
306
+ console.timeEnd("[endpoints] load-modules");
307
+ return endpoints;
308
+ };
309
+ const buildEndpoints = async (projectDir, config, debug = false) => {
310
+ const pagesDir = node_path.join(projectDir, config.pages);
311
+ const outputDir = node_path.join(projectDir, config.output);
312
+ const endpointsDir = node_path.join(projectDir, ".endpoints");
313
+ const endpoints = await resolveEndpoints(pagesDir, endpointsDir, debug);
314
+ if (endpoints.length === 0) return;
315
+ const prerenderEndpoints = endpoints.filter((e) => e.prerender);
316
+ const dynamicEndpoints = endpoints.filter((e) => !e.prerender);
317
+ console.log(
318
+ `Endpoints: ${endpoints.length} compiled` + (prerenderEndpoints.length ? `, ${prerenderEndpoints.length} pre-rendered` : "") + (dynamicEndpoints.length ? `, ${dynamicEndpoints.length} dynamic` : "")
319
+ );
320
+ for (const endpoint of prerenderEndpoints) {
321
+ const { module, routePattern, isDynamic } = endpoint;
322
+ const handler = module.GET;
323
+ if (!handler) {
324
+ if (debug) {
325
+ console.log(
326
+ `Endpoint ${routePattern}: prerender=true but no GET export \u2013 skipping`
327
+ );
328
+ }
329
+ continue;
330
+ }
331
+ let paramSets = [{}];
332
+ if (isDynamic) {
333
+ if (!module.getStaticPaths) {
334
+ console.warn(
335
+ `Dynamic endpoint ${routePattern} has no getStaticPaths() \u2013 skipping`
336
+ );
337
+ continue;
338
+ }
339
+ const paths = await module.getStaticPaths();
340
+ paramSets = paths.map((p) => p.params);
341
+ }
342
+ for (const params of paramSets) {
343
+ let resolvedRoute = routePattern;
344
+ for (const [key, value] of Object.entries(params)) {
345
+ resolvedRoute = resolvedRoute.replace(`[${key}]`, value);
346
+ }
347
+ const requestUrl = `http://localhost${resolvedRoute}`;
348
+ const context = {
349
+ params,
350
+ request: new Request(requestUrl),
351
+ redirect: createRedirect
352
+ };
353
+ if (debug) {
354
+ console.log(`Pre-rendering endpoint: ${resolvedRoute}`);
355
+ }
356
+ try {
357
+ const response = await handler(context);
358
+ const outputFile = node_path.join(outputDir, resolvedRoute);
359
+ const outputFileDir = node_path.dirname(outputFile);
360
+ if (!node_fs.existsSync(outputFileDir)) {
361
+ node_fs.mkdirSync(outputFileDir, { recursive: true });
362
+ }
363
+ if (response.status >= 300 && response.status < 400) {
364
+ const location = response.headers.get("Location") || "/";
365
+ await promises.writeFile(
366
+ outputFile,
367
+ `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=${location}"><link rel="canonical" href="${location}"></head><body>Redirecting to <a href="${location}">${location}</a></body></html>`
368
+ );
369
+ } else {
370
+ const buffer = Buffer.from(await response.arrayBuffer());
371
+ await promises.writeFile(outputFile, buffer);
372
+ }
373
+ if (debug) {
374
+ console.log(` \u2192 ${outputFile}`);
375
+ }
376
+ } catch (error) {
377
+ console.error(`Error pre-rendering endpoint ${resolvedRoute}:`, error);
378
+ }
379
+ }
380
+ }
381
+ };
382
+ const handleEndpointRoute = async (req, res, compiledFile, routePattern, method) => {
383
+ let currentModule;
384
+ try {
385
+ currentModule = await loadEndpointModule(compiledFile);
386
+ } catch (err) {
387
+ console.error(`Failed to load endpoint: ${compiledFile}`, err);
388
+ res.status(500).send("Internal Server Error");
389
+ return;
390
+ }
391
+ const handlerFn = currentModule[method] ?? currentModule.ALL;
392
+ if (!handlerFn) {
393
+ res.status(405).send("Method Not Allowed");
394
+ return;
395
+ }
396
+ const protocol = req.protocol || "http";
397
+ const host = req.get?.("host") || req.headers?.host || "localhost";
398
+ const url = `${protocol}://${host}${req.originalUrl}`;
399
+ const reqInit = {
400
+ method: req.method,
401
+ headers: req.headers
402
+ };
403
+ if (["POST", "PUT", "PATCH", "DELETE"].includes(req.method)) {
404
+ try {
405
+ const chunks = [];
406
+ await new Promise((resolve, reject) => {
407
+ req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
408
+ req.on("end", resolve);
409
+ req.on("error", reject);
410
+ });
411
+ if (chunks.length > 0) {
412
+ reqInit.body = Buffer.concat(chunks);
413
+ }
414
+ } catch {
415
+ }
416
+ }
417
+ const request = new Request(url, reqInit);
418
+ const context = {
419
+ params: req.params || {},
420
+ request,
421
+ redirect: createRedirect
422
+ };
423
+ try {
424
+ const response = await handlerFn(context);
425
+ res.status(response.status);
426
+ response.headers.forEach((value, key) => {
427
+ res.set(key, value);
428
+ });
429
+ if (req.method === "HEAD") {
430
+ res.end();
431
+ return;
432
+ }
433
+ if (response.body) {
434
+ const buf = Buffer.from(await response.arrayBuffer());
435
+ res.send(buf);
436
+ } else {
437
+ res.end();
438
+ }
439
+ } catch (error) {
440
+ console.error(`Endpoint error ${req.method} ${routePattern}:`, error);
441
+ res.status(500).send("Internal Server Error");
442
+ }
443
+ };
444
+ const registerEndpoints = async (app, projectDir, config, debug = false) => {
445
+ const endpointsDir = node_path.join(projectDir, ".endpoints");
446
+ const mjsFiles = await glob.async(node_path.join(endpointsDir, "**/*.mjs"));
447
+ if (mjsFiles.length === 0) return;
448
+ let registered = 0;
449
+ for (const compiledFile of mjsFiles) {
450
+ let route = node_path.relative(endpointsDir, compiledFile).replace(/\\/g, "/").replace(/\.mjs$/, "");
451
+ if (!route.startsWith("/")) route = `/${route}`;
452
+ let module;
453
+ try {
454
+ module = await loadEndpointModule(compiledFile);
455
+ } catch (err) {
456
+ console.error(`Failed to load endpoint: ${compiledFile}`, err);
457
+ continue;
458
+ }
459
+ if (module.prerender === true) {
460
+ if (debug) {
461
+ console.log(`Skipping prerender endpoint: ${route}`);
462
+ }
463
+ continue;
464
+ }
465
+ const expressRoute = routeToExpressPattern(route);
466
+ const hasExportedMethods = HTTP_METHODS.some(
467
+ (m) => m in module && typeof module[m] === "function"
468
+ );
469
+ if (!hasExportedMethods) continue;
470
+ if (debug) {
471
+ console.log(`Registering dynamic endpoint: ${expressRoute}`);
472
+ }
473
+ for (const method of HTTP_METHODS) {
474
+ if (method in module && typeof module[method] === "function") {
475
+ const expressMethod = method === "ALL" ? "all" : method.toLowerCase();
476
+ app[expressMethod](
477
+ expressRoute,
478
+ async (req, res) => handleEndpointRoute(req, res, compiledFile, route, method)
479
+ );
480
+ if (debug) {
481
+ console.log(` ${method} ${expressRoute}`);
482
+ }
483
+ }
484
+ }
485
+ if (module.GET && !module.HEAD) {
486
+ app.head(
487
+ expressRoute,
488
+ async (req, res) => handleEndpointRoute(req, res, compiledFile, route, "GET")
489
+ );
490
+ if (debug) {
491
+ console.log(` HEAD ${expressRoute} (auto from GET)`);
492
+ }
493
+ }
494
+ registered++;
495
+ }
496
+ if (registered > 0) {
497
+ console.log(`Registered ${registered} dynamic endpoint(s)`);
498
+ }
499
+ };
500
+
206
501
  const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
207
502
  const __dirname$1 = node_path.dirname(__filename$1);
208
503
  const build = async ({
@@ -214,7 +509,9 @@ const build = async ({
214
509
  const projectDirStatus = validateProjectDir(projectDir);
215
510
  if (projectDirStatus.code !== "OK") return projectDirStatus;
216
511
  const startTime = performance.now();
512
+ console.time("[build] readConfig");
217
513
  const config = await readConfig(projectDir, debug);
514
+ console.timeEnd("[build] readConfig");
218
515
  if (debug) {
219
516
  console.log("Using config:", config);
220
517
  }
@@ -264,29 +561,44 @@ const build = async ({
264
561
  const isFullBuild = changeKind === "full" || changeKind === "config";
265
562
  const tempExists = node_fs.existsSync(config.tmp);
266
563
  if (isFullBuild) {
564
+ console.time("[build] pre-plugins");
267
565
  for (const plugin of config.plugins || []) {
268
566
  if (plugin.phase === "pre" && (plugin.mode === mode || plugin.mode === "both")) {
567
+ console.time(`[build] pre-plugin:${plugin.name}`);
269
568
  if (debug) {
270
569
  console.log(`Running pre-plugin: ${plugin.name}`);
271
570
  }
272
571
  await plugin.fn(projectDir, config);
572
+ console.timeEnd(`[build] pre-plugin:${plugin.name}`);
273
573
  }
274
574
  }
575
+ console.timeEnd("[build] pre-plugins");
275
576
  }
577
+ console.time("[build] prepare-temp");
276
578
  if (isFullBuild || !tempExists) {
277
579
  if (tempExists) {
580
+ console.time("[build] prepare-temp:rmSync");
278
581
  node_fs.rmSync(config.tmp, { recursive: true });
582
+ console.timeEnd("[build] prepare-temp:rmSync");
279
583
  }
584
+ console.time("[build] prepare-temp:cp");
280
585
  await promises.cp(projectDir, config.tmp, {
281
586
  recursive: true,
282
587
  filter: (src) => {
283
- const relative = src.replace(node_path.join(projectDir, ""), "");
284
- if (relative.startsWith(node_path.join("assets", "")) || relative.startsWith(node_path.join("node_modules", ""))) {
588
+ const relative = src.startsWith(projectDir) ? src.slice(projectDir.length).replace(/^\//, "") : src;
589
+ const firstSegment = relative.split("/")[0];
590
+ if (firstSegment === "node_modules" || firstSegment === config.output || firstSegment === ".endpoints" || firstSegment === ".ssg-temp" || firstSegment === ".git" || firstSegment === "assets") {
285
591
  return false;
286
592
  }
287
593
  return true;
288
594
  }
289
595
  });
596
+ console.timeEnd("[build] prepare-temp:cp");
597
+ const srcNodeModules = node_path.join(projectDir, "node_modules");
598
+ const tmpNodeModules = node_path.join(config.tmp, "node_modules");
599
+ if (node_fs.existsSync(srcNodeModules) && !node_fs.existsSync(tmpNodeModules)) {
600
+ node_fs.symlinkSync(srcNodeModules, tmpNodeModules, "dir");
601
+ }
290
602
  } else {
291
603
  const srcFile = node_path.join(projectDir, changedRelative);
292
604
  const destFile = node_path.join(config.tmp, changedRelative);
@@ -298,6 +610,8 @@ const build = async ({
298
610
  await promises.cp(srcFile, destFile);
299
611
  }
300
612
  }
613
+ console.timeEnd("[build] prepare-temp");
614
+ console.time("[build] copy-hydration");
301
615
  await promises.cp(
302
616
  node_path.resolve(node_path.join(__dirname$1, "components", "index.mjs")),
303
617
  node_path.join(tmpComponentsDir, "hydrate.tsx")
@@ -306,7 +620,9 @@ const build = async ({
306
620
  node_path.resolve(node_path.join(__dirname$1, "runtime.mjs")),
307
621
  node_path.join(tmpComponentsDir, "runtime.ts")
308
622
  );
623
+ console.timeEnd("[build] copy-hydration");
309
624
  if (changeKind !== "asset") {
625
+ console.time("[build] esbuild-pages");
310
626
  const pageEntryPoints = changeKind === "page" ? [node_path.join(config.tmp, changedRelative)] : [node_path.join(tmpPagesDir, "**/*.mdx")];
311
627
  await esbuild.build({
312
628
  entryPoints: pageEntryPoints,
@@ -325,8 +641,10 @@ const build = async ({
325
641
  })
326
642
  ]
327
643
  });
644
+ console.timeEnd("[build] esbuild-pages");
328
645
  }
329
646
  if (isFullBuild || changeKind === "component") {
647
+ console.time("[build] esbuild-components");
330
648
  await esbuild.build({
331
649
  entryPoints: [
332
650
  node_path.join(tmpComponentsDir, "**/*.tsx"),
@@ -338,8 +656,10 @@ const build = async ({
338
656
  target: ["esnext"],
339
657
  outdir: tmpComponentsDir
340
658
  });
659
+ console.timeEnd("[build] esbuild-components");
341
660
  }
342
661
  if (changeKind !== "asset") {
662
+ console.time("[build] render-pages");
343
663
  let outputFiles;
344
664
  if (changeKind === "page") {
345
665
  const jsFile = node_path.join(config.tmp, changedRelative.replace(/\.mdx$/, ".js"));
@@ -356,20 +676,26 @@ const build = async ({
356
676
  `${tmpPagesDir}${node_path.sep}`,
357
677
  ""
358
678
  );
679
+ const pageLabel = relativeOutputHtmlFilePath;
359
680
  if (debug) {
360
681
  console.log("Processing output file (JS):", outputFile);
361
682
  console.log("Relative output HTML path:", relativeOutputHtmlFilePath);
362
683
  }
684
+ console.time(`[build] page:${pageLabel} import`);
363
685
  const code = await promises.readFile(outputFile, "utf-8");
364
686
  const encoded = Buffer.from(code).toString("base64");
365
687
  const dataUrl = `data:text/javascript;base64,${encoded}`;
366
688
  const exports$1 = await import(dataUrl);
689
+ console.timeEnd(`[build] page:${pageLabel} import`);
367
690
  if (debug) {
368
691
  console.log("exports", exports$1);
369
692
  }
693
+ console.time(`[build] page:${pageLabel} vdom`);
370
694
  let vdom = exports$1.default(exports$1);
695
+ console.timeEnd(`[build] page:${pageLabel} vdom`);
371
696
  for (const plugin of config.plugins || []) {
372
697
  if (plugin.phase === "page-vdom" && (plugin.mode === mode || plugin.mode === "both")) {
698
+ console.time(`[build] page:${pageLabel} plugin:${plugin.name}`);
373
699
  if (debug) {
374
700
  console.log(`Running page-vdom plugin: ${plugin.name}`);
375
701
  }
@@ -380,16 +706,20 @@ const build = async ({
380
706
  config,
381
707
  exports$1
382
708
  );
709
+ console.timeEnd(`[build] page:${pageLabel} plugin:${plugin.name}`);
383
710
  }
384
711
  }
712
+ console.time(`[build] page:${pageLabel} renderSync`);
385
713
  const browserGlobals = server.getBrowserGlobals();
386
714
  const document = server.getDocument(false, browserGlobals);
387
715
  browserGlobals.document = document;
388
716
  let el = server.renderSync(vdom, document.documentElement, {
389
717
  browserGlobals
390
718
  });
719
+ console.timeEnd(`[build] page:${pageLabel} renderSync`);
391
720
  for (const plugin of config.plugins || []) {
392
721
  if (plugin.phase === "page-dom" && (plugin.mode === mode || plugin.mode === "both")) {
722
+ console.time(`[build] page:${pageLabel} dom-plugin:${plugin.name}`);
393
723
  if (debug) {
394
724
  console.log(`Running page-dom plugin: ${plugin.name}`);
395
725
  }
@@ -399,11 +729,15 @@ const build = async ({
399
729
  projectDir,
400
730
  config
401
731
  );
732
+ console.timeEnd(`[build] page:${pageLabel} dom-plugin:${plugin.name}`);
402
733
  }
403
734
  }
735
+ console.time(`[build] page:${pageLabel} renderToString`);
404
736
  let html = server.renderToString(el);
737
+ console.timeEnd(`[build] page:${pageLabel} renderToString`);
405
738
  for (const plugin of config.plugins || []) {
406
739
  if (plugin.phase === "page-html" && (plugin.mode === mode || plugin.mode === "both")) {
740
+ console.time(`[build] page:${pageLabel} html-plugin:${plugin.name}`);
407
741
  if (debug) {
408
742
  console.log(`Running page-html plugin: ${plugin.name}`);
409
743
  }
@@ -413,6 +747,7 @@ const build = async ({
413
747
  projectDir,
414
748
  config
415
749
  );
750
+ console.timeEnd(`[build] page:${pageLabel} html-plugin:${plugin.name}`);
416
751
  }
417
752
  }
418
753
  const finalOutputFile = node_path.join(
@@ -424,16 +759,27 @@ const build = async ({
424
759
  if (!node_fs.existsSync(finalOutputDir)) {
425
760
  node_fs.mkdirSync(finalOutputDir, { recursive: true });
426
761
  }
762
+ console.time(`[build] page:${pageLabel} writeFile`);
427
763
  await promises.writeFile(finalOutputFile, html);
764
+ console.timeEnd(`[build] page:${pageLabel} writeFile`);
428
765
  }
766
+ console.timeEnd("[build] render-pages");
429
767
  }
768
+ if (isFullBuild || changeKind === "page") {
769
+ console.time("[build] build-endpoints");
770
+ await buildEndpoints(projectDir, config, debug);
771
+ console.timeEnd("[build] build-endpoints");
772
+ }
773
+ console.time("[build] copy-outputs");
430
774
  if (isFullBuild || changeKind === "component") {
431
775
  await promises.cp(tmpComponentsDir, outputComponentsDir, { recursive: true });
432
776
  }
433
777
  if (isFullBuild || changeKind === "asset") {
434
778
  await promises.cp(inputAssetsDir, outputAssetsDir, { recursive: true });
435
779
  }
780
+ console.timeEnd("[build] copy-outputs");
436
781
  if (isFullBuild || changeKind === "component") {
782
+ console.time("[build] post-plugins");
437
783
  for (const plugin of config.plugins || []) {
438
784
  if (plugin.phase === "post" && (plugin.mode === mode || plugin.mode === "both")) {
439
785
  if (debug) {
@@ -442,6 +788,7 @@ const build = async ({
442
788
  await plugin.fn(projectDir, config);
443
789
  }
444
790
  }
791
+ console.timeEnd("[build] post-plugins");
445
792
  }
446
793
  if (mode === "build" && !debug) {
447
794
  node_fs.rmSync(config.tmp, { recursive: true });
@@ -501,6 +848,15 @@ const serve = async ({
501
848
  message: `Port ${port} is already in use. Please stop the process using this port or choose a different port.`
502
849
  };
503
850
  }
851
+ console.time("[serve] register-endpoints");
852
+ await registerEndpoints(app, projectDir, config, debug);
853
+ console.timeEnd("[serve] register-endpoints");
854
+ app.use((_req, res, next) => {
855
+ res.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
856
+ res.set("Pragma", "no-cache");
857
+ res.set("Expires", "0");
858
+ next();
859
+ });
504
860
  app.use(express.static(outputDir));
505
861
  const server = app.listen(port, (listenedPort) => {
506
862
  console.log(
@@ -523,7 +879,9 @@ const serve = async ({
523
879
  }
524
880
  isBuilding = true;
525
881
  try {
882
+ console.time("[serve] rebuild");
526
883
  await build({ projectDir, debug, mode: "serve", changedFile: filePath });
884
+ console.timeEnd("[serve] rebuild");
527
885
  liveReloadServer.clients.forEach((client) => {
528
886
  if (client.readyState === 1) {
529
887
  client.send(
@@ -576,9 +934,16 @@ const serve = async ({
576
934
  return { code: "OK", message: "Server is running" };
577
935
  };
578
936
 
937
+ exports.HTTP_METHODS = HTTP_METHODS;
579
938
  exports.build = build;
939
+ exports.buildEndpoints = buildEndpoints;
940
+ exports.compileEndpoints = compileEndpoints;
580
941
  exports.configDefaults = configDefaults;
942
+ exports.discoverEndpointSourceFiles = discoverEndpointSourceFiles;
943
+ exports.endpointFileToRoute = endpointFileToRoute;
581
944
  exports.readConfig = readConfig;
945
+ exports.registerEndpoints = registerEndpoints;
582
946
  exports.rehypePlugins = rehypePlugins;
583
947
  exports.remarkPlugins = remarkPlugins;
948
+ exports.resolveEndpoints = resolveEndpoints;
584
949
  exports.serve = serve;