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 <i@innei.in>

* chore: cleanup

Signed-off-by: Innei <i@innei.in>

* fix: typing

Signed-off-by: Innei <i@innei.in>

* fix: deps

Signed-off-by: Innei <i@innei.in>

* fix: import

Signed-off-by: Innei <i@innei.in>

* fix: patch package

Signed-off-by: Innei <i@innei.in>

* fix: tw class

Signed-off-by: Innei <i@innei.in>

---------

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-05-19 20:48:03 +08:00 committed by GitHub
parent 7391642cc0
commit abc6a8d832
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 382 additions and 95 deletions

View File

@ -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"
}
}
}
}

View File

@ -0,0 +1,9 @@
{
"name": "@xlog/shiki",
"exports": {
".": {
"react-server": "./shiki.server.tsx",
"default": "./shiki.client.tsx"
}
}
}

13
packages/shiki/shared.ts Normal file
View File

@ -0,0 +1,13 @@
import type { ShikiTransformer } from "shiki"
import {
transformerMetaHighlight,
transformerNotationDiff,
transformerNotationHighlight,
} from "@shikijs/transformers"
export const shikiTransformers: ShikiTransformer[] = [
transformerMetaHighlight(),
transformerNotationDiff(),
transformerNotationHighlight(),
]

View File

@ -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<ShikiCodeProps> = (props) => {
return (
<Suspense
fallback={
<pre>
<code>{props.code}</code>
</pre>
}
>
<ShikiRenderInternal {...props} />
</Suspense>
)
}
let langModule: Record<
BundledLanguage,
DynamicImportLanguageRegistration
> | null = null
let themeModule: Record<BundledTheme, DynamicImportThemeRegistration> | null =
null
const ShikiRenderInternal: FC<ShikiCodeProps> = ({
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 (
<pre>
<code>{code}</code>
</pre>
)
return <div dangerouslySetInnerHTML={{ __html: rendered }} />
}

View File

@ -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<ShikiCodeProps> = 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 <div dangerouslySetInnerHTML={{ __html: rendered }} />
}
let highlighter: Highlighter | undefined
export const createHighlighter = async () => {
if (!highlighter) {
highlighter = await getHighlighter({
themes: Object.keys(bundledThemes),
langs: Object.keys(bundledLanguages),
})
}
return highlighter
}

7
packages/shiki/types.ts Normal file
View File

@ -0,0 +1,7 @@
import type { StringLiteralUnion, ThemeRegistrationAny } from "shiki/types.mjs"
export interface ShikiCodeProps {
codeTheme?: ThemeRegistrationAny | StringLiteralUnion<any>
language?: string
code: string
}

View File

@ -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"
},

View File

@ -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

View File

