File size: 6,247 Bytes
15d6beb 54a7dba 9ec7404 15d6beb 92cf18f 15d6beb 9ec7404 15d6beb 9ec7404 92cf18f 15d6beb 9ec7404 15d6beb 9ec7404 15d6beb 9ec7404 54a7dba 2d39154 54a7dba 9ec7404 10c5bcc 9ec7404 10c5bcc 54a7dba 10c5bcc 54a7dba 10c5bcc 54a7dba 10c5bcc 54a7dba 10c5bcc 9ec7404 10c5bcc 54a7dba 15d6beb 444a402 15d6beb 54a7dba 444a402 54a7dba 9ec7404 54a7dba 7c1922e 54a7dba 15d6beb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import type { Metadata, Viewport } from "next";
import { readFileSync } from "node:fs";
import path from "node:path";
import { Bricolage_Grotesque, Spline_Sans_Mono } from "next/font/google";
import "./globals.css";
import { readEngineInfo } from "@/lib/engine-info";
const engine = readEngineInfo();
// Fonts are fetched once at build time and served from this site, so there is no
// request to Google at runtime. Bricolage Grotesque does everything a person
// reads, Spline Sans Mono is only ever code and file names.
const display = Bricolage_Grotesque({
subsets: ["latin"],
variable: "--font-display",
axes: ["opsz", "wdth"],
display: "swap",
});
const mono = Spline_Sans_Mono({
subsets: ["latin"],
variable: "--font-code",
display: "swap",
});
export const metadata: Metadata = {
metadataBase: new URL("https://dssl-sable.vercel.app"),
title: { default: "DSSL: a style language that tells you what you got wrong", template: "%s | DSSL" },
description: `DSSL is a styling language for the web: design tokens, its own cascade, loud errors and zero specificity wars. The engine is ${engine.gzKb} kB gzipped, with no build step.`,
keywords: ["DSSL", "CSS alternative", "style language", "design tokens", "web"],
icons: { icon: "/favicon.svg" },
openGraph: { type: "website", siteName: "DSSL", title: "DSSL: a style language that tells you what you got wrong" },
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
};
// The ONLY non-DSSL styling in this app: a pre-paint shim so the very first
// frame doesn't flash white before the DSSL engine boots (dark-mode respect).
const prePaint = `
html { background: #f2f0f7; }
@media (prefers-color-scheme: dark) { html { background: #130f1b; } }
`;
// v0.3.2, performance: the stylesheets ride INSIDE the HTML. The engine's
// auto-boot picks these <style type="text/dssl"> tags up the moment the module
// executes (right after parse, before first paint), so there is no fetch
// waterfall and no wait for React hydration. Server-side read also means a
// browser refresh picks up .dssl edits instantly in dev.
const DSSL_SHEETS = ["tokens", "site", "home", "docs", "playground", "changelog", "bench"] as const;
function dsslInlineStyles(): { name: string; src: string }[] {
return DSSL_SHEETS.map((name) => {
let src = "";
try {
src = readFileSync(path.join(process.cwd(), "public", "styles", `${name}.dssl`), "utf8");
} catch {
// missing sheet: boot simply won't apply it; errors stay loud elsewhere
}
// defensive: a "</style" sequence in source would break out of the tag
src = src.replace(/<\/style/gi, "<\\/style");
return { name, src };
});
}
// Dev-only interop shim: the engine styles the SSR DOM BEFORE React hydrates
// (that is the whole point, no FOUC). React 19 then logs one console.error
// about extra style attributes and, helpfully, does not patch them. Next's
// dev overlay ALSO patches console.error (one layer around everything) and
// counts every error it sees into the "Issues" badge, so a plain filter
// installed here would sit INSIDE Next's collector and the badge would still
// show "1". This shim instead installs an assignment trap: the moment Next
// assigns its own wrapper, we capture it and re-publish OUR filter as the
// outermost layer. DSSL's own hydration diff is swallowed + explained; every
// other console.error still reaches Next's collector untouched.
const hydrationShim = `
(function () {
var HYD = 'A tree hydrated but some attributes';
var MARK = '--dssl';
var inner = console.error; // native, until Next assigns its wrapper
var trapped = false;
function outer() {
var s = '';
for (var i = 0; i < arguments.length; i++) {
var a = arguments[i];
s += (i ? ' ' : '') + (typeof a === 'string' ? a : (a && a.message ? a.message : ''));
}
if (s.indexOf(HYD) !== -1 && s.indexOf(MARK) !== -1) {
console.info('%cDSSL%c styled this page before React hydrated. The "hydrated but some attributes didn\\u2019t match" notice is expected interop: the engine owns those style attributes, and React explicitly leaves them alone.',
'background:#b3372e;color:#fff;padding:1px 6px;border-radius:3px;font-weight:700', '');
return;
}
return inner.apply(console, arguments);
}
try {
var desc = Object.getOwnPropertyDescriptor(console, 'error');
if (!desc || !desc.configurable) throw new Error('not configurable');
Object.defineProperty(console, 'error', {
configurable: true,
get: function () { return trapped ? outer : inner; },
set: function (fn) { inner = fn; trapped = true; }
});
} catch (e) {
// fallback: plain filter, console noise is still explained away,
// though the dev-overlay badge may still count the notice.
console.error = outer;
}
})();
`;
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${display.variable} ${mono.variable}`} suppressHydrationWarning>
<head>
<style dangerouslySetInnerHTML={{ __html: prePaint }} />
{process.env.NODE_ENV !== "production" && (
<script dangerouslySetInnerHTML={{ __html: hydrationShim }} />
)}
{/* the same sheets, compiled to plain CSS by `dssl build` (scripts/build-site-css.mjs): styled on first paint, with
JavaScript off, and in the server-rendered HTML. The engine then adds what a build cannot keep. The reset is in it. */}
<link rel="stylesheet" href="/site.css" />
{dsslInlineStyles().map(({ name, src }) => (
<style
key={name}
type="text/dssl"
data-dssl-name={`${name}.dssl`}
dangerouslySetInnerHTML={{ __html: src }}
/>
))}
{/* engine starts downloading with the document, module defer rules
mean it executes after parse with the full SSR DOM present */}
{/* eslint-disable-next-line @next/next/no-sync-scripts -- type="module" is deferred by spec; the rule cannot see that */}
<script type="module" src="/dssl/boot.js" />
</head>
<body>{children}</body>
</html>
);
}
|