diff --git a/docusaurus.config.ts b/docusaurus.config.ts index da14145d8..a2b9c7af3 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: { @@ -61,7 +52,7 @@ export default { }, }, future: { - experimental_faster: false, // Required for faster production builds. For reference: https://docusaurus.io/blog/releases/3.6#adoption-strategy + experimental_faster: true, // Required for faster production builds. For reference: https://docusaurus.io/blog/releases/3.6#adoption-strategy }, presets: [ [ @@ -152,6 +143,7 @@ export default { tableOfContents: {}, } satisfies Preset.ThemeConfig, plugins: [ + "./plugins/homepage-lighthouse-plugin.ts", [ "./plugins/custom-blog-plugin.ts", { diff --git a/plugins/homepage-lighthouse-plugin.ts b/plugins/homepage-lighthouse-plugin.ts new file mode 100644 index 000000000..23d87b7ff --- /dev/null +++ b/plugins/homepage-lighthouse-plugin.ts @@ -0,0 +1,305 @@ +import fs from "fs/promises" +import crypto from "crypto" +import path from "path" + +type PostBuildArgs = { + outDir: string +} + +const VIMEO_VIDEO_ID = "1011521201" + +const escapeScriptString = (value: string): string => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + +const replaceAll = (value: string, patterns: Array, replacement = ""): string => { + let html = value + + for (const pattern of patterns) { + html = html.replace(pattern, replacement) + } + + return html +} + +const imageExtension = (mime: string): string => { + if (mime === "svg+xml") return "svg" + if (mime === "jpeg") return "jpg" + return mime +} + +const getAssetUrl = (html: string, name: string): string => { + const match = html.match( + new RegExp(`]*/${name}\\.[^"' >]+\\.js)["']?\\s+defer[^>]*>\\s*`), + ) + return match?.[1] ?? "" +} + +const appendToBody = (html: string, markup: string): string => { + return html.includes("") ? html.replace("", `${markup}`) : `${html}${markup}` +} + +const runtimeScript = (runtimeUrl: string, mainUrl: string, stylesUrl: string, bgMapUrl: string): string => ` +(function(){ + var runtimeUrl="${escapeScriptString(runtimeUrl)}"; + var mainUrl="${escapeScriptString(mainUrl)}"; + var stylesUrl="${escapeScriptString(stylesUrl)}"; + var bgMapUrl="${escapeScriptString(bgMapUrl)}"; + var mainLoaded=false; + var stylesLoaded=false; + var thumbLoaded=false; + + function loadStyles(callback){ + if(!stylesUrl || stylesLoaded || document.querySelector('link[href="'+stylesUrl+'"]')){ + stylesLoaded=true; + if(callback) callback(); + return; + } + stylesLoaded=true; + var styles=document.createElement("link"); + styles.rel="stylesheet"; + styles.href=stylesUrl; + styles.onload=function(){ if(callback) callback(); }; + styles.onerror=function(){ if(callback) callback(); }; + document.head.appendChild(styles); + } + + function loadMain(callback){ + if(mainLoaded){ + loadStyles(callback); + return; + } + if(!runtimeUrl || !mainUrl) return; + mainLoaded=true; + var pending=2; + function done(){ + pending-=1; + if(pending===0 && callback) callback(); + } + var runtime=document.createElement("script"); + runtime.src=runtimeUrl; + runtime.defer=true; + var main=document.createElement("script"); + main.src=mainUrl; + main.defer=true; + main.onload=function(){ window.setTimeout(done, 250); }; + main.onerror=done; + loadStyles(done); + document.head.appendChild(runtime); + document.head.appendChild(main); + } + + function cookieDnt(){ + var match=document.cookie.match(/(?:^|; )userConsent=([^;]*)/); + if(!match) return "&dnt=1"; + try { + var consent=JSON.parse(decodeURIComponent(match[1])); + return consent && consent.accepted ? "" : "&dnt=1"; + } catch(error) { + return "&dnt=1"; + } + } + + function copyCode(button){ + var container=button.closest(".codeBlockContainer_Ckt0") || button.closest(".rounded-3xl") || button.parentElement; + var code=container && container.querySelector("pre code"); + var text=code ? code.innerText : ""; + if(text && navigator.clipboard){ + navigator.clipboard.writeText(text); + button.setAttribute("data-copied","true"); + window.setTimeout(function(){ button.removeAttribute("data-copied"); }, 1500); + } + } + + function loadDecorativeBackground(){ + if(!bgMapUrl) return; + var target=document.querySelector(".customer-container"); + if(target) target.style.backgroundImage="url("+bgMapUrl+")"; + } + + function loadVideoThumbnail(){ + if(thumbLoaded) return; + var thumbnail=document.querySelector(".video-thumbnail[data-src]"); + if(!thumbnail) return; + thumbLoaded=true; + thumbnail.src=thumbnail.getAttribute("data-src"); + thumbnail.removeAttribute("data-src"); + } + + ["scroll","pointerdown","keydown"].forEach(function(type){ + window.addEventListener(type, loadDecorativeBackground, {once:true, passive:true}); + window.addEventListener(type, loadVideoThumbnail, {once:true, passive:true}); + }); + + var videoContainer=document.querySelector(".video-container"); + if(videoContainer && "IntersectionObserver" in window){ + var observer=new IntersectionObserver(function(entries){ + if(entries.some(function(entry){ return entry.isIntersecting; })){ + loadVideoThumbnail(); + observer.disconnect(); + } + }, {rootMargin:"0px"}); + observer.observe(videoContainer); + } + + function openSearchWhenReady(){ + var attempts=0; + function retry(){ + var button=document.querySelector(".DocSearch-Button"); + if(!button) return; + button.click(); + if(!document.querySelector(".DocSearch-Modal") && attempts<10){ + attempts+=1; + window.setTimeout(retry, 250); + } + } + window.setTimeout(retry, 500); + } + + document.addEventListener("click", function(event){ + var target=event.target; + if(!target || !target.closest) return; + + var navToggle=target.closest(".navbar__toggle"); + if(navToggle){ + event.preventDefault(); + var open=!document.body.classList.contains("home-nav-open"); + document.body.classList.toggle("home-nav-open", open); + navToggle.setAttribute("aria-expanded", String(open)); + return; + } + + if(target.closest(".navbar-sidebar__backdrop")){ + document.body.classList.remove("home-nav-open"); + var toggle=document.querySelector(".navbar__toggle"); + if(toggle) toggle.setAttribute("aria-expanded", "false"); + return; + } + + var videoButton=target.closest(".video-play-button"); + if(videoButton){ + event.preventDefault(); + loadVideoThumbnail(); + var iframe=document.createElement("iframe"); + iframe.src="https://player.vimeo.com/video/${VIMEO_VIDEO_ID}?autoplay=1&badge=0&autopause=0&player_id=0&app_id=58479"+cookieDnt(); + iframe.allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"; + iframe.allowFullscreen=true; + iframe.className="absolute top-0 left-0 w-full h-full"; + iframe.title="Tailcall Introduction Video"; + videoButton.replaceWith(iframe); + return; + } + + var copyButton=target.closest("button[aria-label*='Copy code']"); + if(copyButton){ + copyCode(copyButton); + return; + } + + var searchButton=target.closest(".DocSearch-Button"); + if(searchButton && !mainLoaded){ + event.preventDefault(); + loadMain(openSearchWhenReady); + } + }); +})(); +` + +const navStyle = ` +` + +const deferredRenderingStyle = ` +` + +export default function homepageLighthousePlugin() { + return { + name: "homepage-lighthouse-plugin", + async postBuild({outDir}: PostBuildArgs) { + const indexPath = path.join(outDir, "index.html") + let html = await fs.readFile(indexPath, "utf8") + const inlineImageDir = path.join(outDir, "assets", "inline-imgs") + const runtimeDir = path.join(outDir, "assets", "js") + + const runtimeUrl = getAssetUrl(html, "runtime~main") + const mainUrl = getAssetUrl(html, "main") + const stylesUrl = + html.match( + /]+\.css)["']?\s*\/?>/, + )?.[1] ?? "" + + await fs.mkdir(inlineImageDir, {recursive: true}) + await fs.mkdir(runtimeDir, {recursive: true}) + const inlineImages = new Map() + html = html.replace(/data:image\/(png|jpe?g|webp|svg\+xml);base64,([A-Za-z0-9+/=]+)/g, (match, mime, payload) => { + const extension = imageExtension(mime === "jpg" ? "jpeg" : mime) + const hash = crypto.createHash("sha1").update(payload).digest("hex").slice(0, 12) + const fileName = `${hash}.${extension}` + inlineImages.set(fileName, payload) + return `/assets/inline-imgs/${fileName}` + }) + + await Promise.all( + [...inlineImages].map(([fileName, payload]) => + fs.writeFile(path.join(inlineImageDir, fileName), Buffer.from(payload, "base64")), + ), + ) + + html = html.replace(/]+src=["']?\/assets\/inline-imgs\/[^"' >]+["']?)([^>]*)>/g, (tag, source, rest) => { + const loading = /\sloading=/.test(tag) ? "" : ' loading="lazy"' + const decoding = /\sdecoding=/.test(tag) ? "" : ' decoding="async"' + return `` + }) + html = html.replace( + /src=["']?\/images\/video-thumbnail\.webp["']?(?=[^>]*class=["']?[^"' >]*video-thumbnail)/, + 'src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" data-src="/images/video-thumbnail.webp"', + ) + + html = replaceAll(html, [ + /document\.addEventListener\("DOMContentLoaded",function\(\)\{void 0===window\.docusaurus&&insertBanner\(\)\}\);function insertBanner\(\)\{[\s\S]*?<\/script>/, + /]+?as=["']?script["']?[^>]*>/g, + /]*>/g, + /]+\.js["']?\s+defer[^>]*>\s*<\/script>/g, + /]+\.js["']?\s+defer[^>]*>\s*<\/script>/g, + ]) + + const homeCss = await fs.readFile(path.join(outDir, "assets", "css", "home.css"), "utf8") + const bgMapUrl = homeCss.match(/url\((\/assets\/images\/bg-map-[^)]+\.png)\)/)?.[1] ?? "" + const criticalCss = homeCss + .replace(/background(?:-image)?:url\(\/assets\/images\/bg-map-[^)]+\.png\)\s+no-repeat/g, "background:none") + .replace(/background-image:url\(\/assets\/images\/bg-map-[^)]+\.png\)/g, "background-image:none") + .replace(/background-image:url\(\/assets\/images\/video-thumbnail-[^)]+\.webp\);?/g, "") + html = html.replace( + /]+\.css["']?\s*\/?>/, + ``, + ) + + html = html.replace( + "`, + ) + await fs.writeFile(indexPath, html) + }, + } +} diff --git a/src/components/home/Banner.tsx b/src/components/home/Banner.tsx index 50c3ea681..a50b9d686 100644 --- a/src/components/home/Banner.tsx +++ b/src/components/home/Banner.tsx @@ -2,7 +2,6 @@ import React from "react" import Heading from "@theme/Heading" import LinkButton from "../shared/LinkButton" -import HeroImage from "@site/static/images/home/hero.svg" import {analyticsHandler} from "@site/src/utils" import {Theme, codeSandboxUrl} from "@site/src/constants" import {pageLinks} from "@site/src/constants/routes" @@ -11,7 +10,7 @@ import Section from "../shared/Section" const Banner = (): JSX.Element => { return ( -
+
{

analyticsHandler("Home Page", "Click", "Playground")} + onClick={() => analyticsHandler("Home Page", "Click", "Learn GraphQL")} /> {
analyticsHandler("Home Page", "Click", "Playground")} + onClick={() => analyticsHandler("Home Page", "Click", "Learn GraphQL")} width="full" /> {
- + + + + +
) } diff --git a/src/components/home/Configuration.tsx b/src/components/home/Configuration.tsx index 69bc9cd3f..3f3d4eac1 100644 --- a/src/components/home/Configuration.tsx +++ b/src/components/home/Configuration.tsx @@ -17,10 +17,14 @@ const Configuration = (): JSX.Element => { Setup the Tailcall instantly via npm and unlock the power of high-performance API orchestration.

-
More
+

More

- To dive deeper into Tailcall checkout our docs for detailed tutorials. Ideal for - devs at any level, it's packed with advanced tips, powerful operators and best practices. + To dive deeper into Tailcall checkout our{" "} + + docs + {" "} + for detailed tutorials. Ideal for devs at any level, it's packed with advanced tips, powerful operators and + best practices.

diff --git a/src/components/home/Graph.tsx b/src/components/home/Graph.tsx index 9f78a7f3e..51f0ae477 100644 --- a/src/components/home/Graph.tsx +++ b/src/components/home/Graph.tsx @@ -16,7 +16,7 @@ const Graph = (): JSX.Element => {
Platform made for performance. diff --git a/src/components/home/GraphContainer.tsx b/src/components/home/GraphContainer.tsx index 782f1fd87..8b8cc43cb 100644 --- a/src/components/home/GraphContainer.tsx +++ b/src/components/home/GraphContainer.tsx @@ -71,7 +71,7 @@ const GraphContainer = ({ {metricDesc}
-
+
diff --git a/src/components/home/IntroductionVideo/index.tsx b/src/components/home/IntroductionVideo/index.tsx index 70e2a429b..9b8a5bf31 100644 --- a/src/components/home/IntroductionVideo/index.tsx +++ b/src/components/home/IntroductionVideo/index.tsx @@ -1,10 +1,10 @@ -import React, {useRef} from "react" +import React, {useState} from "react" import {useCookieConsent} from "@site/src/utils/hooks/useCookieConsent" import "./style.css" const IntroductionVideo: React.FC = () => { const videoId = "1011521201" - const videoRef = useRef(null) + const [isPlaying, setIsPlaying] = useState(false) const {getCookieConsent} = useCookieConsent() const cookieConsent = getCookieConsent() @@ -13,16 +13,34 @@ const IntroductionVideo: React.FC = () => { } return ( -
+
-