Skip to content

Commit 164a7ec

Browse files
authored
feat: implement shiki for code formatting (#284)
1 parent 53c89e6 commit 164a7ec

File tree

7 files changed

+305
-46
lines changed

7 files changed

+305
-46
lines changed

libs/blog-bff/articles/api/src/lib/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ app.get('/:slug', async (c) => {
4545
const client = new WpPosts(c.var.createWPClient({ namespace: 'al/v1' }));
4646

4747
const result = await client.getBySlug(slug);
48+
4849
return c.json(toArticle(result.data));
4950
});
5051

libs/blog-bff/articles/api/src/lib/mappers.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111

1212
import { WPPostDetailsDto, WPPostDto } from './dtos';
1313
import {
14-
crayonCodeRewriter,
1514
modifyImages,
1615
modifyLinks,
1716
removeEmptyParagraphs,
@@ -71,6 +70,7 @@ export const toArticle = (dto?: WPPostDetailsDto): Article => {
7170
allowedClasses: {
7271
blockquote: ['twitter-tweet'],
7372
pre: ['lang:*'],
73+
code: ['language-*'],
7474
div: ['crayon-line', 'crayon-syntax'],
7575
},
7676
transformTags: {
@@ -81,7 +81,6 @@ export const toArticle = (dto?: WPPostDetailsDto): Article => {
8181

8282
rewriteHTML(
8383
wpCodeRewriter,
84-
crayonCodeRewriter,
8584
removeEmptyParagraphs,
8685
modifyLinks,
8786
modifyImages,

libs/blog-bff/articles/api/src/lib/utils.ts

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
1+
import { createHighlighterCore } from 'shiki/core';
2+
import { loadWasm } from 'shiki/engine/oniguruma';
13
import type { CheerioAPI } from 'cheerio';
2-
import hljs from 'highlight.js';
34

4-
const DEFAULT_LANGUAGE_SUBSET = ['typescript', 'html', 'css', 'scss', 'json'];
5+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
6+
// @ts-ignore
7+
await loadWasm(import('shiki/dist/onig.wasm'));
8+
9+
const highlighter = await createHighlighterCore({
10+
themes: [
11+
import('shiki/themes/github-dark.mjs'), // dark mode
12+
import('shiki/themes/github-light.mjs'), // light mode
13+
],
14+
langs: [
15+
import('shiki/langs/shell.mjs'),
16+
import('shiki/langs/bash.mjs'),
17+
import('shiki/langs/json.mjs'),
18+
import('shiki/langs/typescript.mjs'),
19+
import('shiki/langs/angular-ts.mjs'),
20+
import('shiki/langs/angular-html.mjs'),
21+
],
22+
});
23+
24+
const themes = highlighter.getLoadedThemes();
525

626
type RewriteAdapter = ($: CheerioAPI) => void;
727

@@ -18,7 +38,7 @@ export const rewriteHTML = (...adapters: RewriteAdapter[]) => {
1838
* @param $
1939
*/
2040
export const wpCodeRewriter: RewriteAdapter = ($) => {
21-
$('pre').each((index, element) => {
41+
$('pre').each((_, element) => {
2242
const code = $(element).text();
2343

2444
// Check if the content is already wrapped in a <code> block
@@ -29,46 +49,41 @@ export const wpCodeRewriter: RewriteAdapter = ($) => {
2949
// Also add `hljs` class to make it apply hljs styling schema
3050
if (!hasCodeBlock) {
3151
$(element).html(`<code class="hljs">${code}</code>`);
32-
} else {
33-
$(element).children('code').addClass('hljs');
3452
}
3553

36-
// Detect the language and apply syntax highlighting
37-
const highlightedCode = hljs.highlightAuto(
38-
code,
39-
DEFAULT_LANGUAGE_SUBSET,
40-
).value;
54+
const classAttr = $(element).find('code').attr()['class'];
55+
const classes = classAttr.split(' ');
56+
const codeLanguageClass = classes.find((cl) =>
57+
/^language-[\w-]+$/.test(cl),
58+
);
4159

42-
// Replace the content of the <code> block with the highlighted code
43-
$(element).children('code').html(highlightedCode);
44-
});
45-
};
60+
let language: string;
4661

47-
/**
48-
* Rewrites code blocks generated by `crayon` plugin and applies HLJS styling
49-
* @param $
50-
*/
51-
export const crayonCodeRewriter: RewriteAdapter = ($) => {
52-
$('.crayon-syntax').each((_, element) => {
53-
const $element = $(element);
54-
let code = '';
62+
if (codeLanguageClass) {
63+
language = codeLanguageClass.replace('language-', '');
64+
} else {
65+
language = 'angular-ts';
66+
}
5567

56-
// Extract code from Crayon lines
57-
$element.find('.crayon-line').each((_, line) => {
58-
code += $(line).text() + '\n';
59-
});
68+
if (language === 'typescript' || language === 'ts') {
69+
language = 'angular-ts';
70+
}
6071

61-
// Detect the language and apply syntax highlighting
62-
const highlightedCode = hljs.highlightAuto(
63-
code,
64-
DEFAULT_LANGUAGE_SUBSET,
65-
).value;
72+
if (language === 'html') {
73+
language = 'angular-html';
74+
}
6675

67-
// Create a new <pre><code> element with the highlighted code
68-
const preCodeBlock = `<pre><code class="hljs">${highlightedCode}</code></pre>`;
76+
const highlightedCode = highlighter.codeToHtml(code, {
77+
theme: highlighter.getLoadedThemes()[0],
78+
themes: {
79+
dark: themes[0],
80+
light: themes[1],
81+
},
82+
lang: language,
83+
});
6984

70-
// Replace the entire crayon-syntax element with the new preCodeBlock
71-
$element.replaceWith(preCodeBlock);
85+
// Replace the content of the <code> block with the highlighted code
86+
$(element).replaceWith(highlightedCode);
7287
});
7388
};
7489

libs/blog-bff/articles/api/tsconfig.lib.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"extends": "./tsconfig.json",
33
"compilerOptions": {
4-
"module": "commonjs",
4+
"target": "ESNext",
5+
"module": "ESNext",
56
"outDir": "../../../../dist/out-tsc",
67
"declaration": true,
78
"types": ["node", "@cloudflare/workers-types"],

libs/blog/articles/ui-article-content/src/lib/article-content/article-content.component.scss

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1-
@use 'highlight.js/styles/github-dark.min.css';
2-
31
@mixin heading-styles {
42
padding-bottom: 0.3rem;
53
margin-bottom: 1.1rem;
64
border-bottom: 1px solid #5c93bb2b;
75
margin-top: 1.6rem;
86
}
97

8+
@media (prefers-color-scheme: dark) {
9+
.shiki,
10+
.shiki span {
11+
color: var(--shiki-dark) !important;
12+
background-color: var(--shiki-dark-bg) !important;
13+
font-style: var(--shiki-dark-font-style) !important;
14+
font-weight: var(--shiki-dark-font-weight) !important;
15+
text-decoration: var(--shiki-dark-text-decoration) !important;
16+
}
17+
}
18+
1019
.blog-article-content {
20+
pre.shiki {
21+
@apply mb-4 overflow-x-auto rounded-2xl p-4;
22+
}
23+
1124
h1 {
1225
@apply text-3xl;
1326
@include heading-styles;
@@ -37,16 +50,13 @@
3750
@apply al-link;
3851
}
3952

40-
code.hljs {
41-
@apply my-4 rounded-2xl p-6;
42-
}
43-
44-
code:not(.hljs) {
53+
code:not(pre code) {
4554
padding: 0.2em 0.4em;
46-
background: #215aa012;
55+
background: #2e2f3b;
4756
border-radius: 4px;
4857
font-size: 0.85rem;
4958
font-weight: 600;
59+
@apply rounded-md;
5060
}
5161

5262
strong {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"prettier-plugin-organize-attributes": "^1.0.0",
5555
"rxjs": "~7.8.1",
5656
"sanitize-html": "^2.13.0",
57+
"shiki": "^1.22.2",
5758
"stylelint": "^16.3.1",
5859
"tailwind-merge": "^2.3.0",
5960
"tslib": "^2.6.1",

0 commit comments

Comments
 (0)