diff --git a/.gitignore b/.gitignore index 2debba5d..52cfb965 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,6 @@ yarn.lock .vercel # Generated Content -src/shiki/ public/robots.txt public/sitemap.xml public/sitemap-*.xml \ No newline at end of file diff --git a/docs/articles/archiving-requests-to-storage.md b/docs/articles/archiving-requests-to-storage.md index 386496af..54ac39f8 100644 --- a/docs/articles/archiving-requests-to-storage.md +++ b/docs/articles/archiving-requests-to-storage.md @@ -43,8 +43,7 @@ You'll need another environment variable called `BLOB_CONTAINER_PATH`. We'll write a policy called `request-archive-policy` that can be used on all routes. -```ts -// file-archive-policy.ts +```ts title="file-archive-policy.ts" import { ZuploRequest, ZuploContext } from "@zuplo/runtime"; export type RequestArchivePolicyOptions = { diff --git a/package-lock.json b/package-lock.json index 2769a059..e54aa274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,10 +41,10 @@ "remark-gfm": "^3.0.1", "remark-rehype": "^10.1.0", "sharp": "^0.32.0", - "shiki": "^0.14.7", "simple-functional-loader": "^1.2.1", "tailwindcss": "^3.3.3", "typescript": "^5.2.2", + "unified": "^10.1.2", "unist-util-visit": "^4.1.2", "zustand": "^4.4.7" }, @@ -3484,11 +3484,6 @@ "node": ">=8" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4703,9 +4698,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "engines": { "node": ">=8" } @@ -8802,9 +8797,9 @@ } }, "node_modules/node-abi": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.35.0.tgz", - "integrity": "sha512-jAlSOFR1Bls963NmFwxeQkNTzqjUF0NThm8Le7eRIRGzFUVJuMOFZDLv5Y30W/Oaw+KEebEJLAigwO9gQHoEmw==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", "dependencies": { "semver": "^7.3.5" }, @@ -8813,9 +8808,9 @@ } }, "node_modules/node-addon-api": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.0.0.tgz", - "integrity": "sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, "node_modules/node-eval": { "version": "2.0.0", @@ -10514,17 +10509,6 @@ "node": ">=8" } }, - "node_modules/shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, "node_modules/shikiji": { "version": "0.9.19", "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.9.19.tgz", @@ -11751,16 +11735,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -14827,11 +14801,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15567,9 +15536,9 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, "devlop": { "version": "1.1.0", @@ -18428,17 +18397,17 @@ "requires": {} }, "node-abi": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.35.0.tgz", - "integrity": "sha512-jAlSOFR1Bls963NmFwxeQkNTzqjUF0NThm8Le7eRIRGzFUVJuMOFZDLv5Y30W/Oaw+KEebEJLAigwO9gQHoEmw==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", "requires": { "semver": "^7.3.5" } }, "node-addon-api": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.0.0.tgz", - "integrity": "sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, "node-eval": { "version": "2.0.0", @@ -19607,17 +19576,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", - "requires": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, "shikiji": { "version": "0.9.19", "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.9.19.tgz", @@ -20444,16 +20402,6 @@ "unist-util-stringify-position": "^3.0.0" } }, - "vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" - }, - "vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" - }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 50714446..27de7b25 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,8 @@ "build:scripts": "node ./scripts/build.mjs", "format": "prettier --write .", "typecheck": "tsc", - "postinstall": "npm run get-policies && npm run ci:shiki", + "postinstall": "npm run get-policies", "get-policies": "sh ./scripts/get-policies.sh", - "ci:shiki": "node ./scripts/copy-shiki.mjs", "ci:policies": "node ./scripts/build.mjs && node --enable-source-maps ./scripts/update-policies.main.mjs" }, "browserslist": "defaults, not ie <= 11", @@ -50,10 +49,10 @@ "remark-gfm": "^3.0.1", "remark-rehype": "^10.1.0", "sharp": "^0.32.0", - "shiki": "^0.14.7", "simple-functional-loader": "^1.2.1", "tailwindcss": "^3.3.3", "typescript": "^5.2.2", + "unified": "^10.1.2", "unist-util-visit": "^4.1.2", "zustand": "^4.4.7" }, diff --git a/scripts/copy-shiki.mjs b/scripts/copy-shiki.mjs deleted file mode 100644 index 18439fcd..00000000 --- a/scripts/copy-shiki.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { cp } from "fs/promises"; -import path from "path"; - -await cp( - path.join(process.cwd(), "node_modules", "shiki", "languages"), - path.join(process.cwd(), "src", "shiki", "languages"), - { recursive: true }, -); -await cp( - path.join(process.cwd(), "node_modules", "shiki", "themes"), - path.join(process.cwd(), "src", "shiki", "themes"), - { recursive: true }, -); diff --git a/src/components/Code.tsx b/src/components/Code.tsx deleted file mode 100644 index 1adbcf85..00000000 --- a/src/components/Code.tsx +++ /dev/null @@ -1,363 +0,0 @@ -"use client"; - -import { Tab } from "@headlessui/react"; -import clsx from "clsx"; -import { Check, Copy } from "lucide-react"; -import { - Children, - createContext, - isValidElement, - useContext, - useEffect, - useRef, - useState, -} from "react"; -import { create } from "zustand"; - -import { Tag } from "@/components/Tag"; - -const languageNames: Record = { - js: "JavaScript", - ts: "TypeScript", - javascript: "JavaScript", - typescript: "TypeScript", -}; - -function getPanelTitle({ - title, - language, -}: { - title?: string; - language?: string; -}) { - if (title) { - return title; - } - if (language && language in languageNames) { - return languageNames[language]; - } - return "Code"; -} - -function CopyButton({ code }: { code: string }) { - let [copyCount, setCopyCount] = useState(0); - let copied = copyCount > 0; - - useEffect(() => { - if (copyCount > 0) { - let timeout = setTimeout(() => setCopyCount(0), 1000); - return () => { - clearTimeout(timeout); - }; - } - }, [copyCount]); - - return ( - - ); -} - -function CodePanelHeader({ tag, label }: { tag?: string; label?: string }) { - if (!tag && !label) { - return null; - } - - return ( -
- {tag && ( -
- {tag} -
- )} - {tag && label && } - {label && ( - {label} - )} -
- ); -} - -function CodePanel({ - children, - tag, - label, - code, -}: { - children: React.ReactNode; - tag?: string; - label?: string; - code?: string; -}) { - let child = Children.only(children); - - if (isValidElement(child)) { - tag = child.props.tag ?? tag; - label = child.props.label ?? label; - code = child.props.code ?? code; - } - - if (!code) { - throw new Error( - "`CodePanel` requires a `code` prop, or a child with a `code` prop.", - ); - } - - return ( -
- -
-
{children}
- -
-
- ); -} - -function CodeGroupHeader({ - title, - children, - selectedIndex, -}: { - title: string; - children: React.ReactNode; - selectedIndex: number; -}) { - let hasTabs = Children.count(children) > 1; - - if (!title && !hasTabs) { - return null; - } - - return ( -
- {title && ( -

- {title} -

- )} - {hasTabs && ( - - {Children.map(children, (child, childIndex) => ( - - {getPanelTitle(isValidElement(child) ? child.props : {})} - - ))} - - )} -
- ); -} - -function CodeGroupPanels({ - children, - ...props -}: React.ComponentPropsWithoutRef) { - let hasTabs = Children.count(children) > 1; - - if (hasTabs) { - return ( - - {Children.map(children, (child) => ( - - {child} - - ))} - - ); - } - - return {children}; -} - -function usePreventLayoutShift() { - let positionRef = useRef(null); - let rafRef = useRef(); - - useEffect(() => { - return () => { - if (typeof rafRef.current !== "undefined") { - window.cancelAnimationFrame(rafRef.current); - } - }; - }, []); - - return { - positionRef, - preventLayoutShift(callback: () => void) { - if (!positionRef.current) { - return; - } - - let initialTop = positionRef.current.getBoundingClientRect().top; - - callback(); - - rafRef.current = window.requestAnimationFrame(() => { - let newTop = - positionRef.current?.getBoundingClientRect().top ?? initialTop; - window.scrollBy(0, newTop - initialTop); - }); - }, - }; -} - -const usePreferredLanguageStore = create<{ - preferredLanguages: Array; - addPreferredLanguage: (language: string) => void; -}>()((set) => ({ - preferredLanguages: [], - addPreferredLanguage: (language) => - set((state) => ({ - preferredLanguages: [ - ...state.preferredLanguages.filter( - (preferredLanguage) => preferredLanguage !== language, - ), - language, - ], - })), -})); - -function useTabGroupProps(availableLanguages: Array) { - let { preferredLanguages, addPreferredLanguage } = - usePreferredLanguageStore(); - let [selectedIndex, setSelectedIndex] = useState(0); - let activeLanguage = [...availableLanguages].sort( - (a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a), - )[0]; - let languageIndex = availableLanguages.indexOf(activeLanguage); - let newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex; - if (newSelectedIndex !== selectedIndex) { - setSelectedIndex(newSelectedIndex); - } - - let { positionRef, preventLayoutShift } = usePreventLayoutShift(); - - return { - as: "div" as const, - ref: positionRef, - selectedIndex, - onChange: (newSelectedIndex: number) => { - preventLayoutShift(() => - addPreferredLanguage(availableLanguages[newSelectedIndex]), - ); - }, - }; -} - -const CodeGroupContext = createContext(false); - -export function CodeGroup({ - children, - title, - backgroundColor, - ...props -}: React.ComponentPropsWithoutRef & { - title: string; - backgroundColor: string; -}) { - let languages = - Children.map(children, (child) => - getPanelTitle(isValidElement(child) ? child.props : {}), - ) ?? []; - let tabGroupProps = useTabGroupProps(languages); - let hasTabs = Children.count(children) > 1; - - let containerClassName = - "my-6 overflow-hidden rounded-md bg-zinc-900 shadow-md dark:ring-1 dark:ring-white/10"; - let header = ( - - {children} - - ); - let panels = {children}; - - return ( - - {hasTabs ? ( - -
- {header} - {panels} -
-
- ) : ( -
-
- {header} - {panels} -
-
- )} -
- ); -} - -export function Code({ - children, - ...props -}: React.ComponentPropsWithoutRef<"code">) { - let isGrouped = useContext(CodeGroupContext); - - if (isGrouped) { - if (typeof children !== "string") { - throw new Error( - "`Code` children must be a string when nested inside a `CodeGroup`.", - ); - } - return ; - } - - return {children}; -} - -export function Pre({ - children, - ...props -}: React.ComponentPropsWithoutRef) { - let isGrouped = useContext(CodeGroupContext); - - if (isGrouped) { - return children; - } - - return {children}; -} diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx new file mode 100644 index 00000000..5a330664 --- /dev/null +++ b/src/components/CopyButton.tsx @@ -0,0 +1,55 @@ +"use client"; + +import clsx from "clsx"; +import { Check, Copy } from "lucide-react"; +import { useEffect, useState } from "react"; + +export function CopyButton({ code }: { code: string }) { + let [copyCount, setCopyCount] = useState(0); + let copied = copyCount > 0; + + useEffect(() => { + if (copyCount > 0) { + let timeout = setTimeout(() => setCopyCount(0), 1000); + return () => { + clearTimeout(timeout); + }; + } + }, [copyCount]); + + return ( + + ); +} diff --git a/src/components/Fence.tsx b/src/components/Fence.tsx index 48c4af4b..3383519c 100644 --- a/src/components/Fence.tsx +++ b/src/components/Fence.tsx @@ -1,6 +1,5 @@ import { ReactElement } from "react"; -import * as shiki from "shiki"; -import { CodeGroup } from "./Code"; +import { compileMdxFragment } from "../lib/markdown/mdx"; export async function Fence({ children, @@ -9,30 +8,19 @@ export async function Fence({ children: string | ReactElement; language: string; }) { - const highlighter = await shiki.getHighlighter({ - theme: "github-dark", - }); - - let code: string; - let lang: string; + let raw: string; + let lang: any; if (typeof children === "string") { - code = children; + raw = children; lang = language; } else if (children.type === "code") { - code = children.props.children; + raw = children.props.children; lang = children.props.className?.replace("language-", "") ?? "txt"; } else { throw new Error("Invalid use of component"); } - const html = highlighter.codeToHtml(code, { lang }); - return ( - -
- - ); + const mdx = `\`\`\`${lang}\n${raw}\n\`\`\``; + const html = await compileMdxFragment(mdx); + return <>{html}; } diff --git a/src/components/Pre.tsx b/src/components/Pre.tsx new file mode 100644 index 00000000..59eb4d0f --- /dev/null +++ b/src/components/Pre.tsx @@ -0,0 +1,14 @@ +import { CopyButton } from "./CopyButton"; + +export default function Pre({ children, raw, className, ...props }: any) { + return ( +
+
+        
+ + {children} +
+
+
+ ); +} diff --git a/src/components/markdown/index.tsx b/src/components/markdown/index.tsx index 1648084b..b7cd0181 100644 --- a/src/components/markdown/index.tsx +++ b/src/components/markdown/index.tsx @@ -1,7 +1,7 @@ +import Pre from "@/components/Pre"; import GithubButton from "@/components/markdown/GithubButton"; import { MDXProvider } from "@mdx-js/react"; import { ChevronLeftIcon } from "lucide-react"; -import { Fence } from "../Fence"; import Callout from "./Callout"; import ZupIt from "./ZupIt"; import { @@ -32,7 +32,8 @@ const components: React.ComponentProps["components"] = { GithubButton, Callout, ZupIt, - pre: Fence as any, + pre: Pre, + // pre: Fence as any, img: (props) => ( // eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element diff --git a/src/lib/markdown/mdx.ts b/src/lib/markdown/mdx.ts index 5e16ed6f..d92442d4 100644 --- a/src/lib/markdown/mdx.ts +++ b/src/lib/markdown/mdx.ts @@ -2,21 +2,22 @@ import { migrateContent } from "@/build/migrate.mjs"; import { Section } from "@/lib/interfaces"; import remarkTransformLink from "@/lib/markdown/md-links"; import { CompileOptions } from "@mdx-js/mdx"; -import fs from "fs/promises"; import { Element } from "hast"; import { h } from "hastscript"; +import { Root } from "mdast"; import { compileMDX } from "next-mdx-remote/rsc"; -import path from "path"; import rehypeAutolinkHeadings, { Options as RehypeAutolinkHeadingsOptions, } from "rehype-autolink-headings"; +import rehypeCode, { Options as CodeOptions } from "rehype-pretty-code"; import rehypeRewrite, { RehypeRewriteOptions } from "rehype-rewrite"; import rehypeSlug from "rehype-slug"; import remarkGfm from "remark-gfm"; +import { Plugin } from "unified"; +import { visit } from "unist-util-visit"; import { VFile } from "vfile"; import components from "../../components/markdown"; import remarkStaticImage from "./static-images"; - export interface SerializeOptions { /** * Pass-through variables for use in the MDX content @@ -33,27 +34,6 @@ export interface SerializeOptions { parseFrontmatter?: boolean; } -// Shiki loads languages and themes using "fs" instead of "import", so Next.js -// doesn't bundle them into production build. To work around, we manually copy -// them over to our source code (lib/shiki/*) and update the "paths". -// -// Note that they are only referenced on server side -// See: https://github.com/shikijs/shiki/issues/138 -const getShikiPath = (): string => { - return path.join(process.cwd(), "src/shiki"); -}; - -const touched = { current: false }; - -// "Touch" the shiki assets so that Vercel will include them in the production -// bundle. This is required because shiki itself dynamically access these files, -// so Vercel doesn't know about them by default -const touchShikiPath = (): void => { - if (touched.current) return; // only need to do once - fs.readdir(getShikiPath()); // fire and forget - touched.current = true; -}; - const rehypeAutolinkHeadingsOptions: RehypeAutolinkHeadingsOptions = { behavior: "append", properties: { @@ -89,6 +69,11 @@ const rehypeRewriteOptions: RehypeRewriteOptions = { }, }; +const rehypeCodeOptions: CodeOptions = { + theme: "github-dark", + keepBackground: false, +}; + function getHeaderRewriteOptions(headings: Element[]): RehypeRewriteOptions { return { rewrite: (node) => { @@ -102,27 +87,70 @@ function getHeaderRewriteOptions(headings: Element[]): RehypeRewriteOptions { }; } -function getOptions(headings: Element[]): SerializeOptions { +const rehypeGetRawCode: Plugin<[], Root, Root> = () => (tree: any) => { + visit(tree, (node) => { + if (node?.type === "element" && node?.tagName === "pre") { + const [codeEl] = node.children; + + if (codeEl.tagName !== "code") return; + + node.raw = codeEl.children?.[0].value; + } + }); +}; +const rehypeAddRawCode: Plugin<[], Root, Root> = () => (tree: any) => { + visit(tree, (node) => { + if (node?.type === "element" && node?.tagName === "figure") { + if (!("data-rehype-pretty-code-figure" in node.properties)) { + return; + } + + for (const child of node.children) { + if (child.tagName === "pre") { + child.properties["raw"] = node.raw; + } + } + } + }); +}; + +function getOptions(headings?: Element[]): SerializeOptions { return { parseFrontmatter: true, mdxOptions: { remarkPlugins: [remarkTransformLink as any, remarkStaticImage, remarkGfm], rehypePlugins: [ + rehypeGetRawCode, + [rehypeCode as any, rehypeCodeOptions], + rehypeAddRawCode, rehypeSlug, [rehypeAutolinkHeadings, rehypeAutolinkHeadingsOptions], [rehypeRewrite as any, rehypeRewriteOptions], - [rehypeRewrite as any, getHeaderRewriteOptions(headings)], + ...(headings + ? [rehypeRewrite as any, getHeaderRewriteOptions(headings)] + : []), ], }, }; } +export async function compileMdxFragment(source: string) { + const vfile = new VFile(source); + vfile.path = "fragement.mdx"; + + const options = getOptions(); + const result = await compileMDX({ + source: vfile as any, + options, + components, + }); + return result.content; +} + export async function compileMdx>( source: string, filepath: string, ) { - touchShikiPath(); - const migrated = await migrateContent(source); const vfile = new VFile(migrated); diff --git a/src/styles/prism.css b/src/styles/prism.css deleted file mode 100644 index 1b00e3e9..00000000 --- a/src/styles/prism.css +++ /dev/null @@ -1,47 +0,0 @@ -pre[class*='language-'] { - color: theme('colors.gray.50'); -} - -.token.tag, -.token.class-name, -.token.selector, -.token.selector .class, -.token.selector.class, -.token.function { - color: theme('colors.pink.400'); -} - -.token.attr-name, -.token.keyword, -.token.rule, -.token.pseudo-class, -.token.important { - color: theme('colors.gray.300'); -} - -.token.module { - color: theme('colors.pink.400'); -} - -.token.attr-value, -.token.class, -.token.string, -.token.property { - color: theme('colors.sky.300'); -} - -.token.punctuation, -.token.attr-equals { - color: theme('colors.gray.500'); -} - -.token.unit, -.language-css .token.function { - color: theme('colors.teal.200'); -} - -.token.comment, -.token.operator, -.token.combinator { - color: theme('colors.gray.400'); -} diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index 6e8498cc..e6a868e2 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -25,3 +25,15 @@ mask-position: center; mask-size: contain; } + +pre { + @apply !rounded-md; +} + +[data-rehype-pretty-code-title] { + @apply bg-gray-900 text-zinc-200 rounded-t-md py-2 px-3 font-semibold text-sm border-b border-b-gray-700; +} + +figcaption + div > pre { + @apply !rounded-t-none; +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 8ea4147b..a1a86dce 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -76,15 +76,6 @@ export default { }, "blockquote p:first-of-type::before": { content: "none" }, "blockquote p:first-of-type::after": { content: "none" }, - // '.prose :where(code):not(:where([class ~ ="not-prose"], [class ~ ="not-prose"] *))': - // { - // fontWeight: 500, - // borderWidth: 1, - // borderColor: theme("colors.gray.300"), - // backgroundColor: theme("colors.gray.50"), - // borderRadius: 4, - // padding: 4, - // }, }, }, lg: { @@ -98,8 +89,6 @@ export default { }, invert: { css: { - // "--tw-prose-headings": theme("colors.white"), - // "--tw-prose-invert-headings": theme("colors.white"), "--tw-prose-body": theme("colors.white"), p: { color: "white",