feat: add Shiki transformers for better syntax highlighting (#534)

* refactor: organize prose related styles inside nested prose

* feat: add Shiki transformers for better syntax highlighting

* chore: update blog posts using Shiki transformers

Closes #210
This commit is contained in:
Sat Naing
2025-06-13 23:08:18 +07:00
committed by GitHub
parent b07ee5cdcc
commit 76e15b55e3
9 changed files with 121 additions and 85 deletions
+12 -1
View File
@@ -4,6 +4,11 @@ import sitemap from "@astrojs/sitemap";
import remarkToc from "remark-toc"; import remarkToc from "remark-toc";
import remarkCollapse from "remark-collapse"; import remarkCollapse from "remark-collapse";
import { SITE } from "./src/config"; import { SITE } from "./src/config";
import {
transformerNotationDiff,
transformerNotationHighlight,
transformerNotationWordHighlight,
} from "@shikijs/transformers";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
@@ -18,7 +23,13 @@ export default defineConfig({
shikiConfig: { shikiConfig: {
// For more themes, visit https://shiki.style/themes // For more themes, visit https://shiki.style/themes
themes: { light: "min-light", dark: "night-owl" }, themes: { light: "min-light", dark: "night-owl" },
wrap: true, defaultColor: false,
wrap: false,
transformers: [
transformerNotationDiff({ matchAlgorithm: "v3" }),
transformerNotationHighlight(),
transformerNotationWordHighlight(),
],
}, },
}, },
vite: { vite: {
+1
View File
@@ -29,6 +29,7 @@
"devDependencies": { "devDependencies": {
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@pagefind/default-ui": "^1.3.0", "@pagefind/default-ui": "^1.3.0",
"@shikijs/transformers": "^3.6.0",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@types/lodash.kebabcase": "^4.1.9", "@types/lodash.kebabcase": "^4.1.9",
"@typescript-eslint/parser": "^8.34.0", "@typescript-eslint/parser": "^8.34.0",
+11
View File
@@ -51,6 +51,9 @@ importers:
'@pagefind/default-ui': '@pagefind/default-ui':
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.0 version: 1.3.0
'@shikijs/transformers':
specifier: ^3.6.0
version: 3.6.0
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.16 specifier: ^0.5.16
version: 0.5.16(tailwindcss@4.1.8) version: 0.5.16(tailwindcss@4.1.8)
@@ -892,6 +895,9 @@ packages:
'@shikijs/themes@3.6.0': '@shikijs/themes@3.6.0':
resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==} resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==}
'@shikijs/transformers@3.6.0':
resolution: {integrity: sha512-PYkU54lYV0RCaUG8n2FNTF+YWiU3uPhcjLGq2x/C8lIrUX9GVnRb3bK+R5xtdFHbuctntATKm7ondp/H/dux9Q==}
'@shikijs/types@3.6.0': '@shikijs/types@3.6.0':
resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==} resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==}
@@ -3628,6 +3634,11 @@ snapshots:
dependencies: dependencies:
'@shikijs/types': 3.6.0 '@shikijs/types': 3.6.0
'@shikijs/transformers@3.6.0':
dependencies:
'@shikijs/core': 3.6.0
'@shikijs/types': 3.6.0
'@shikijs/types@3.6.0': '@shikijs/types@3.6.0':
dependencies: dependencies:
'@shikijs/vscode-textmate': 10.0.2 '@shikijs/vscode-textmate': 10.0.2
+6 -2
View File
@@ -44,8 +44,8 @@ If you dont want subdirectories to affect the post URL, just prefix the folde
src/data/blog/very-first-post.md -> mysite.com/posts/very-first-post src/data/blog/very-first-post.md -> mysite.com/posts/very-first-post
src/data/blog/2025/example-post.md -> mysite.com/posts/2025/example-post src/data/blog/2025/example-post.md -> mysite.com/posts/2025/example-post
src/data/blog/_2026/another-post.md -> mysite.com/posts/another-post src/data/blog/_2026/another-post.md -> mysite.com/posts/another-post
src/data/blog/docs/_legacy/how-to.md -> mysite.com/docs/how-to src/data/blog/docs/_legacy/how-to.md -> mysite.com/posts/docs/how-to
src/data/blog/Example Dir/Dummy Post.md -> mysite.com/example-dir/dummy-post src/data/blog/Example Dir/Dummy Post.md -> mysite.com/posts/example-dir/dummy-post
``` ```
> 💡 Tip: You can override a blog posts slug in the frontmatter as well. See the next section for more details. > 💡 Tip: You can override a blog posts slug in the frontmatter as well. See the next section for more details.
@@ -91,6 +91,7 @@ If you omit `tags` in a blog post (in other words, if no tag is specified), the
export const blogSchema = z.object({ export const blogSchema = z.object({
// --- // ---
draft: z.boolean().optional(), draft: z.boolean().optional(),
// [!code highlight:1]
tags: z.array(z.string()).default(["others"]), // replace "others" with whatever you want tags: z.array(z.string()).default(["others"]), // replace "others" with whatever you want
// --- // ---
}); });
@@ -128,6 +129,7 @@ Write `Table of contents` in h2 format (## in markdown) and place it where you w
For instance, if you want to place your table of contents just under the intro paragraph (like I usually do), you can do that in the following way. For instance, if you want to place your table of contents just under the intro paragraph (like I usually do), you can do that in the following way.
<!-- prettier-ignore-start -->
```md ```md
--- ---
# some frontmatter # some frontmatter
@@ -135,10 +137,12 @@ For instance, if you want to place your table of contents just under the intro p
Here are some recommendations, tips & ticks for creating new posts in AstroPaper blog theme. Here are some recommendations, tips & ticks for creating new posts in AstroPaper blog theme.
<!-- [!code ++] -->
## Table of contents ## Table of contents
<!-- the rest of the post --> <!-- the rest of the post -->
``` ```
<!-- prettier-ignore-end -->
## Headings ## Headings
@@ -43,15 +43,15 @@ In this section, you will find instructions on how to add support for LaTeX in y
// other configs // other configs
markdown: { markdown: {
remarkPlugins: [ remarkPlugins: [
remarkMath, // <- new plugin remarkMath, // [!code ++]
remarkToc, remarkToc,
[remarkCollapse, { test: "Table of contents" }], [remarkCollapse, { test: "Table of contents" }],
], ],
rehypePlugins: [rehypeKatex], // <- new plugin rehypePlugins: [rehypeKatex], // [!code ++]
shikiConfig: { shikiConfig: {
// For more themes, visit https://shiki.style/themes // For more themes, visit https://shiki.style/themes
themes: { light: "min-light", dark: "night-owl" }, themes: { light: "min-light", dark: "night-owl" },
wrap: true, wrap: false,
}, },
}, },
// other configs // other configs
@@ -71,6 +71,7 @@ In this section, you will find instructions on how to add support for LaTeX in y
<!-- others... --> <!-- others... -->
<script is:inline src="/toggle-theme.js"></script> <script is:inline src="/toggle-theme.js"></script>
<!-- [!code highlight:4] -->
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css"
@@ -90,6 +91,7 @@ In this section, you will find instructions on how to add support for LaTeX in y
/* other classes */ /* other classes */
/* Katex text color */ /* Katex text color */
/* [!code highlight:3] */
.prose .katex-display { .prose .katex-display {
@apply text-foreground; @apply text-foreground;
} }
@@ -77,18 +77,18 @@ You should now have a script tag that looks like this:
Simply add that to the source code of the site. Most likely, if you're using _AstroPaper_ and want to enable comments on posts, navigate to `src/layouts/PostDetails.astro` and paste it into the desired location where you want the comments to appear, perhaps underneath the `Share this post on:` buttons. Simply add that to the source code of the site. Most likely, if you're using _AstroPaper_ and want to enable comments on posts, navigate to `src/layouts/PostDetails.astro` and paste it into the desired location where you want the comments to appear, perhaps underneath the `Share this post on:` buttons.
```diff ```astro
<Layout {...layoutProps}>
<main>
<ShareLinks /> <ShareLinks />
</div>
+ <script src="https://giscus.app/client.js"
+ data-repo="[ENTER REPO HERE]"
+ data-repo-id="[ENTER REPO ID HERE]"
+ data-category="[ENTER CATEGORY NAME HERE]"
+ data-category-id="[ENTER CATEGORY ID HERE]"
+ ...
+ </script>
<!-- [!code ++:6] -->
<script
src="https://giscus.app/client.js"
data-repo="[ENTER REPO HERE]"
data-repo-id="[ENTER REPO ID HERE]"
data-category="[ENTER CATEGORY NAME HERE]"
data-category-id="[ENTER CATEGORY ID HERE]"></script>
</main> </main>
<Footer /> <Footer />
</Layout> </Layout>
@@ -189,19 +189,18 @@ Note that specifying a `theme` here will override the `lightTheme` and `darkThem
To complete the process, add the new Comments component to `src/layouts/PostDetails.astro` (replacing the `script` tag from the previous step). To complete the process, add the new Comments component to `src/layouts/PostDetails.astro` (replacing the `script` tag from the previous step).
```diff ```jsx
+ import Comments from "@/components/Comments"; // [!code ++:1]
import Comments from "@/components/Comments";
<ShareLinks /> <ShareLinks />
</div>
+ <Comments client:only="react" /> // [!code ++:1]
<Comments client:only="react" />
<hr class="my-6 border-dashed" /> <hr class="my-6 border-dashed" />
</main> <Footer />
<Footer />
</Layout>
``` ```
And that's it! And that's it!
+11 -13
View File
@@ -142,15 +142,15 @@ To allow Astro to compile the markdown and do its thing, it needs to know what i
To allow the key to be there with no value we need to edit line 10 to add the `.nullable()` function. To allow the key to be there with no value we need to edit line 10 to add the `.nullable()` function.
```typescript ```ts
const blog = defineCollection({ const blog = defineCollection({
type: "content", type: "content",
schema: ({ image }) => schema: ({ image }) =>
z.object({ z.object({
author: z.string().default(SITE.author), author: z.string().default(SITE.author),
pubDatetime: z.date(), pubDatetime: z.date(),
- modDatetime: z.date().optional(), modDatetime: z.date().optional(), // [!code --]
+ modDatetime: z.date().optional().nullable(), modDatetime: z.date().optional().nullable(), // [!code ++]
title: z.string(), title: z.string(),
featured: z.boolean().optional(), featured: z.boolean().optional(),
draft: z.boolean().optional(), draft: z.boolean().optional(),
@@ -167,8 +167,8 @@ To stop the IDE complaining in the blog engine files I have also done the follow
1. added `| null` to line 15 in `src/layouts/Layout.astro` so that it looks like 1. added `| null` to line 15 in `src/layouts/Layout.astro` so that it looks like
```typescript ```typescript
export interface Props { export interface Props {
title?: string; title?: string;
author?: string; author?: string;
description?: string; description?: string;
@@ -176,16 +176,14 @@ export interface Props {
canonicalURL?: string; canonicalURL?: string;
pubDatetime?: Date; pubDatetime?: Date;
modDatetime?: Date | null; modDatetime?: Date | null;
} }
``` ```
<!-- This needs to be 2 as it doesn't pick it up with the code block -->
2. added `| null` to line 5 in `src/components/Datetime.tsx` so that it looks like 2. added `| null` to line 5 in `src/components/Datetime.tsx` so that it looks like
```typescript ```typescript
interface DatetimesProps { interface DatetimesProps {
pubDatetime: string | Date; pubDatetime: string | Date;
modDatetime: string | Date | undefined | null; modDatetime: string | Date | undefined | null;
} }
``` ```
+4 -1
View File
@@ -103,7 +103,10 @@ const nextPost =
<Datetime {pubDatetime} {modDatetime} {timezone} size="lg" class="my-2" /> <Datetime {pubDatetime} {modDatetime} {timezone} size="lg" class="my-2" />
<EditPost class="max-sm:hidden" {hideEditPost} {post} /> <EditPost class="max-sm:hidden" {hideEditPost} {post} />
</div> </div>
<article id="article" class="mx-auto prose mt-8 max-w-app"> <article
id="article"
class="mx-auto prose mt-8 max-w-app prose-pre:bg-(--shiki-light-bg) dark:prose-pre:bg-(--shiki-dark-bg)"
>
<Content /> <Content />
</article> </article>
+37 -30
View File
@@ -1,35 +1,56 @@
@plugin '@tailwindcss/typography'; @plugin '@tailwindcss/typography';
@layer base { @layer base {
/* ===== Override default Tailwind Typography styles ===== */
.prose { .prose {
@apply prose-headings:!mb-3 prose-headings:!text-foreground prose-h3:italic prose-p:!text-foreground prose-a:!text-foreground prose-a:!decoration-dashed prose-a:underline-offset-8 hover:prose-a:text-accent prose-blockquote:!border-l-accent/50 prose-blockquote:opacity-80 prose-figcaption:!text-foreground prose-figcaption:opacity-70 prose-strong:!text-foreground prose-code:rounded prose-code:bg-muted/75 prose-code:p-1 prose-code:!text-foreground prose-code:before:!content-none prose-code:after:!content-none prose-ol:!text-foreground prose-ul:overflow-x-clip prose-ul:!text-foreground prose-li:marker:!text-accent prose-table:text-foreground prose-th:border prose-th:border-border prose-td:border prose-td:border-border prose-img:mx-auto prose-img:!my-2 prose-img:border-2 prose-img:border-border prose-hr:!border-border; @apply prose-headings:!mb-3 prose-headings:!text-foreground prose-h3:italic prose-p:!text-foreground prose-a:!text-foreground prose-a:!decoration-dashed prose-a:underline-offset-8 hover:prose-a:text-accent prose-blockquote:!border-l-accent/50 prose-blockquote:opacity-80 prose-figcaption:!text-foreground prose-figcaption:opacity-70 prose-strong:!text-foreground prose-code:rounded prose-code:bg-muted/75 prose-code:p-1 prose-code:!text-foreground prose-code:before:!content-none prose-code:after:!content-none prose-ol:!text-foreground prose-ul:overflow-x-clip prose-ul:!text-foreground prose-li:marker:!text-accent prose-table:text-foreground prose-th:border prose-th:border-border prose-td:border prose-td:border-border prose-img:mx-auto prose-img:!my-2 prose-img:border-2 prose-img:border-border prose-hr:!border-border;
}
.prose a { a {
@apply break-words hover:!text-accent; @apply break-words hover:!text-accent;
} }
.prose thead th:first-child,
details {
@apply inline-block cursor-pointer text-foreground select-none [&_p]:hidden [&_ul]:!my-0;
}
summary {
@apply focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-accent focus-visible:outline-dashed;
}
thead th:first-child,
tbody td:first-child, tbody td:first-child,
tfoot td:first-child { tfoot td:first-child {
padding-inline-start: 0.5714286em !important; padding-inline-start: 0.5714286em !important;
} }
.prose h2#table-of-contents {
@apply mb-2;
}
.prose details {
@apply inline-block cursor-pointer text-foreground select-none;
}
.prose summary {
@apply focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-accent focus-visible:outline-dashed;
}
.prose h2#table-of-contents + p {
@apply hidden;
} }
/* ===== Code Blocks & Syntax Highlighting ===== */ /* ===== Code Blocks & Syntax Highlighting ===== */
pre:has(code) { .astro-code {
@apply border border-border; @apply border bg-(--shiki-light-bg) text-(--shiki-light) outline-border [&_span]:text-(--shiki-light);
} }
html[data-theme="dark"] .astro-code {
@apply bg-(--shiki-dark-bg) text-(--shiki-dark) [&_span]:text-(--shiki-dark);
}
/* Styles for Shiki transformers */
/* https://shiki.style/packages/transformers */
.astro-code {
.line.diff.add {
@apply relative *:bg-green-500/20 before:absolute before:-left-3 before:text-green-500 before:content-['+'];
}
.line.diff.remove {
@apply relative *:bg-red-500/30 before:absolute before:-left-3 before:text-red-500 before:content-['-'];
}
.line.highlighted {
@apply *:!bg-slate-400/20;
}
.highlighted-word {
@apply rounded-sm border border-border px-0.5 py-px;
}
}
/* Break words in code and blockqoute */
.prose code, .prose code,
.prose blockquote { .prose blockquote {
@apply break-words; @apply break-words;
@@ -39,18 +60,4 @@
/* add line breaks whenever necessary for codes under table */ /* add line breaks whenever necessary for codes under table */
@apply break-all sm:break-normal; @apply break-all sm:break-normal;
} }
pre > code {
white-space: pre;
}
/* Apply Dark Theme (if multi-theme specified) */
html[data-theme="dark"] pre:has(code),
html[data-theme="dark"] pre:has(code) span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
} }