Skip to content

Commit 3dd7ff1

Browse files
committed
feat: enhance code block rendering with dynamic formatting and highlighting
1 parent 8ba62e0 commit 3dd7ff1

File tree

5 files changed

+126
-49
lines changed

5 files changed

+126
-49
lines changed
Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,85 @@
1+
"use client";
2+
13
import { CopyButton } from "@/components/ui/copy-button";
2-
import prettier from "prettier";
3-
import { codeToHtml } from "shiki";
4+
import * as babel from "prettier/plugins/babel";
5+
import * as estree from "prettier/plugins/estree";
6+
import * as prettier from "prettier/standalone";
7+
import { type JSX, useLayoutEffect, useState } from "react";
48
import type { BundledLanguage } from "shiki/bundle/web";
9+
import { highlight } from "./shared";
510

6-
interface LanguageProps {
7-
children: string;
11+
interface CodeBlockProps {
12+
code: string;
813
lang: BundledLanguage;
14+
initial?: JSX.Element;
15+
}
16+
17+
async function formatCode(code: string, lang: string) {
18+
try {
19+
// Configuración básica para JavaScript/TypeScript
20+
const plugins = [babel, estree];
21+
console.log("Formatting with plugins:", plugins);
22+
23+
const formatted = await prettier.format(code, {
24+
parser: "babel-ts",
25+
plugins,
26+
semi: true,
27+
singleQuote: true,
28+
tabWidth: 2,
29+
useTabs: false,
30+
printWidth: 120,
31+
});
32+
33+
console.log("Formatted code:", formatted);
34+
return formatted;
35+
} catch (error) {
36+
console.error("Error formatting code:", error);
37+
return code; // Retorna el código original si hay error
38+
}
939
}
1040

11-
const getParserForLanguage = (language: string): string => {
12-
const languageMap: { [key: string]: string } = {
13-
js: "babel",
14-
jsx: "babel",
15-
ts: "typescript",
16-
tsx: "typescript",
17-
json: "json",
18-
css: "css",
19-
scss: "scss",
20-
less: "less",
21-
html: "html",
22-
xml: "xml",
23-
markdown: "markdown",
24-
md: "markdown",
25-
yaml: "yaml",
26-
yml: "yaml",
27-
};
28-
29-
return languageMap[language.toLowerCase()] || "babel";
30-
};
31-
32-
export async function CodeBlock(props: LanguageProps) {
33-
const format = await prettier.format(props.children, {
34-
semi: true,
35-
singleQuote: true,
36-
tabWidth: 2,
37-
useTabs: false,
38-
printWidth: 120,
39-
parser: getParserForLanguage(props.lang),
40-
});
41-
const out = await codeToHtml(format, {
42-
lang: props.lang,
43-
theme: "houston",
44-
});
41+
export function CodeBlock({ code, lang, initial }: CodeBlockProps) {
42+
const [nodes, setNodes] = useState<JSX.Element | undefined>(initial);
43+
const [formattedCode, setFormattedCode] = useState(code);
44+
45+
useLayoutEffect(() => {
46+
async function formatAndHighlight() {
47+
try {
48+
console.log("Original code:", code);
49+
const formatted = await formatCode(code, lang);
50+
setFormattedCode(formatted);
51+
52+
// Then highlight the formatted code
53+
const highlighted = await highlight(formatted, lang);
54+
setNodes(highlighted);
55+
} catch (error) {
56+
console.error("Error in formatAndHighlight:", error);
57+
// If formatting fails, try to highlight the original code
58+
const highlighted = await highlight(code, lang);
59+
setNodes(highlighted);
60+
}
61+
}
62+
63+
void formatAndHighlight();
64+
}, [code, lang]);
65+
66+
if (!nodes) {
67+
return (
68+
<div className="group relative">
69+
<div className="text-sm p-4 rounded-lg bg-[#18191F] overflow-auto animate-pulse">
70+
<div className="h-4 bg-gray-700 rounded w-3/4 mb-2" />
71+
<div className="h-4 bg-gray-700 rounded w-1/2" />
72+
</div>
73+
</div>
74+
);
75+
}
4576

4677
return (
4778
<div className="group relative">
48-
<CopyButton text={format} />
49-
<div
50-
dangerouslySetInnerHTML={{ __html: out }}
51-
className="text-sm p-4 rounded-lg bg-[#18191F] overflow-auto"
52-
/>
79+
<CopyButton text={formattedCode} />
80+
<div className="text-sm p-4 rounded-lg bg-[#18191F] overflow-auto">
81+
{nodes}
82+
</div>
5383
</div>
5484
);
5585
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
2+
import type { JSX } from "react";
3+
import { Fragment } from "react";
4+
import { jsx, jsxs } from "react/jsx-runtime";
5+
import type { BundledLanguage } from "shiki/bundle/web";
6+
import { codeToHast } from "shiki/bundle/web";
7+
8+
export async function highlight(code: string, lang: BundledLanguage) {
9+
const out = await codeToHast(code, {
10+
lang,
11+
theme: "houston",
12+
});
13+
14+
return toJsxRuntime(out, {
15+
Fragment,
16+
jsx,
17+
jsxs,
18+
}) as JSX.Element;
19+
}

apps/website/app/[locale]/blog/[slug]/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,10 @@ export default async function BlogPostPage({ params }: Props) {
177177
code: ({ className, children }) => {
178178
const match = /language-(\w+)/.exec(className || "");
179179
return (
180-
<CodeBlock lang={match ? (match[1] as BundledLanguage) : "ts"}>
181-
{children?.toString() || ""}
182-
</CodeBlock>
180+
<CodeBlock
181+
lang={match ? (match[1] as BundledLanguage) : "ts"}
182+
code={children?.toString() || ""}
183+
/>
183184
);
184185
},
185186
};

apps/website/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"scripts": {
77
"dev": "next dev",
88
"build": "next build",
9-
"start": "next start",
9+
"start": "next start -p 3001",
1010
"lint": "next lint",
1111
"typecheck": "tsc --noEmit"
1212
},
1313
"browserslist": "defaults, not ie <= 11",
1414
"dependencies": {
15+
"hast-util-to-jsx-runtime": "2.3.5",
1516
"@headlessui/react": "^2.2.0",
1617
"@headlessui/tailwindcss": "^0.2.0",
1718
"@radix-ui/react-accordion": "^1.2.1",

pnpm-lock.yaml

Lines changed: 28 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)