diff --git a/packages/mdxts/src/components/CodeBlock.tsx b/packages/mdxts/src/components/CodeBlock.tsx index 54f12382..e841cb45 100644 --- a/packages/mdxts/src/components/CodeBlock.tsx +++ b/packages/mdxts/src/components/CodeBlock.tsx @@ -8,7 +8,7 @@ import { format, resolveConfig } from 'prettier' import { BUNDLED_LANGUAGES } from 'shiki' import 'server-only' -import { getTheme } from '../index' +import { getTheme } from '../utils/get-theme' import { getContext } from '../utils/context' import { getSourcePath } from '../utils/get-source-path' import { isJsxOnly } from '../utils/is-jsx-only' @@ -41,9 +41,6 @@ export type BaseCodeBlockProps = { /** Lines to highlight. */ highlight?: string - /** VS Code-based theme for highlighting. */ - theme?: Theme - /** Show or hide the copy button. */ allowCopy?: boolean @@ -107,7 +104,6 @@ export async function CodeBlock({ language, lineNumbers, highlight, - theme: themeProp, className, showErrors, allowErrors, @@ -124,14 +120,7 @@ export async function CodeBlock({ const contextValue = getContext(Context) const { isNestedInEditor, sourcePath, sourcePathLine, sourcePathColumn } = props as PrivateCodeBlockProps - const theme = themeProp ?? contextValue.theme ?? getTheme() - - if (!theme) { - throw new Error( - 'The [theme] prop was not provided to the [CodeBlock] component. Pass an explicit theme or make sure the mdxts/loader package is configured correctly.' - ) - } - + const theme = getTheme() let id = 'source' in props ? props.source : filenameProp if ('value' in props && id === undefined) { @@ -254,7 +243,6 @@ export async function CodeBlock({ padding, paddingHorizontal, paddingVertical, - theme, isNestedInEditor, showErrors, allowErrors, diff --git a/packages/mdxts/src/components/CodeInline.tsx b/packages/mdxts/src/components/CodeInline.tsx index 81743e59..aa7b59f7 100644 --- a/packages/mdxts/src/components/CodeInline.tsx +++ b/packages/mdxts/src/components/CodeInline.tsx @@ -2,10 +2,8 @@ import React, { Fragment } from 'react' import { BUNDLED_LANGUAGES } from 'shiki' import 'server-only' -import { getTheme } from '../index' -import { getContext } from '../utils/context' -import { Context } from './Context' -import { getHighlighter, type Theme } from './highlighter' +import { getTheme } from '../utils/get-theme' +import { getHighlighter } from './highlighter' const languageMap: Record = { mjs: 'javascript', @@ -19,9 +17,6 @@ export type CodeInlineProps = { /** Language of the code snippet. */ language?: (typeof BUNDLED_LANGUAGES)[number] | (typeof languageKeys)[number] - /** VS Code-based theme for highlighting. */ - theme?: Theme - /** Padding to apply to the wrapping element. */ padding?: string @@ -41,7 +36,6 @@ export type CodeInlineProps = { /** Renders a `code` element with syntax highlighting. */ export async function CodeInline({ language, - theme: themeProp, className, padding = '0.25rem', paddingHorizontal = padding, @@ -49,14 +43,7 @@ export async function CodeInline({ style, ...props }: CodeInlineProps) { - const contextValue = getContext(Context) - const theme = themeProp ?? contextValue.theme ?? getTheme() - - if (!theme) { - throw new Error( - 'The [theme] prop was not provided to the [CodeInline] component. Pass an explicit theme or make sure the mdxts/loader package is configured correctly.' - ) - } + const theme = getTheme() let finalValue: string = props.value // Trim extra whitespace from inline code blocks since it's difficult to read. diff --git a/packages/mdxts/src/components/CodeToolbar.tsx b/packages/mdxts/src/components/CodeToolbar.tsx index 3b213956..f1d18a74 100644 --- a/packages/mdxts/src/components/CodeToolbar.tsx +++ b/packages/mdxts/src/components/CodeToolbar.tsx @@ -1,7 +1,6 @@ -'use client' import React from 'react' +import { getTheme } from '../utils/get-theme' import { CopyButton } from './CopyButton' -import type { Theme } from './highlighter' type BaseCodeToolbarProps = { /** The value of the code block. */ @@ -10,9 +9,6 @@ type BaseCodeToolbarProps = { /** The path to the source file on disk in development and the git provider source in production. */ sourcePath?: string - /** The theme to use for highlighting. */ - theme: Theme - /** Whether or not to allow copying the code block. */ allowCopy?: boolean } @@ -31,10 +27,10 @@ type CodeToolbarProps = export function CodeToolbar({ value, sourcePath, - theme, allowCopy, ...props }: CodeToolbarProps) { + const theme = getTheme() return (
) : null} @@ -270,7 +267,6 @@ export function CodeView({ filename={filename} highlighter={highlighter} language={language} - theme={theme} diagnostics={tokenDiagnostics} edit={edit} rootDirectory={rootDirectory} diff --git a/packages/mdxts/src/components/Context.tsx b/packages/mdxts/src/components/Context.tsx index 53d3ad2e..d25401a1 100644 --- a/packages/mdxts/src/components/Context.tsx +++ b/packages/mdxts/src/components/Context.tsx @@ -1,9 +1,7 @@ import { createContext } from '../utils/context' export const Context = createContext<{ - theme?: any workingDirectory?: string }>({ - theme: undefined, workingDirectory: undefined, }) diff --git a/packages/mdxts/src/components/ExportedTypes.tsx b/packages/mdxts/src/components/ExportedTypes.tsx index 38c1b5c1..2585e178 100644 --- a/packages/mdxts/src/components/ExportedTypes.tsx +++ b/packages/mdxts/src/components/ExportedTypes.tsx @@ -287,24 +287,14 @@ export function ExportedTypes(props: ExportedTypesProps) { if (typeof props.children === 'function') { return ( - + {props.children(exportedTypes)} ) } return ( - + {exportedTypes.map((declaration, index) => { return (
- - - ) - } - return } diff --git a/packages/mdxts/src/components/QuickInfo.tsx b/packages/mdxts/src/components/QuickInfo.tsx index b69f7ff0..28bbb2fc 100644 --- a/packages/mdxts/src/components/QuickInfo.tsx +++ b/packages/mdxts/src/components/QuickInfo.tsx @@ -2,6 +2,7 @@ import React, { Fragment } from 'react' import { type ts, type Diagnostic } from 'ts-morph' import { getDiagnosticMessageText } from '@tsxmod/utils' +import { getTheme } from '../utils/get-theme' import { type getHighlighter } from './highlighter' import { languageService } from './project' import { MDXContent } from './MDXContent' @@ -12,7 +13,6 @@ export function QuickInfo({ filename, highlighter, language, - theme, diagnostics, edit, isQuickInfoOpen, @@ -23,13 +23,13 @@ export function QuickInfo({ filename: string highlighter: Awaited> language: string - theme: any diagnostics: Diagnostic[] edit: any isQuickInfoOpen?: boolean rootDirectory?: string baseDirectory?: string }) { + const theme = getTheme() const quickInfo = languageService.getQuickInfoAtPosition(filename, position) if (!quickInfo) { diff --git a/packages/mdxts/src/components/highlighter.ts b/packages/mdxts/src/components/highlighter.ts index 021f2c30..378041ec 100644 --- a/packages/mdxts/src/components/highlighter.ts +++ b/packages/mdxts/src/components/highlighter.ts @@ -3,8 +3,8 @@ import { getHighlighter as shikiGetHighlighter } from 'shiki' import type { SourceFile } from 'ts-morph' import { Node, SyntaxKind } from 'ts-morph' +import { getTheme } from '../utils/get-theme' import { getContext } from '../utils/context' -import { getTheme } from '../index' import { Context } from './Context' type Color = string @@ -78,9 +78,7 @@ export const getHighlighter = cache(async function getHighlighter( ): Promise { if (highlighter === null) { if (!options) { - const contextValue = getContext(Context) - const theme = contextValue.theme ?? getTheme() - options = { theme } + options = { theme: getTheme() } } highlighter = await shikiGetHighlighter(options) diff --git a/packages/mdxts/src/index.ts b/packages/mdxts/src/index.ts index 42bb09ac..3dd3754a 100644 --- a/packages/mdxts/src/index.ts +++ b/packages/mdxts/src/index.ts @@ -13,6 +13,8 @@ import type { Headings } from './mdx-plugins/remark/add-headings' import type { AllModules, ModuleData } from './utils/get-all-data' import { getAllData } from './utils/get-all-data' +export { getTheme } from './utils/get-theme' + type FeedOptions = Omit< ConstructorParameters[0], 'generator' | 'link' | 'id' @@ -613,21 +615,3 @@ function generateRssFeed }>( return feed.rss2() } - -let theme: any = null - -/** - * Sets the current theme. - * @internal - */ -export function setTheme(newTheme: any) { - theme = newTheme -} - -/** - * Returns the current theme. - * @internal - */ -export function getTheme() { - return theme -} diff --git a/packages/mdxts/src/loader/index.ts b/packages/mdxts/src/loader/index.ts index 61336966..bb1cee6c 100644 --- a/packages/mdxts/src/loader/index.ts +++ b/packages/mdxts/src/loader/index.ts @@ -1,6 +1,5 @@ import * as webpack from 'webpack' -import { dirname, basename, join, relative, resolve, sep } from 'node:path' -import { existsSync } from 'node:fs' +import { dirname, join, relative, resolve, sep } from 'node:path' import { glob } from 'fast-glob' import globParent from 'glob-parent' import { Node, Project, SyntaxKind } from 'ts-morph' @@ -18,65 +17,21 @@ import { findCommonRootPath } from '../utils/find-common-root-path' * will be used (e.g. `Button.tsx` and `Button.mdx`). */ export default async function loader( - this: webpack.LoaderContext<{ - themePath?: string - }>, + this: webpack.LoaderContext<{}>, source: string | Buffer ) { const callback = this.async() - const options = this.getOptions() const sourceString = source.toString() const workingDirectory = dirname(this.resourcePath) - /** Add theme to Next.js entry layout files and examples. */ - const isExample = this.resourcePath.endsWith('.examples.tsx') - if ( - !source.includes('import theme') && - (isNextJsEntryLayout(this.resourcePath) || isExample) && - options.themePath - ) { - let relativeThemePath = relative(workingDirectory, options.themePath) - - if (options.themePath.endsWith('.json') && !existsSync(options.themePath)) { - throw new Error( - `mdxts: Could not find theme at ${options.themePath} or ${relativeThemePath}. Please provide a valid theme path.` - ) - } - - if (!options.themePath.endsWith('.json')) { - relativeThemePath = `shiki/themes/${options.themePath}.json` - } - - if (isExample) { - /** Examples don't inherit `setTheme` below so we explicitly import the theme and pass it to the components. */ - source = `${source}\nimport theme from '${relativeThemePath}';` - .replaceAll( - ' | null = null + +/** + * Returns the configured code syntax highlighting theme. + * @internal + */ +export function getTheme() { + const themePath = process.env.MDXTS_THEME_PATH + + if (themePath === undefined) { + throw new Error( + '[mdxts] The MDXTS_THEME_PATH environment variable is undefined. Set process.env.MDXTS_THEME_PATH or configure the `theme` option in the `mdxts/next` plugin to load a theme.' + ) + } + + if (theme === null) { + theme = JSON.parse(readFileSync(themePath, 'utf-8')) + } + + return theme! +} diff --git a/site/app/HeroExample.tsx b/site/app/HeroExample.tsx index 46644356..128464c7 100644 --- a/site/app/HeroExample.tsx +++ b/site/app/HeroExample.tsx @@ -45,8 +45,6 @@ export function Sidebar() { } `.trim() -const theme = getTheme() -const LINE_COLOR = theme.colors['panel.border'] const codeProps = { padding: '0.7rem', toolbar: false, @@ -54,6 +52,8 @@ const codeProps = { } satisfies Partial export function HeroExample() { + const theme = getTheme() + const lineColor = theme.colors['panel.border'] const entries = allDocs.all().filter((doc) => doc.depth === 1) const lastEntriesIndex = entries.length - 1 return ( @@ -129,7 +129,7 @@ export function HeroExample() { style={{ gridColumn: '21 / span 36', gridRow: '8 / span 5', - border: `1px solid ${LINE_COLOR}`, + border: `1px solid ${lineColor}`, borderLeft: 'none', borderTopRightRadius: '0.5rem', borderBottomRightRadius: '0.5rem', @@ -139,7 +139,7 @@ export function HeroExample() { style={{ gridColumn: '31 / span 2', gridRow: '13 / span 5', - border: `1px solid ${LINE_COLOR}`, + border: `1px solid ${lineColor}`, borderRight: 'none', translate: '0 -1px', borderBottomLeftRadius: '0.5rem', @@ -149,7 +149,7 @@ export function HeroExample() { style={{ gridColumn: '3 / span 18', gridRow: '13 / span 7', - border: `1px solid ${LINE_COLOR}`, + border: `1px solid ${lineColor}`, borderRight: 'none', translate: '0 -1px', borderBottomLeftRadius: '0.5rem', @@ -187,6 +187,7 @@ function Card({ row: string children: React.ReactNode }) { + const theme = getTheme() return (
@@ -255,6 +257,7 @@ function VerticalLine({ align?: 'start' | 'center' | 'end' style?: React.CSSProperties }) { + const theme = getTheme() return (