diff --git a/docusaurus.config.ts b/docusaurus.config.ts index da14145d8..eeba68114 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -13,15 +13,6 @@ export default { trailingSlash: true, tagline: "GraphQL platform engineered for scale", headTags: [ - { - tagName: "script", - attributes: { - id: "chatbotscript", - "data-accountid": "CZPG9aVdtk59Tjz4SMTu8w==", - "data-websiteid": "75VGI0NlBqessD4BQn2pFg==", - src: "https://app.robofy.ai/bot/js/common.js?v=" + new Date().getTime(), - }, - }, { tagName: "script", attributes: { @@ -108,6 +99,8 @@ export default { logo: { alt: "My Site Logo", src: "icons/companies/tailcall.svg", + width: 103, + height: 36, }, items: [ {to: "/", label: "Home", position: "left", activeBaseRegex: "^/$"}, @@ -152,6 +145,7 @@ export default { tableOfContents: {}, } satisfies Preset.ThemeConfig, plugins: [ + "./plugins/homepage-first-load-plugin.ts", [ "./plugins/custom-blog-plugin.ts", { diff --git a/plugins/homepage-first-load-plugin.ts b/plugins/homepage-first-load-plugin.ts new file mode 100644 index 000000000..a78fff04b --- /dev/null +++ b/plugins/homepage-first-load-plugin.ts @@ -0,0 +1,147 @@ +import fs from "fs" +import path from "path" +import type {LoadContext, Plugin} from "@docusaurus/types" + +const HOME_CRITICAL_CSS = "/assets/css/home-critical.css" +const EXTRA_CRITICAL_CSS = + ':root{--ifm-navbar-height:4.5rem;--ifm-navbar-logo-height:2.25rem;--ifm-font-family-base:"Space Grotesk",sans-serif;--ifm-font-color-base:#121315;--ifm-background-color:#fff;--ifm-link-color:#3578e5;--ifm-global-radius:.4rem}*,:after,:before{box-sizing:border-box}#__docusaurus-base-url-issue-banner-container,.themedComponent_mlkZ{display:none}[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.relative{position:relative}.text-black{color:#000}.text-tailCall-light-300{color:#e7e7e7}.text-tailCall-light-100{color:#fff}.bg-tailCall-dark-600{background-color:#121212}.bg-tailCall-yellow{background-color:#fdea2e}' + +const escapeClassName = (className: string): string => { + return className.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1") +} + +const getClassNeedles = (html: string): string[] => { + const classNames = new Set() + + for (const match of html.matchAll(/class="([^"]+)"/g)) { + match[1] + .split(/\s+/) + .map((className) => className.trim()) + .filter(Boolean) + .forEach((className) => classNames.add(`.${escapeClassName(className)}`)) + } + + return [...classNames] +} + +const getBlocks = (css: string): Array<{prelude: string; body: string; block: string}> => { + const blocks: Array<{prelude: string; body: string; block: string}> = [] + let cursor = 0 + + while (cursor < css.length) { + const open = css.indexOf("{", cursor) + if (open === -1) break + + let depth = 1 + let close = open + 1 + while (close < css.length && depth > 0) { + if (css[close] === "{") depth += 1 + if (css[close] === "}") depth -= 1 + close += 1 + } + + const preludeStart = css.lastIndexOf("}", open - 1) + 1 + const prelude = css.slice(Math.max(cursor, preludeStart), open).trim() + const body = css.slice(open + 1, close - 1) + const block = css.slice(Math.max(cursor, preludeStart), close).trim() + + if (prelude && block) blocks.push({prelude, body, block}) + cursor = close + } + + return blocks +} + +const isGlobalSelector = (prelude: string): boolean => { + return /(^|,)\s*(:root|html|body|\*|::before|::after|a|button|code|footer|h[1-6]|img|li|main|nav|ol|p|picture|pre|section|svg|ul)(\W|$)/.test( + prelude, + ) +} + +const keepRule = (prelude: string, classNeedles: string[]): boolean => { + if (prelude.startsWith("@font-face")) return false + if (prelude.startsWith("@keyframes")) return true + if (isGlobalSelector(prelude)) return true + + return classNeedles.some((needle) => prelude.includes(needle)) +} + +const filterCss = (css: string, classNeedles: string[]): string => { + return getBlocks(css) + .map(({prelude, body, block}) => { + if (prelude.startsWith("@media") || prelude.startsWith("@supports") || prelude.startsWith("@container")) { + const nested = filterCss(body, classNeedles) + return nested ? `${prelude}{${nested}}` : "" + } + + if (prelude.includes("DocSearch") || prelude.includes("graphiql")) { + return "" + } + + return keepRule(prelude, classNeedles) ? block : "" + }) + .filter(Boolean) + .join("\n") +} + +const removeInitialHomepageAssets = ( + html: string, + fullCssHref: string, + scriptHrefs: string[], + criticalCss: string, +): string => { + const loader = `` + + return html + .replace(/]*id="chatbotscript"[^>]*><\/script>/, "") + .replace( + //i, + "", + ) + .replace(/]*rel="(?:preload|modulepreload)"[^>]*as="script"[^>]*>/g, "") + .replace( + new RegExp(``), + ``, + ) + .replace(/