From 80737758fdc475b37fa032ce82f119eff7f246b7 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sat, 8 Jun 2024 21:25:56 +0800 Subject: [PATCH] add cal.com.tsx --- .github/workflows/benchmark.yml | 15 +- benches/transformer.rs | 15 +- files/cal.com.tsx | 30591 +++++++++++++++++++++++++ typescript.js => files/typescript.js | 0 4 files changed, 30607 insertions(+), 14 deletions(-) create mode 100644 files/cal.com.tsx rename typescript.js => files/typescript.js (100%) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 9d8c250..7b1131b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,20 +16,21 @@ on: - 'Cargo.lock' - 'rust-toolchain.toml' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.ref_name != 'main' }} + jobs: benchmark: name: Benchmark runs-on: ubuntu-latest steps: - - name: Checkout Branch - uses: actions/checkout@v4 + - uses: taiki-e/checkout-action@v1 - - name: Setup rust toolchain, cache and cargo-codspeed binary - uses: moonrepo/setup-rust@v1 + - uses: Boshen/setup-rust@main with: - channel: stable - cache-target: release - bins: cargo-codspeed + save-cache: ${{ github.ref_name == 'main' }} + tools: cargo-codspeed - name: Build Benchmark run: cargo codspeed build --features codspeed diff --git a/benches/transformer.rs b/benches/transformer.rs index d4c69ea..4722f0c 100644 --- a/benches/transformer.rs +++ b/benches/transformer.rs @@ -132,13 +132,14 @@ impl TheBencher for SwcBencher { } fn transformer_benchmark(c: &mut Criterion) { - let filename = "typescript.js"; - let source = std::fs::read_to_string(filename).unwrap(); - - let mut g = c.benchmark_group(filename); - OxcBencher::bench(&mut g, &source); - SwcBencher::bench(&mut g, &source); - g.finish(); + let filenames = ["typescript.js", "cal.com.tsx"]; + for filename in filenames { + let source = std::fs::read_to_string(Path::new("files").join(filename)).unwrap(); + let mut g = c.benchmark_group(filename); + OxcBencher::bench(&mut g, &source); + SwcBencher::bench(&mut g, &source); + g.finish(); + } } criterion_group!(transformer, transformer_benchmark); diff --git a/files/cal.com.tsx b/files/cal.com.tsx new file mode 100644 index 0000000..7112f68 --- /dev/null +++ b/files/cal.com.tsx @@ -0,0 +1,30591 @@ +// adds tooltip context to all stories +import { TooltipProvider } from "@radix-ui/react-tooltip"; +import type { Preview } from "@storybook/react"; +import React from "react"; +import { I18nextProvider } from "react-i18next"; + +import type { EmbedThemeConfig } from "@calcom/embed-core/src/types"; +// adds trpc context to all stories (esp. booker) +import { StorybookTrpcProvider } from "@calcom/ui"; + +import "../styles/globals.css"; +import "../styles/storybook-styles.css"; +import i18n from "./i18next"; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + + globals: { + locale: "en", + locales: { + en: "English", + fr: "Français", + }, + }, + + i18n, + + nextjs: { + appDirectory: true, + }, + }, + + decorators: [ + (Story) => ( + + + +
+ +
+
+
+
+ ), + ], +}; + +export default preview; + +declare global { + interface Window { + getEmbedNamespace: () => string | null; + getEmbedTheme: () => EmbedThemeConfig | null; + } +} + +window.getEmbedNamespace = () => { + const url = new URL(document.URL); + const namespace = url.searchParams.get("embed"); + return namespace; +}; + +window.getEmbedTheme = () => { + return "auto"; +}; +export const Title = ({ + title, + suffix, + subtitle, + offset, +}: { + title: string; + suffix?: string; + subtitle?: string; + offset?: boolean; +}) => { + return ( +
+

+ {title} + {suffix && {suffix}} +

+ {subtitle &&

{subtitle}

} +
+ ); +}; +export const Note = ({ children }: { children: React.ReactNode }) => ( +
+
{children}
+
+); +import { classNames } from "@calcom/lib"; + +interface ExampleProps { + children: React.ReactNode; + title: string; + isFullWidth?: boolean; +} +export const Example = ({ children, title, isFullWidth = false }: ExampleProps) => { + return ( +
+ {title} +
{children}
+
+ ); +}; + +interface ExamplesProps { + children: React.ReactNode; + title: string; + footnote?: React.ReactNode; + dark?: boolean; +} + +export const Examples = ({ children, title, footnote = null, dark }: ExamplesProps) => { + return ( +
+

{title}

+
{children}
+ {!!footnote &&
{footnote}
} +
+ ); +}; +import { ArgsTable } from "@storybook/addon-docs"; +import type { SortType } from "@storybook/blocks"; +import type { PropDescriptor } from "@storybook/preview-api"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore storybook addon types component as any so we have to do +type Component = any; +type BaseProps = { + include?: PropDescriptor; + exclude?: PropDescriptor; + sort?: SortType; +}; + +type OfProps = BaseProps & { + of: "." | "^" | Component; +}; + +export function CustomArgsTable({ of, sort }: OfProps) { + return ( +
+ +
+ ); +} +import type { ReactElement, ReactNode } from "react"; +import React from "react"; + +import { classNames } from "@calcom/lib"; + +export function VariantsTable({ + children, + titles, + isDark, + columnMinWidth = 150, +}: { + children: ReactElement | ReactElement[]; + titles: string[]; + isDark?: boolean; + // Mainly useful on mobile, so components don't get squeesed + columnMinWidth?: number; +}) { + const columns = React.Children.toArray(children) as ReactElement[]; + return ( +
+
+ + + {columns.map((column) => ( + + + {React.Children.count(column.props.children) && + React.Children.map(column.props.children, (cell) => ( + + ))} + + ))} +
+ {column.props.variant} + + {cell} +
+
+ {!isDark && ( +
+ + {children} + +
+ )} +
+ ); +} + +interface RowProps { + variant: string; + children: ReactNode; +} + +/** + * There are two reasons we have this "empty" wrapper component: + * 1. In order to have an isolate group per variant, which we iterate through in the table component. + * 2. To have a way to pass the variant. + */ +export function VariantRow({ children }: RowProps) { + return <>{children}; +} + +export function RowTitles({ titles }: { titles: string[] }) { + return ( + + + {titles.map((title) => ( + + {title} + + ))} + + ); +} +import dynamic from "next/dynamic"; +import type { SwaggerUI } from "swagger-ui-react"; + +import { SnippedGenerator, requestSnippets } from "@lib/snippets"; + +const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), { + ssr: false, +}); + +export default function APIDocs() { + return ( + + ); +} +import "highlight.js/styles/default.css"; +import "swagger-ui-react/swagger-ui.css"; + +import "../styles/globals.css"; + +function MyApp({ Component, pageProps }) { + return ; +} + +export default MyApp; +import { type TFunction } from "i18next"; +import i18next from "i18next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { headers } from "next/headers"; + +import { constructGenericImage } from "@calcom/lib/OgImages"; +import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants"; +//@ts-expect-error no type definitions +import config from "@calcom/web/next-i18next.config"; + +import { preparePageMetadata } from "@lib/metadata"; + +const create = async (locale: string, ns: string) => { + const { _nextI18Next } = await serverSideTranslations(locale, [ns], config); + + const _i18n = i18next.createInstance(); + _i18n.init({ + lng: locale, + resources: _nextI18Next?.initialI18nStore, + fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale, + }); + return _i18n; +}; + +const getFixedT = async (locale: string, ns: string) => { + const i18n = await create(locale, ns); + + return i18n.getFixedT(locale, ns); +}; + +export const _generateMetadata = async ( + getTitle: (t: TFunction) => string, + getDescription: (t: TFunction) => string +) => { + const h = headers(); + const canonical = h.get("x-pathname") ?? ""; + const locale = h.get("x-locale") ?? "en"; + + const t = await getFixedT(locale, "common"); + + const title = getTitle(t); + const description = getDescription(t); + + const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL); + + const image = + SEO_IMG_OGIMG + + constructGenericImage({ + title, + description, + }); + + return preparePageMetadata({ + title, + canonical, + image, + description, + siteName: APP_NAME, + metadataBase, + }); +}; +import type { GetStaticProps, GetStaticPropsContext } from "next"; +import { notFound, redirect } from "next/navigation"; + +export const withAppDirSsg = + >(getStaticProps: GetStaticProps) => + async (context: GetStaticPropsContext) => { + const ssgResponse = await getStaticProps(context); + + if ("redirect" in ssgResponse) { + redirect(ssgResponse.redirect.destination); + } + + if ("notFound" in ssgResponse) { + notFound(); + } + + const props = await Promise.resolve(ssgResponse.props); + + return { + ...ssgResponse.props, + // includes dehydratedState required for future page trpcPropvider + ...("trpcState" in props && { dehydratedState: props.trpcState }), + }; + }; +import type { GetServerSideProps, GetServerSidePropsContext } from "next"; +import { notFound, redirect } from "next/navigation"; + +export const withAppDirSsr = + >(getServerSideProps: GetServerSideProps) => + async (context: GetServerSidePropsContext) => { + const ssrResponse = await getServerSideProps(context); + + if ("redirect" in ssrResponse) { + redirect(ssrResponse.redirect.destination); + } + if ("notFound" in ssrResponse) { + notFound(); + } + + const props = await Promise.resolve(ssrResponse.props); + + return { + ...props, + // includes dehydratedState required for future page trpcPropvider + ...("trpcState" in props && { dehydratedState: props.trpcState }), + }; + }; +import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { HydrateClient } from "app/_trpc/HydrateClient"; +import { trpc } from "app/_trpc/client"; +import { useState } from "react"; +import superjson from "superjson"; + +import { httpBatchLink, httpLink, loggerLink, splitLink } from "@calcom/trpc/client"; +import { ENDPOINTS } from "@calcom/trpc/react/shared"; + +export type Endpoint = (typeof ENDPOINTS)[number]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const resolveEndpoint = (links: any) => { + // TODO: Update our trpc routes so they are more clear. + // This function parses paths like the following and maps them + // to the correct API endpoints. + // - viewer.me - 2 segment paths like this are for logged in requests + // - viewer.public.i18n - 3 segments paths can be public or authed + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (ctx: any) => { + const parts = ctx.op.path.split("."); + let endpoint; + let path = ""; + if (parts.length == 2) { + endpoint = parts[0] as keyof typeof links; + path = parts[1]; + } else { + endpoint = parts[1] as keyof typeof links; + path = parts.splice(2, parts.length - 2).join("."); + } + return links[endpoint]({ ...ctx, op: { ...ctx.op, path } }); + }; +}; + +export const TrpcProvider: React.FC<{ children: React.ReactNode; dehydratedState?: DehydratedState }> = ({ + children, + dehydratedState, +}) => { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { queries: { staleTime: 5000 } }, + }) + ); + const url = + typeof window !== "undefined" + ? "/api/trpc" + : process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}/api/trpc` + : `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`; + + const [trpcClient] = useState(() => + trpc.createClient({ + links: [ + // adds pretty logs to your console in development and logs errors in production + loggerLink({ + enabled: (opts) => + !!process.env.NEXT_PUBLIC_DEBUG || (opts.direction === "down" && opts.result instanceof Error), + }), + splitLink({ + // check for context property `skipBatch` + condition: (op) => !!op.context.skipBatch, + // when condition is true, use normal request + true: (runtime) => { + const links = Object.fromEntries( + ENDPOINTS.map((endpoint) => [ + endpoint, + httpLink({ + url: `${url}/${endpoint}`, + })(runtime), + ]) + ); + return resolveEndpoint(links); + }, + // when condition is false, use batch request + false: (runtime) => { + const links = Object.fromEntries( + ENDPOINTS.map((endpoint) => [ + endpoint, + httpBatchLink({ + url: `${url}/${endpoint}`, + })(runtime), + ]) + ); + return resolveEndpoint(links); + }, + }), + ], + transformer: superjson, + }) + ); + + return ( + + + {dehydratedState ? {children} : children} + + + ); +}; +"use client"; + +import { createHydrateClient } from "app/_trpc/createHydrateClient"; +import superjson from "superjson"; + +export const HydrateClient = createHydrateClient({ + transformer: superjson, +}); +"use client"; + +import { type DehydratedState, HydrationBoundary } from "@tanstack/react-query"; +import { useMemo } from "react"; + +type DataTransformer = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + serialize: (data: any) => any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + deserialize: (data: any) => any; +}; + +export function createHydrateClient(opts: { transformer: DataTransformer }) { + return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) { + const { state, children } = props; + + const transformedState: DehydratedState = useMemo(() => { + if (opts.transformer) { + return opts.transformer.deserialize(state); + } + return state; + }, [state]); + + return {children}; + }; +} +import type { LayoutProps, PageProps } from "app/_types"; +import { type GetServerSidePropsContext } from "next"; +import { cookies, headers } from "next/headers"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import PageWrapper from "@components/PageWrapperAppDir"; + +type WithLayoutParams> = { + getLayout: ((page: React.ReactElement) => React.ReactNode) | null; + Page?: (props: T) => React.ReactElement | null; + getData?: (arg: GetServerSidePropsContext) => Promise; + isBookingPage?: boolean; +}; + +export function WithLayout>({ + getLayout, + getData, + Page, + isBookingPage, +}: WithLayoutParams) { + return async

