From abc6a8d832a7a0c0113fed1d16217d1e0c74509a Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 19 May 2024 20:48:03 +0800 Subject: [PATCH] refactor: move shiki package, split rsc and rcc boundary component and dynamic import shiki resource (#1820) * refactor: move shiki package, split rsc and rcc boundary component Signed-off-by: Innei * chore: cleanup Signed-off-by: Innei * fix: typing Signed-off-by: Innei * fix: deps Signed-off-by: Innei * fix: import Signed-off-by: Innei * fix: patch package Signed-off-by: Innei * fix: tw class Signed-off-by: Innei --------- Signed-off-by: Innei --- package.json | 9 +- packages/shiki/package.json | 9 + packages/shiki/shared.ts | 13 ++ packages/shiki/shiki.client.tsx | 169 ++++++++++++++++++ packages/shiki/shiki.server.tsx | 41 +++++ packages/shiki/types.ts | 7 + .../tailwindcss-variable-colors@0.0.2.patch | 12 ++ pnpm-lock.yaml | 38 ++-- src/app/[locale]/(home)/about/page.tsx | 2 +- src/components/common/CommentItem.tsx | 10 +- src/components/common/FadeIn.tsx | 2 +- src/components/common/MarkdownContent.tsx | 4 - .../common/MarkdownContentServer.tsx | 5 +- src/components/common/PatronModal.tsx | 4 +- src/components/common/ReactionShare.tsx | 2 +- src/components/common/Titles.tsx | 2 +- src/components/dashboard/DualColumnEditor.tsx | 9 +- src/components/dashboard/ImportPreview.tsx | 3 - src/components/ui/ShikiRemark.tsx | 39 ++++ src/hooks/useCodemirrorTheme.ts | 2 +- src/hooks/useHighlighter.ts | 18 -- src/markdown/index.ts | 47 +++-- tsconfig.json | 30 +++- 23 files changed, 382 insertions(+), 95 deletions(-) create mode 100644 packages/shiki/package.json create mode 100644 packages/shiki/shared.ts create mode 100644 packages/shiki/shiki.client.tsx create mode 100644 packages/shiki/shiki.server.tsx create mode 100644 packages/shiki/types.ts create mode 100644 patches/tailwindcss-variable-colors@0.0.2.patch create mode 100644 src/components/ui/ShikiRemark.tsx delete mode 100644 src/hooks/useHighlighter.ts diff --git a/package.json b/package.json index 32e75860..d1592d5b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@monaco-editor/react": "4.6.0", "@popperjs/core": "2.11.8", "@prisma/client": "5.13.0", - "@shikijs/rehype": "^1.5.1", "@shikijs/transformers": "^1.5.1", "@supercharge/request-ip": "1.2.0", "@tanstack/react-query": "4.36.1", @@ -67,12 +66,14 @@ "@urql/core": "5.0.3", "@vercel/otel": "1.8.2", "@wagmi/core": "1", + "@xlog/shiki": "link:./packages/shiki", "ahooks": "3.7.11", "aplayer": "1.10.1", "async-lock": "1.4.1", "canvas-confetti": "1.9.3", "cheerio": "1.0.0-rc.12", "chroma-js": "2.4.2", + "client-only": "0.0.1", "clsx": "2.1.1", "crossbell": "1.11.9", "dayjs": "1.11.11", @@ -143,6 +144,7 @@ "remove-markdown": "0.5.0", "rss-parser": "3.13.0", "serialize-javascript": "6.0.2", + "server-only": "0.0.1", "sharp": "0.32.6", "shiki": "^1.5.1", "sortablejs": "1.15.2", @@ -212,6 +214,9 @@ "overrides": { "@wagmi/core": "$@wagmi/core", "open-graph-scraper>undici": "6.6.2" + }, + "patchedDependencies": { + "tailwindcss-variable-colors@0.0.2": "patches/tailwindcss-variable-colors@0.0.2.patch" } } -} +} \ No newline at end of file diff --git a/packages/shiki/package.json b/packages/shiki/package.json new file mode 100644 index 00000000..93269cbd --- /dev/null +++ b/packages/shiki/package.json @@ -0,0 +1,9 @@ +{ + "name": "@xlog/shiki", + "exports": { + ".": { + "react-server": "./shiki.server.tsx", + "default": "./shiki.client.tsx" + } + } +} \ No newline at end of file diff --git a/packages/shiki/shared.ts b/packages/shiki/shared.ts new file mode 100644 index 00000000..d93f67e5 --- /dev/null +++ b/packages/shiki/shared.ts @@ -0,0 +1,13 @@ +import type { ShikiTransformer } from "shiki" + +import { + transformerMetaHighlight, + transformerNotationDiff, + transformerNotationHighlight, +} from "@shikijs/transformers" + +export const shikiTransformers: ShikiTransformer[] = [ + transformerMetaHighlight(), + transformerNotationDiff(), + transformerNotationHighlight(), +] diff --git a/packages/shiki/shiki.client.tsx b/packages/shiki/shiki.client.tsx new file mode 100644 index 00000000..426b96dc --- /dev/null +++ b/packages/shiki/shiki.client.tsx @@ -0,0 +1,169 @@ +"use client" + +import { Suspense, use, useMemo, useRef, type FC } from "react" +import { + bundledLanguages, + bundledThemes, + getHighlighter, + type BundledLanguage, + type BundledTheme, + type DynamicImportLanguageRegistration, + type DynamicImportThemeRegistration, + type Highlighter, + type HighlighterCore, +} from "shiki" + +import { shikiTransformers } from "./shared" +import type { ShikiCodeProps } from "./types" + +let highlighter: Highlighter | undefined +export const createHighlighter = async () => { + if (!highlighter) { + highlighter = await getHighlighter({ + themes: Object.keys(bundledThemes), + langs: Object.keys(bundledLanguages), + }) + } + return highlighter +} + +let highlighterCore: HighlighterCore | null = null +const codeHighlighterPromise = (async () => { + if (highlighterCore) return highlighterCore + const [{ getHighlighterCore }, getWasm] = await Promise.all([ + import("shiki/core"), + import("shiki/wasm").then((m) => m.default), + ]) + + const core = await getHighlighterCore({ + themes: [ + import("shiki/themes/github-light.mjs"), + import("shiki/themes/github-dark.mjs"), + ], + langs: [], + loadWasm: getWasm, + }) + + highlighterCore = core + return core +})() + +export const ShikiRender: FC = (props) => { + return ( + + {props.code} + + } + > + + + ) +} + +let langModule: Record< + BundledLanguage, + DynamicImportLanguageRegistration +> | null = null +let themeModule: Record | null = + null + +const ShikiRenderInternal: FC = ({ + code, + codeTheme = { + light: "github-light-default", + dark: "github-dark-default", + }, + language, +}) => { + const shiki = use(codeHighlighterPromise) + const loadThemesRef = useRef([] as string[]) + const loadLanguagesRef = useRef([] as string[]) + + use( + useMemo(() => { + async function register() { + if (!language || !codeTheme) return + + async function loadShikiLanguage( + language: string, + languageModule: any, + ) { + if (!shiki) return + if (!shiki.getLoadedLanguages().includes(language)) { + await shiki.loadLanguage(await languageModule()) + } + } + async function loadShikiTheme(theme: string, themeModule: any) { + if (!shiki) return + if (!shiki.getLoadedThemes().includes(theme)) { + await shiki.loadTheme(await themeModule()) + } + } + const [{ bundledLanguages }, { bundledThemes }] = + langModule && themeModule + ? [ + { + bundledLanguages: langModule, + }, + { bundledThemes: themeModule }, + ] + : await Promise.all([import("shiki/langs"), import("shiki/themes")]) + + langModule = bundledLanguages + themeModule = bundledThemes + + if ( + language && + loadLanguagesRef.current.includes(language) && + codeTheme && + loadThemesRef.current.includes(codeTheme) + ) + return + return Promise.all([ + (async () => { + if (language) { + const importFn = (bundledLanguages as any)[language] + if (!importFn) return + await loadShikiLanguage(language || "", importFn) + loadLanguagesRef.current.push(language) + } + })(), + (async () => { + if (codeTheme) { + const themes = [codeTheme.light, codeTheme.dark] + return themes.map(async (theme) => { + const importFn = (bundledThemes as any)[theme] + if (!importFn) return + await loadShikiTheme(theme || "", importFn) + loadThemesRef.current.push(theme) + }) + } + })(), + ]) + } + return register() + }, [codeTheme, language, shiki]), + ) + + const rendered = useMemo(() => { + try { + return shiki.codeToHtml(code, { + lang: language!, + themes: codeTheme, + transformers: shikiTransformers, + }) + } catch { + return null + } + }, [shiki, code, language, codeTheme]) + + if (!rendered) + return ( +
+        {code}
+      
+ ) + return
+} diff --git a/packages/shiki/shiki.server.tsx b/packages/shiki/shiki.server.tsx new file mode 100644 index 00000000..c736044b --- /dev/null +++ b/packages/shiki/shiki.server.tsx @@ -0,0 +1,41 @@ +import type { FC } from "react" +import { + bundledLanguages, + bundledThemes, + getHighlighter, + type Highlighter, +} from "shiki" + +import { shikiTransformers } from "./shared" +import type { ShikiCodeProps } from "./types" + +export const ShikiRender: FC = async ({ + code, + codeTheme, + language, +}) => { + const highlighter = await createHighlighter() + + const rendered = highlighter.codeToHtml(code, { + lang: language || "text", + themes: codeTheme || { + light: "github-light-default", + dark: "github-dark-default", + }, + transformers: shikiTransformers, + }) + + return
+} + +let highlighter: Highlighter | undefined + +export const createHighlighter = async () => { + if (!highlighter) { + highlighter = await getHighlighter({ + themes: Object.keys(bundledThemes), + langs: Object.keys(bundledLanguages), + }) + } + return highlighter +} diff --git a/packages/shiki/types.ts b/packages/shiki/types.ts new file mode 100644 index 00000000..6346fe0d --- /dev/null +++ b/packages/shiki/types.ts @@ -0,0 +1,7 @@ +import type { StringLiteralUnion, ThemeRegistrationAny } from "shiki/types.mjs" + +export interface ShikiCodeProps { + codeTheme?: ThemeRegistrationAny | StringLiteralUnion + language?: string + code: string +} diff --git a/patches/tailwindcss-variable-colors@0.0.2.patch b/patches/tailwindcss-variable-colors@0.0.2.patch new file mode 100644 index 00000000..d7a47c45 --- /dev/null +++ b/patches/tailwindcss-variable-colors@0.0.2.patch @@ -0,0 +1,12 @@ +diff --git a/package.json b/package.json +index 01142d760fc17a9a5202ce09f9ddd4cd203e860d..64db0fa428cf3daed5685ee72bd3c2844a03977f 100644 +--- a/package.json ++++ b/package.json +@@ -23,6 +23,7 @@ + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "exports": { ++ "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8eb2d85c..bf55f57c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,11 @@ overrides: '@wagmi/core': '1' open-graph-scraper>undici: 6.6.2 +patchedDependencies: + tailwindcss-variable-colors@0.0.2: + hash: uq3gxwrbflqvdc47smhui7x62u + path: patches/tailwindcss-variable-colors@0.0.2.patch + importers: .: @@ -99,9 +104,6 @@ importers: '@prisma/client': specifier: 5.13.0 version: 5.13.0(prisma@5.13.0) - '@shikijs/rehype': - specifier: ^1.5.1 - version: 1.5.1 '@shikijs/transformers': specifier: ^1.5.1 version: 1.5.1 @@ -123,6 +125,9 @@ importers: '@wagmi/core': specifier: '1' version: 1.4.13(@types/react@18.3.1)(bufferutil@4.0.8)(immer@10.1.1)(ioredis@5.4.1)(react@18.3.1)(typescript@5.4.5)(utf-8-validate@5.0.10)(viem@1.21.1(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + '@xlog/shiki': + specifier: link:./packages/shiki + version: link:packages/shiki ahooks: specifier: 3.7.11 version: 3.7.11(react@18.3.1) @@ -141,6 +146,9 @@ importers: chroma-js: specifier: 2.4.2 version: 2.4.2 + client-only: + specifier: 0.0.1 + version: 0.0.1 clsx: specifier: 2.1.1 version: 2.1.1 @@ -351,6 +359,9 @@ importers: serialize-javascript: specifier: 6.0.2 version: 6.0.2 + server-only: + specifier: 0.0.1 + version: 0.0.1 sharp: specifier: 0.32.6 version: 0.32.6 @@ -507,7 +518,7 @@ importers: version: 3.4.3 tailwindcss-variable-colors: specifier: 0.0.2 - version: 0.0.2(tailwindcss@3.4.3) + version: 0.0.2(patch_hash=uq3gxwrbflqvdc47smhui7x62u)(tailwindcss@3.4.3) typescript: specifier: 5.4.5 version: 5.4.5 @@ -1992,9 +2003,6 @@ packages: '@shikijs/core@1.5.1': resolution: {integrity: sha512-xjV63pRUBvxA1LsxOUhRKLPh0uUjwBLzAKLdEuYSLIylo71sYuwDcttqNP01Ib1TZlLfO840CXHPlgUUsYFjzg==} - '@shikijs/rehype@1.5.1': - resolution: {integrity: sha512-UzmDzid4Zv4ZeY+GvJlKabBVdTt40aCCoOyeq/JwLJ0YmiZhm5vOicpzdbmMcUClgpCtBjJRLaOg8mkhd7YX/w==} - '@shikijs/transformers@1.5.1': resolution: {integrity: sha512-vir+y0elkjh2CepLVbqeGX+ftuc6WpfWNCMV/EBIallSLzhBfDO9r/TORDVOzegbTg9JMEmtOFv6PT9cSZTcyA==} @@ -6188,6 +6196,9 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + server-only@0.0.1: + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -8880,15 +8891,6 @@ snapshots: '@shikijs/core@1.5.1': {} - '@shikijs/rehype@1.5.1': - dependencies: - '@shikijs/transformers': 1.5.1 - '@types/hast': 3.0.4 - hast-util-to-string: 3.0.0 - shiki: 1.5.1 - unified: 11.0.4 - unist-util-visit: 5.0.0 - '@shikijs/transformers@1.5.1': dependencies: shiki: 1.5.1 @@ -14225,6 +14227,8 @@ snapshots: dependencies: randombytes: 2.1.0 + server-only@0.0.1: {} + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -14518,7 +14522,7 @@ snapshots: dependencies: tailwindcss: 3.4.3 - tailwindcss-variable-colors@0.0.2(tailwindcss@3.4.3): + tailwindcss-variable-colors@0.0.2(patch_hash=uq3gxwrbflqvdc47smhui7x62u)(tailwindcss@3.4.3): dependencies: tailwindcss: 3.4.3 diff --git a/src/app/[locale]/(home)/about/page.tsx b/src/app/[locale]/(home)/about/page.tsx index 98c4c239..9f349903 100644 --- a/src/app/[locale]/(home)/about/page.tsx +++ b/src/app/[locale]/(home)/about/page.tsx @@ -158,7 +158,7 @@ export default async function Home() {
{depth < 2 && (