@ -158,7 +158,7 @@ export default async function Home() {
<div className="text-center mt-28" key={feature.title}>
<div className="">
<div
className={`h-20 w-[1px] bg-gradient-to-b mx-auto text-feature-${feature.title.toLocaleLowerCase()}`}
className={`h-20 w-px bg-gradient-to-b mx-auto text-feature-${feature.title.toLocaleLowerCase()}`}
style={
{
"--tw-gradient-from": "transparent",

View File

@ -18,7 +18,6 @@ import { Avatar } from "~/components/ui/Avatar"
import { Button } from "~/components/ui/Button"
import { UniLink } from "~/components/ui/UniLink"
import { useDate } from "~/hooks/useDate"
import { useHighlighter } from "~/hooks/useHighlighter"
import { CSB_SCAN } from "~/lib/env"
import { getSiteLink } from "~/lib/helpers"
import { cn } from "~/lib/utils"
@ -58,8 +57,6 @@ export const CommentItem = ({
])
}, [deletePage.isSuccess, originalCharacterId, originalNoteId, queryClient])
const highlighter = useHighlighter()
if (!comment.metadata?.content?.content) {
return null
}
@ -142,7 +139,6 @@ export const CommentItem = ({
<MarkdownContent
content={comment.metadata?.content?.content}
strictMode={true}
highlighter={highlighter}
></MarkdownContent>
<div className="mt-1 flex items-center">
<div
@ -157,7 +153,7 @@ export const CommentItem = ({
</div>
{depth < 2 && (
<Button
className="text-gray-500 text-[13px] ml-1 mt-[-1px]"
className="text-gray-500 text-[13px] ml-1 -mt-px"
variant="text"
onClick={() => setReplyOpen(!replyOpen)}
>
@ -171,7 +167,7 @@ export const CommentItem = ({
)}
{comment.characterId === account?.characterId && (
<Button
className="text-gray-500 text-[13px] mt-[-1px]"
className="text-gray-500 text-[13px] -mt-px"
variant="text"
onClick={() => setEditOpen(!editOpen)}
>
@ -183,7 +179,7 @@ export const CommentItem = ({
!(comment as any)?.fromNotes?.list?.length && (
<>
<Button
className="text-gray-500 text-[13px] mt-[-1px]"
className="text-gray-500 text-[13px] -mt-px"
variant="text"
onClick={() => setDeleteConfirmModalOpen(true)}
isLoading={deletePage.isLoading}

View File

@ -32,7 +32,7 @@ const FadeIn = ({
isInView === false && !locked
? // @see https://www.debugbear.com/blog/opacity-animation-poor-lcp
"opacity-[0.00001] translate-y-[20%]"
: "opacity-[1] translate-y-0",
: "opacity-100 translate-y-0",
className,
)}
{...props}

View File

@ -1,6 +1,5 @@
import type { Result as TocResult } from "mdast-util-toc"
import { memo, type MutableRefObject } from "react"
import { Highlighter } from "shiki"
import type { BundledTheme } from "shiki/themes"
import PostActions from "~/components/site/PostActions"
@ -24,7 +23,6 @@ const MarkdownContent = memo(function PageContent({
site,
withActions,
onlyContent,
highlighter,
codeTheme,
}: {
content?: string
@ -39,7 +37,6 @@ const MarkdownContent = memo(function PageContent({
site?: ExpandedCharacter
withActions?: boolean
onlyContent?: boolean
highlighter?: Highlighter
codeTheme?: {
light?: BundledTheme
dark?: BundledTheme
@ -53,7 +50,6 @@ const MarkdownContent = memo(function PageContent({
content,
strictMode,
codeTheme,
highlighter,
})
}

View File

@ -1,5 +1,3 @@
import { createHighlighter } from "~/lib/highlighter"
import MarkdownContent from "./MarkdownContent"
type MarkdownContentServerProps = Omit<
@ -8,8 +6,7 @@ type MarkdownContentServerProps = Omit<
>
const MarkdownContentServer = async (props: MarkdownContentServerProps) => {
const highlighter = await createHighlighter()
return <MarkdownContent {...props} highlighter={highlighter} />
return <MarkdownContent {...props} />
}
export default MarkdownContentServer

View File

@ -35,8 +35,8 @@ export const usePatronModal = () => {
present({
title: (
<span className="inline-flex items-center justify-center w-full space-x-1">
<span className="text-red-500 flex size-6 mb-[-1px]">
<i className="i-mingcute-heart-fill text-2xl mb-[-1px]" />
<span className="text-red-500 flex size-6 -mb-px">
<i className="i-mingcute-heart-fill text-2xl -mb-px" />
</span>
<span className="truncate">{title}</span>
</span>

View File

@ -171,7 +171,7 @@ const ShareModal: FC<ShareModalProps> = ({ url, text, title }) => {
{shareList.map(({ name, icon, onClick }) => (
<li
key={name}
className="flex cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-lg transition-colors hover:bg-gray-100 [&_img]:size-[1rem]"
className="flex cursor-pointer items-center space-x-2 rounded-md px-3 py-2 text-lg transition-colors hover:bg-gray-100 [&_img]:size-4"
aria-label={`Share to ${name}`}
role="button"
onClick={() => onClick({ url, text, title })}

View File

@ -38,7 +38,7 @@ export const Titles = ({ characterId }: { characterId?: number }) => {
label={title.name}
childrenClassName={cn(
icons[title.name].bg,
"inline-flex p-[1px] rounded-sm",
"inline-flex p-px rounded-sm",
)}
>
<span

View File

@ -10,7 +10,6 @@ import MarkdownContent from "~/components/common/MarkdownContent"
import { toolbarShortcuts } from "~/components/dashboard/toolbars"
import { editorUpload } from "~/components/dashboard/toolbars/Multimedia"
import CodeMirror from "~/components/ui/CodeMirror"
import { useHighlighter } from "~/hooks/useHighlighter"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
import { cn } from "~/lib/utils"
import { renderPageContent } from "~/markdown"
@ -33,7 +32,6 @@ export default function DualColumnEditor({
dark?: BundledTheme
}
}) {
const highlighter = useHighlighter()
const isMobileLayout = useIsMobileLayout()
const t = useTranslations()
@ -53,17 +51,15 @@ export default function DualColumnEditor({
useDebounceEffect(
() => {
if (!highlighter) return
const result = renderPageContent({
content: values,
highlighter,
strictMode: true,
codeTheme,
})
setTree(result.tree)
setParsedContent(result)
},
[values, codeTheme, highlighter],
[values, codeTheme],
{
wait: 500,
},
@ -225,7 +221,7 @@ export default function DualColumnEditor({
/>
)}
{!isMobileLayout && (
<div className="z-10 w-[1px]">
<div className="z-10 w-px">
<div
aria-label="Toggle preview view"
className="bg-accent rounded-full cursor-pointer text-white size-6 -translate-x-1/2"
@ -248,7 +244,6 @@ export default function DualColumnEditor({
onMouseEnter={() => {
setCurrentScrollArea("preview")
}}
highlighter={highlighter}
codeTheme={codeTheme}
/>
)}

View File

@ -4,7 +4,6 @@ import dynamic from "next/dynamic"
import { useState } from "react"
import { useDate } from "~/hooks/useDate"
import { useHighlighter } from "~/hooks/useHighlighter"
import { RESERVED_TAGS } from "~/lib/constants"
import { cn } from "~/lib/utils"
@ -19,7 +18,6 @@ export const ImportPreview = ({ note }: { note: NoteMetadata }) => {
const date = useDate()
const [showcaseMore, setShowcaseMore] = useState(false)
const t = useTranslations()
const highlighter = useHighlighter()
return (
<article className="border rounded-xl p-6 mt-4">
@ -62,7 +60,6 @@ export const ImportPreview = ({ note }: { note: NoteMetadata }) => {
<DynamicMarkdownContent
className="mt-4"
content={note?.content}
highlighter={highlighter}
></DynamicMarkdownContent>
</div>
</article>

View File

@ -0,0 +1,39 @@
import { type FC, type ReactNode } from "react"
import type { BundledTheme } from "shiki/themes"
import { ShikiRender } from "@xlog/shiki"
const ShikiRemark: FC<{
codeTheme?: {
light?: BundledTheme
dark?: BundledTheme
}
children?: ReactNode
}> = (props) => {
const code = pickMdAstCode(props)
const language = pickCodeLanguage(props)
return (
<ShikiRender code={code} language={language} codeTheme={props.codeTheme} />
)
}
const pickMdAstCode = (props: any) => {
return props.children.type === "code"
? (props.children.props.children as string)
: ""
}
const pickCodeLanguage = (props: any) => {
const className =
props.children.type === "code"
? (props.children.props.className as string)
: ""
if (className.includes("language-")) {
return className.replace("language-", "")
}
return ""
}
export default ShikiRemark

View File

@ -4,7 +4,7 @@ import type { Extension } from "@codemirror/state"
import { Compartment } from "@codemirror/state"
import { oneDark } from "@codemirror/theme-one-dark"
import { EditorView } from "@codemirror/view"
import { githubLight } from "@ddietr/codemirror-themes/theme/github-light"
import { githubLight } from "@ddietr/codemirror-themes/github-light.js"
import { useIsUnmounted } from "./useLifecycle"

View File

@ -1,18 +0,0 @@
import { useEffect, useState } from "react"
import type { Highlighter } from "shiki"
import { createHighlighter } from "~/lib/highlighter"
export function useHighlighter() {
const [highlighter, setHighlighter] = useState<Highlighter | undefined>()
useEffect(() => {
createHighlighter()
.then((h) => {
setHighlighter(h)
})
.catch((e) => {
console.error(e)
})
}, [])
return highlighter
}

View File

@ -1,10 +1,16 @@
import type { Root as HashRoot } from "hast"
import { toHtml } from "hast-util-to-html"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import { toJsxRuntime, type ExtraProps } from "hast-util-to-jsx-runtime"
import jsYaml from "js-yaml"
import type { Root as MdashRoot } from "mdast"
import { toc } from "mdast-util-toc"
import dynamic from "next/dynamic"
import {
createElement,
type ClassAttributes,
type FC,
type HTMLAttributes,
} from "react"
import { toast } from "react-hot-toast"
import { Fragment, jsx, jsxs } from "react/jsx-runtime"
import rehypeAutolinkHeadings from "rehype-autolink-headings"
@ -23,7 +29,6 @@ import remarkGithubAlerts from "remark-github-alerts"
import remarkMath from "remark-math"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import type { Highlighter } from "shiki"
import type { BundledTheme } from "shiki/themes"
import { unified } from "unified"
import { visit } from "unist-util-visit"
@ -31,8 +36,6 @@ import { VFile } from "vfile"
// @ts-expect-error
import remarkCalloutDirectives from "@microflash/remark-callout-directives"
import rehypeShikiFromHighlighter from "@shikijs/rehype/core"
import { transformerMetaHighlight } from "@shikijs/transformers"
import AdvancedImage from "~/components/ui/AdvancedImage"
import { isServerSide } from "~/lib/utils"
@ -59,15 +62,20 @@ const XLogPost = dynamic(() => import("~/components/ui/XLogPost"))
const APlayer = dynamic(() => import("~/components/ui/APlayer"))
const DPlayer = dynamic(() => import("~/components/ui/DPlayer"))
const RSS = dynamic(() => import("~/components/ui/RSS"))
const ShikiRemark = dynamic(() => import("~/components/ui/ShikiRemark"))
const memoedPreComponentMap = {} as Record<string, any>
const hashCodeThemeKey = (codeTheme?: Record<string, any>): string => {
if (!codeTheme) return "default"
return Object.values(codeTheme).join(",")
}
export const renderPageContent = ({
content,
highlighter,
strictMode,
codeTheme,
}: {
content: string
highlighter?: Highlighter
strictMode?: boolean
codeTheme?: {
light?: BundledTheme
@ -128,18 +136,6 @@ export const renderPageContent = ({
})
.use(rehypeRemoveH1)
if (highlighter) {
pipeline.use(rehypeShikiFromHighlighter, highlighter, {
themes: codeTheme ?? {
light: "github-light-default",
dark: "github-dark-default",
},
onError: (e) => {
console.error(e)
},
transformers: [transformerMetaHighlight()],
})
}
pipeline
.use(rehypeKatex, {
strict: false,
@ -157,7 +153,17 @@ export const renderPageContent = ({
toast.error(error?.message)
}
}
let Pre: FC<
ClassAttributes<HTMLPreElement> &
HTMLAttributes<HTMLPreElement> &
ExtraProps
> = memoedPreComponentMap[hashCodeThemeKey(codeTheme)]
if (!Pre) {
Pre = function Pre(props: any) {
return createElement(ShikiRemark, { ...props, codeTheme }, props.children)
}
memoedPreComponentMap[hashCodeThemeKey(codeTheme)] = Pre
}
return {
tree: hastTree,
toToc: () =>
@ -186,6 +192,9 @@ export const renderPageContent = ({
// @ts-expect-error
style: Style,
rss: RSS,
// @ts-expect-error
pre: Pre,
},
ignoreInvalidStyle: true,
// @ts-expect-error: untyped.

View File

@ -1,16 +1,22 @@
{
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "esnext"],
"lib": [
"DOM",
"DOM.Iterable",
"esnext"
],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "preserve",
"moduleResolution": "node",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
"~/*": [
"./src/*"
]
},
"noEmit": true,
"forceConsistentCasingInFileNames": true,
@ -18,13 +24,23 @@
"skipLibCheck": true,
"incremental": true,
"module": "esnext",
"typeRoots": ["./types"],
"typeRoots": [
"./types"
],
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"packages/**/*"
],
"exclude": [
"node_modules"
]
}