(p: P extends "P" ? PageProps : LayoutProps) => { + const h = headers(); + const nonce = h.get("x-nonce") ?? undefined; + let props = {} as T; + + if ("searchParams" in p && getData) { + props = (await getData(buildLegacyCtx(h, cookies(), p.params, p.searchParams))) ?? ({} as T); + } + + const children = "children" in p ? p.children : null; + + return ( + + {Page ? : children} + + ); + }; +} +import { dir } from "i18next"; +import { Inter } from "next/font/google"; +import localFont from "next/font/local"; +import { headers, cookies } from "next/headers"; +import Script from "next/script"; +import React from "react"; + +import { getLocale } from "@calcom/features/auth/lib/getLocale"; +import { IS_PRODUCTION } from "@calcom/lib/constants"; + +import { prepareRootMetadata } from "@lib/metadata"; + +import "../styles/globals.css"; + +const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" }); +const calFont = localFont({ + src: "../fonts/CalSans-SemiBold.woff2", + variable: "--font-cal", + preload: true, + display: "block", +}); + +export const generateMetadata = () => + prepareRootMetadata({ + twitterCreator: "@calcom", + twitterSite: "@calcom", + robots: { + index: false, + follow: false, + }, + }); + +const getInitialProps = async (url: string) => { + const { pathname, searchParams } = new URL(url); + + const isEmbed = pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null; + const embedColorScheme = searchParams?.get("ui.color-scheme"); + + const req = { headers: headers(), cookies: cookies() }; + const newLocale = await getLocale(req); + const direction = dir(newLocale); + + return { isEmbed, embedColorScheme, locale: newLocale, direction }; +}; + +const getFallbackProps = () => ({ + locale: "en", + direction: "ltr", + isEmbed: false, + embedColorScheme: false, +}); + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const h = headers(); + + const fullUrl = h.get("x-url") ?? ""; + const nonce = h.get("x-csp") ?? ""; + + const isSSG = !fullUrl; + + const { locale, direction, isEmbed, embedColorScheme } = isSSG + ? getFallbackProps() + : await getInitialProps(fullUrl); + + return ( + + + {!IS_PRODUCTION && process.env.VERCEL_ENV === "preview" && ( + // eslint-disable-next-line @next/next/no-sync-scripts +