summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNantha Sorubakanthan <nantha@mielota.com>2025-11-03 19:58:32 +0100
committerNantha Sorubakanthan <nantha@mielota.com>2025-11-03 19:58:32 +0100
commit285b2f67e967286019f0f29d1f0921134347a586 (patch)
tree77ba381c9a9bb71054c61801edd7b2cfb09f5788
parentf32c2e84104dbcae287c7563c835d2f20c8393f1 (diff)
Syntax highlighting from javascript script
-rw-r--r--.gitignore2
-rw-r--r--package.json13
-rw-r--r--scripts/highlight.mjs57
3 files changed, 72 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index c4b387a..191d1a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
.hugo_build.lock
public/
+node_modules/
+package-lock.json
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0d3ac30
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "scripts": {
+ "build": "hugo && npm run highlight",
+ "highlight": "node scripts/highlight.mjs"
+ },
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domutils": "^3.2.2",
+ "glob": "^11.0.3",
+ "htmlparser2": "^10.0.0",
+ "shiki": "^3.14.0"
+ }
+}
diff --git a/scripts/highlight.mjs b/scripts/highlight.mjs
new file mode 100644
index 0000000..2870866
--- /dev/null
+++ b/scripts/highlight.mjs
@@ -0,0 +1,57 @@
+import { createHighlighter, bundledLanguages } from 'shiki';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { readFile, writeFile } from 'node:fs/promises';
+import { glob } from 'glob';
+import { parseDocument } from 'htmlparser2';
+import { findAll, findOne, textContent, replaceElement } from 'domutils';
+import render from 'dom-serializer';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+main();
+
+async function main() {
+ const LIGHT_THEME = 'github-dark';
+ const DARK_THEME = LIGHT_THEME;
+
+ const start = Date.now();
+
+ const highlighter = await createHighlighter({
+ themes: [LIGHT_THEME, DARK_THEME],
+ langs: Object.keys(bundledLanguages),
+ });
+
+ const files = await glob(join(__dirname, '../public/**/**/index.html'));
+ await Promise.all(
+ files.map((filepath) => highlightFile(highlighter, filepath, { lightTheme: LIGHT_THEME, darkTheme: DARK_THEME }))
+ );
+
+ console.log(`Highlighted ${files.length} files in ${Math.round(Date.now() - start)} ms`);
+}
+
+async function highlightFile(highlighter, filepath, { lightTheme, darkTheme }) {
+ const contents = await readFile(filepath, { encoding: 'utf-8' });
+ await writeFile(filepath, highlightHtmlContent(highlighter, contents, { lightTheme, darkTheme }));
+}
+
+function highlightHtmlContent(highlighter, htmlContent, { lightTheme, darkTheme }) {
+ const doc = parseDocument(htmlContent);
+ for (const preNode of findAll((e) => e.name === 'pre', doc.children)) {
+ const codeNode = findOne((e) => e.name === 'code', preNode.children);
+ if (!codeNode) continue;
+
+ const lang = codeNode.attribs['class']?.replace(/^language-/, '') ?? 'text';
+ const code = textContent(codeNode);
+ const highlighted = highlighter.codeToHtml(code, {
+ lang,
+ themes: { light: lightTheme, dark: darkTheme },
+ });
+
+ const highlightedPreNode = parseDocument(highlighted).children[0];
+ replaceElement(preNode, highlightedPreNode);
+ }
+
+ return render(doc);
+}
+