finishSite

tcmts.site.finishSite · #6aoa5rrr · term · runs on the server

uses node:fs

Make a built static site (out, its stylesheet style.css) quick to go through: the stylesheet is named by its content (style.<digest>.css, which tcm serve --site says to keep for good) in every page's link to it, and each page gets SPECULATION_RULES. Returns the stylesheet's new name.

import { SPECULATION_RULES } from "#codebase/tcmts/site";
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";

export function finishSite(out: string): string {
  const css = fs.readFileSync(path.join(out, "style.css"));
  const style = `style.${createHash("sha256").update(css).digest("hex").slice(0, 12)}.css`;
  fs.renameSync(path.join(out, "style.css"), path.join(out, style));
  const walk = (dir: string) => {
    for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
      const p = path.join(dir, e.name);
      if (e.isDirectory()) walk(p);
      else if (e.name.endsWith(".html")) {
        const html = fs.readFileSync(p, "utf8");
        // (a page of the docs app follows its own links itself, having read what they show: prerendering one would be
        // for nothing; the others, its code browser's, are prerendered)
        const rules = /var __TCM_STATIC__/.test(html)
          ? SPECULATION_RULES.replace(
              '{"href_matches":"/*"}',
              '{"selector_matches":"[rel~=external]"},{"href_matches":"/*"}',
            )
          : SPECULATION_RULES;
        fs.writeFileSync(
          p,
          html
            .replace(/(<link rel="stylesheet" href="(?:\.\.\/)*)style\.css"/, `$1${style}"`)
            .replace("</head>", () => `${rules}\n</head>`),
        );
      }
    }
  };
  walk(out);
  return style;
}

uses

used by