Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
498 changes: 270 additions & 228 deletions components/editor/CodeBlock.tsx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions components/icons/Book.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';

/**
* @description Icon component for Book
*/
import React from 'react';

export default function IconBook({ className = '', ...props }: React.SVGProps<SVGSVGElement>) {
return (
<svg
Expand Down
134 changes: 134 additions & 0 deletions components/shims/react-syntax-highlighter-shim.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Highlight } from 'prism-react-renderer';
import React from 'react';

const prismLightTheme = {
plain: {
color: '#393A34',
backgroundColor: '#ffffff',
},
styles: [
{
types: ['comment', 'prolog', 'doctype', 'cdata'],
style: { color: '#999988', fontStyle: 'italic' as const },
},
{
types: ['namespace'],
style: { opacity: 0.7 },
},
{
types: ['string', 'attr-value'],
style: { color: '#669900' },
},
{
types: ['punctuation', 'operator'],
style: { color: '#393A34' },
},
{
types: [
'entity',
'url',
'symbol',
'number',
'boolean',
'variable',
'constant',
'regex',
'inserted',
],
style: { color: '#36acaa' },
},
{
types: ['atrule', 'keyword', 'attr-name', 'selector'],
style: { color: '#00a4db' },
},
{
types: ['function', 'deleted', 'tag'],
style: { color: '#d73a49' },
},
{
types: ['function-variable'],
style: { color: '#6f42c1' },
},
{
types: ['tag', 'selector', 'keyword'],
style: { color: '#00009f' },
},
{
types: ['property'],
style: { color: '#990055' },
},
],
};

interface SyntaxHighlighterProps {
/** Programming language (Schyma always passes 'json') */
language?: string;

/** Theme/style object — ignored in this shim; we use our own theme */
style?: Record<string, unknown>;

/** Inline style overrides applied to the outer <pre> */
customStyle?: React.CSSProperties;

/** Whether to render line numbers */
showLineNumbers?: boolean;

/** The code string to highlight */
children?: string;
}

/**
* @description Lightweight syntax highlighting component that replaces react-syntax-highlighter.
* Acts as a shim backed by prism-react-renderer to reduce bundle size.
*/
function SyntaxHighlighter({
language = 'json',
customStyle = {},
showLineNumbers = false,
children = '',
}: Readonly<SyntaxHighlighterProps>) {
return (
<Highlight theme={prismLightTheme} code={children} language={language}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre
className={className}
style={{ ...style, ...customStyle, overflowX: 'auto' }}
>
{tokens.map((line, i) => (
<div
key={`line-${i}-${line[0]?.content || ''}`}
{...getLineProps({ line })}
>
{showLineNumbers && (
<span
style={{
display: 'inline-block',
width: '2em',
textAlign: 'right',
paddingRight: '1em',
userSelect: 'none',
opacity: 0.5,
color: '#999',
}}
>
{i + 1}
</span>
)}
{line.map((token, tokenIndex) => (
<span
key={`token-${tokenIndex}-${token.content}`}
{...getTokenProps({ token })}
/>
))}
</div>
))}
</pre>
)}
</Highlight>
);
}

export { SyntaxHighlighter as Prism };
export { prismLightTheme as prism };

export default SyntaxHighlighter;
63 changes: 56 additions & 7 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import frontmatter from 'remark-frontmatter';
import images from 'remark-images';
import gemoji from 'remark-gemoji-to-emoji';
Expand All @@ -7,31 +10,77 @@ import headingId from 'remark-heading-id';
import remarkGfm from 'remark-gfm';
import withMDX from '@next/mdx';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

/**
* Lightweight shim that replaces the heavy react-syntax-highlighter (and its
* Refractor engine with ~280 language definitions) with a thin wrapper around
* prism-react-renderer, which is already used by the project's own CodeBlock
* component. Schyma is the only consumer of react-syntax-highlighter; the
* alias is therefore safe and removes ~2 MB of unused JS from the client
* bundle.
*/
const rshShimPath = path.resolve(
__dirname,
'components/shims/react-syntax-highlighter-shim.tsx',
);

/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
pageExtensions: ['tsx', 'ts', 'md', 'mdx'],
eslint: {
ignoreDuringBuilds: true
ignoreDuringBuilds: true,
},
output: 'export',
webpack(config, { isServer }) {
if (!isServer) {
config.resolve.fallback.fs = false;
}

// Redirect all react-syntax-highlighter imports to the lightweight shim.
// Schyma imports from two paths:
// 1. 'react-syntax-highlighter' → { Prism }
// 2. 'react-syntax-highlighter/dist/cjs/styles/prism' → { prism } theme
// Both are satisfied by the single shim module.
config.resolve.alias = {
...config.resolve.alias,
'react-syntax-highlighter/dist/cjs/styles/prism': rshShimPath,
'react-syntax-highlighter': rshShimPath,
};

return config;
}
},
};

const mdxConfig = withMDX({
extension: /\.mdx?$/,
providerImportSource: "@mdx-js/react",
providerImportSource: '@mdx-js/react',
options: {
remarkPlugins: [frontmatter, gemoji, headingId, slug, images, a11yEmoji, remarkGfm],
rehypePlugins: []
}
remarkPlugins: [
frontmatter,
gemoji,
headingId,
slug,
images,
a11yEmoji,
remarkGfm,
],
rehypePlugins: [],
},
});

export default mdxConfig(nextConfig);
export default async function getNextConfig() {
// Only load @next/bundle-analyzer when ANALYZE=true to avoid MODULE_NOT_FOUND
// in production environments where devDependencies are pruned.
const withBundleAnalyzer =
process.env.ANALYZE === 'true'
? (await import('@next/bundle-analyzer')).default({
enabled: true,
openAnalyzer: true,
})
: (config) => config;

return withBundleAnalyzer(mdxConfig(nextConfig));
}
Loading
Loading