Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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: "^/$"},
Expand Down Expand Up @@ -152,6 +145,7 @@ export default {
tableOfContents: {},
} satisfies Preset.ThemeConfig,
plugins: [
"./plugins/homepage-first-load-plugin.ts",
[
"./plugins/custom-blog-plugin.ts",
{
Expand Down
147 changes: 147 additions & 0 deletions plugins/homepage-first-load-plugin.ts
Original file line number Diff line number Diff line change
@@ -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<string>()

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 = `<script>
(()=>{let loaded=false;const css=${JSON.stringify(fullCssHref)};const scripts=${JSON.stringify(scriptHrefs)};
function load(){if(loaded)return Promise.resolve();loaded=true;const cssLink=document.createElement("link");cssLink.rel="stylesheet";cssLink.href=css;document.head.appendChild(cssLink);return scripts.reduce((chain,src)=>chain.then(()=>new Promise((resolve)=>{const script=document.createElement("script");script.src=src;script.defer=true;script.onload=script.onerror=resolve;document.body.appendChild(script);})),Promise.resolve());}
function loadDeferredImages(){document.querySelectorAll("img[data-src]").forEach((img)=>{img.setAttribute("src",img.getAttribute("data-src"));img.removeAttribute("data-src");});}
function loadAll(){loadDeferredImages();return load();}
["pointerdown","keydown","touchstart","wheel"].forEach((eventName)=>window.addEventListener(eventName,loadAll,{once:true,passive:true}));
})();</script>`

return html
.replace(/<script\b[^>]*id="chatbotscript"[^>]*><\/script>/, "")
.replace(
/<link data-rh="true" rel="preconnect" href="https:\/\/X27WDVHRQ3-dsn\.algolia\.net" crossorigin="anonymous">/i,
"",
)
.replace(/<link\b[^>]*rel="(?:preload|modulepreload)"[^>]*as="script"[^>]*>/g, "")
.replace(
new RegExp(`<link rel="stylesheet" href="${fullCssHref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}">`),
`<style>${criticalCss}</style>`,
)
.replace(/<script src="\/assets\/js\/(?:runtime~main|main)\.[^"]+\.js" defer="defer"><\/script>/g, "")
.replace("</body>", `${loader}</body>`)
}

export default function homepageFirstLoadPlugin(_context: LoadContext): Plugin {
return {
name: "homepage-first-load-plugin",
async postBuild({outDir}) {
const indexPath = path.join(outDir, "index.html")
if (!fs.existsSync(indexPath)) return

let html = fs.readFileSync(indexPath, "utf8")
const stylesheetMatch = html.match(/<link rel="stylesheet" href="([^"]*\/assets\/css\/styles\.[^"]+\.css)">/)
if (!stylesheetMatch) return

const fullCssHref = stylesheetMatch[1]
const fullCssPath = path.join(outDir, fullCssHref.replace(/^\//, ""))
const fullCss = fs.readFileSync(fullCssPath, "utf8")
const criticalHtml = html.split("</main>")[0] || html
const criticalCss = `${filterCss(fullCss, getClassNeedles(criticalHtml))
.replace(/\.customer-container\{[^}]*bg-map[^}]*\}/g, "")
.replace(/background-image:url\([^)]*video-thumbnail[^)]*\);?/g, "")}\n${EXTRA_CRITICAL_CSS}`

const cssOutputPath = path.join(outDir, HOME_CRITICAL_CSS.replace(/^\//, ""))
fs.mkdirSync(path.dirname(cssOutputPath), {recursive: true})
fs.writeFileSync(cssOutputPath, criticalCss)

const scriptHrefs = [
...html.matchAll(/<script src="(\/assets\/js\/(?:runtime~main|main)\.[^"]+\.js)" defer="defer"><\/script>/g),
].map((match) => match[1])

html = removeInitialHomepageAssets(html, fullCssHref, scriptHrefs, criticalCss)
fs.writeFileSync(indexPath, html)
},
}
}
23 changes: 15 additions & 8 deletions src/components/home/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ 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 {Theme} from "@site/src/constants"
import {pageLinks} from "@site/src/constants/routes"
import Link from "@docusaurus/Link"
import Section from "../shared/Section"

const Banner = (): JSX.Element => {
Expand All @@ -26,11 +24,11 @@ const Banner = (): JSX.Element => {
</p>
<div className="hidden sm:flex justify-center mt-SPACE_06 sm:mt-SPACE_10 space-x-SPACE_04 sm:space-x-SPACE_06">
<LinkButton
title="Learn More"
title="Explore Tailcall"
href={pageLinks.introduction}
theme={Theme.Dark}
width="small"
onClick={() => analyticsHandler("Home Page", "Click", "Playground")}
onClick={() => analyticsHandler("Home Page", "Click", "Explore Tailcall")}
/>
<LinkButton
title="Get Started"
Expand All @@ -43,10 +41,10 @@ const Banner = (): JSX.Element => {

<div className="sm:hidden flex justify-between md:justify-center mt-SPACE_06 sm:mt-SPACE_10 space-x-SPACE_04 sm:space-x-SPACE_06">
<LinkButton
title="Learn More"
title="Explore Tailcall"
href={pageLinks.introduction}
theme={Theme.Dark}
onClick={() => analyticsHandler("Home Page", "Click", "Playground")}
onClick={() => analyticsHandler("Home Page", "Click", "Explore Tailcall")}
width="full"
/>
<LinkButton
Expand All @@ -59,7 +57,16 @@ const Banner = (): JSX.Element => {
</div>
</div>
</Section>
<HeroImage className="object-contain h-full sm:h-full w-full mt-8 max-w-7xl" />
<picture className="hidden sm:block mt-8 max-w-7xl">
<source media="(min-width: 640px)" srcSet="/images/home/hero.svg" />
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1' height='1'/%3E"
alt=""
aria-hidden="true"
loading="lazy"
className="object-contain h-full sm:h-full w-full"
/>
</picture>
</main>
)
}
Expand Down
12 changes: 11 additions & 1 deletion src/components/home/BenefitsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {ArrowRight} from "lucide-react"
import Link from "@docusaurus/Link"
import clsx from "clsx"

const deferredPixel = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="

const BenefitsCard = (): JSX.Element => {
return (
<div className="mt-16 mb-10 lg:my-24">
Expand All @@ -20,7 +22,15 @@ const BenefitsCard = (): JSX.Element => {
href={item.redirection_url}
>
<div className="flex-shrink-0 mb-4 md:mb-0 md:mr-6">
<img src={item.image} alt="Image Describing Why Tailcall" className="w-16 h-16 object-contain" />
<img
src={deferredPixel}
data-src={item.image}
alt="Image Describing Why Tailcall"
className="w-16 h-16 object-contain"
width={64}
height={64}
loading="lazy"
/>
</div>
<div className="flex-grow">
<p className="text-title-small sm:text-title-large text-white mb-2 flex items-center justify-between">
Expand Down
21 changes: 19 additions & 2 deletions src/components/home/ChooseTailcall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {chooseTailcall, tailcallFeatures, Theme} from "@site/src/constants"
import LinkButton from "../shared/LinkButton"
import Link from "@docusaurus/Link"

const deferredPixel = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="

const ChooseTailcall = (): JSX.Element => {
return (
<div className="flex flex-col items-center justify-center">
Expand All @@ -16,7 +18,15 @@ const ChooseTailcall = (): JSX.Element => {
key={item.id}
>
<div className="h-16 w-16 sm:w-full sm:h-full">
<img src={item.image} alt="Image Describing Why Tailcall" className="max-w-[72px] sm:max-w-[110px]" />
<img
src={deferredPixel}
data-src={item.image}
alt="Image Describing Why Tailcall"
className="max-w-[72px] sm:max-w-[110px]"
width={72}
height={72}
loading="lazy"
/>
</div>

<div>
Expand All @@ -33,7 +43,14 @@ const ChooseTailcall = (): JSX.Element => {
className="flex w-fit p-6 border-2 border-solid border-tailCall-border-dark-300 rounded-xl md:items-center md:justify-center cursor-pointer hover:no-underline text-tailCall-light-300 hover:text-tailCall-light-300 hover:border-[#FDEA2E] benefits-drop-shadow"
key={item.id}
>
<img src={item.image} alt={`${item.title} Image`} height={24} width={24} />
<img
src={deferredPixel}
data-src={item.image}
alt={`${item.title} Image`}
height={24}
width={24}
loading="lazy"
/>
<span className="text-content-small lg:text-title-tiny ml-2">{item.title}</span>
</Link>
))}
Expand Down
10 changes: 7 additions & 3 deletions src/components/home/Configuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ const Configuration = (): JSX.Element => {
Setup the Tailcall instantly via npm and unlock the power of high-performance API orchestration.
</p>
<div>
<h5>More</h5>
<h3 className="text-title-small">More</h3>
<p className="text-content-small sm:text-content-medium mb-SPACE_11">
To dive deeper into Tailcall checkout our <Link href="/docs">docs</Link> 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{" "}
<Link href="/docs" className="text-tailCall-dark-500 underline">
Tailcall docs
</Link>{" "}
for detailed tutorials. Ideal for devs at any level, it's packed with advanced tips, powerful operators and
best practices.
</p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/home/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Graph = (): JSX.Element => {
<Section className="bg-tailCall-dark-600 h-full w-full text-tailCall-light-100 lg:pt-48 lg:pb-36">
<div className="flex items-center justify-between lg:mb-12">
<Heading
as="h5"
as="h2"
className="text-title-large sm:text-display-tiny lg:text-display-medium sm:max-w-sm lg:max-w-xl"
>
Platform made for performance.
Expand Down
33 changes: 22 additions & 11 deletions src/components/home/IntroductionVideo/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(null)
const [isVideoLoaded, setIsVideoLoaded] = useState(false)
const {getCookieConsent} = useCookieConsent()
const cookieConsent = getCookieConsent()

Expand All @@ -13,16 +13,27 @@ const IntroductionVideo: React.FC = () => {
}

return (
<div className="video-wrapper" ref={videoRef}>
<div className="video-wrapper">
<div className="video-container">
<iframe
src={`https://player.vimeo.com/video/${videoId}?autoplay=0&badge=0&autopause=0&player_id=0&app_id=58479${handleVimeoAnalytics()}`}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="absolute top-0 left-0 w-full h-full"
title="Tailcall Introduction Video"
loading="lazy"
/>
{isVideoLoaded ? (
<iframe
src={`https://player.vimeo.com/video/${videoId}?autoplay=1&badge=0&autopause=0&player_id=0&app_id=58479${handleVimeoAnalytics()}`}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="absolute top-0 left-0 w-full h-full"
title="Tailcall Introduction Video"
loading="lazy"
/>
) : (
<button
type="button"
className="video-play-button"
aria-label="Play Tailcall introduction video"
onClick={() => setIsVideoLoaded(true)}
>
<span className="video-play-icon" aria-hidden="true" />
</button>
)}
</div>
</div>
)
Expand Down
Loading
Loading