diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml index 0ad96cd..61a3c78 100644 --- a/.github/workflows/hugo.yaml +++ b/.github/workflows/hugo.yaml @@ -59,7 +59,17 @@ jobs: hugo \ --gc \ --minify \ - --baseURL "${{ steps.pages.outputs.base_url }}/" + --baseURL "${{ steps.pages.outputs.base_url }}/" + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: yarn + + - name: Install and run + run: yarn install && yarn run syntax + - name: Upload artifact uses: actions/upload-pages-artifact@v2 with: diff --git a/.gitignore b/.gitignore index 6650314..73f98ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .hugo_build.lock -.DS_Store \ No newline at end of file +.DS_Store +node_modules +public \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c30900e..4cb4dbb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "plaintext": false, "markdown": false, "scminput": false - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/README.md b/README.md index b8a44fa..01eb9f7 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,5 @@ This post would appear in 3 sections: tech, api and plugins. Which have their ow ### This blog It uses Hugo as a static site generator, it was chosen because it is is simple to install and run locally and shouldn't break over a very long time period (the Artsy blog [I used to write on](https://artsy.github.io/blog/2019/05/03/ortas-best-of/) once or twice a month was Jekyll and required a lot of custom work to get useful features but those eventually started slowing the system down and getting ruby set up is a pain). + +It has an optional post-completion hook which you can test via `yarn build && yarn shikify` which uses Shiki for code samples instead of Hugo's defaults. \ No newline at end of file diff --git a/content/posts/2024/06/23/shiki-hugo/index.md b/content/posts/2024/06/23/shiki-hugo/index.md new file mode 100644 index 0000000..b92d359 --- /dev/null +++ b/content/posts/2024/06/23/shiki-hugo/index.md @@ -0,0 +1,129 @@ ++++ +title = 'Using Shiki Syntax Highlighting in Hugo' +date = 2024-06-23T14:59:00+01:00 +authors = ["orta"] +tags = ["tech", "api", "graphql", "redwood"] +theme = "outlook-hayesy-beta" ++++ + +When I decided on Hugo for this blog, I knew I was gonna have to take a hit on something I felt was very important to me and my writing: fancy tools for syntax highlighting. + +I choose Hugo because it should be super easy for folks to contribute (no fancy Node tooling setup etc) - so I have Shiki being applied as an **optional** post build step. + +First up, we need to disable the current syntax highlighting for codefences by editing `hugo.toml`: + +```toml +[markup] + [markup.highlight] + codeFences = false +``` + +That means that the hugo process would make a codefenced block look like this HTML: + +```html +
[markup]
+ [markup.highlight]
+ codeFences = false
+
+```
+
+Which we can work with! So, the goal will be to edit the built files after Hugo has done its thing to switch the syntax highlighter.So, lets add the Node infra to do this, starting with adding some dependencies:
+
+```ts
+yarn add shiki @types/node node-html-parser
+```
+
+Then create a new script file:
+
+```ts
+import { createHighlighter, bundledLanguages } from "shiki"
+import { readdirSync, readFileSync, writeFileSync } from "fs"
+import { parse } from "node-html-parser"
+
+const posts = "public/posts"
+const files = await readdirSync(posts, { recursive: true, encoding: "utf-8" })
+const indexFiles = files.filter((file) => file.endsWith("index.html") && file.split("/").length > 3)
+
+const highlighter = await createHighlighter({
+ themes: ["nord"],
+ langs: Object.keys(bundledLanguages),
+})
+
+// Find all of the files in the posts directory which are index.html
+for (const file of indexFiles) {
+ // Grab the file, and parse it into a DOM
+ const content = readFileSync(posts + "/" + file, { encoding: "utf-8" })
+ const dom = parse(content)
+
+ // This isn't a particularly smart query implementation,
+ // so lets take the simple route and just grab all of the pre tags
+ const codeBlocks = dom.querySelectorAll("pre")
+
+ for (const codeBlock of codeBlocks) {
+ // We need to look for the code inside it
+ const codeChild = codeBlock.childNodes[0]
+ if (!codeChild) continue
+
+ const codeElement = parse(codeChild.toString())
+
+ // Pull out the language from the original code block
+ let lang = "text"
+ if (codeChild.rawText.startsWith(' file.endsWith("index.html") && file.split("/").length > 3)
+
+const highlighter = await createHighlighter({
+ themes: ["solarized-light"],
+ langs: Object.keys(bundledLanguages),
+})
+
+// Find all of the files in the posts directory which are index.html
+
+for (const file of indexFiles) {
+ // Grab the file, and parse it into a DOM
+ const content = readFileSync(posts + "/" + file, { encoding: "utf-8" })
+ const dom = parse(content)
+
+ // This isn't a particularly smart query implementation,
+ // so lets take the simple route and just grab all of the pre tags
+ const codeBlocks = dom.querySelectorAll("pre")
+
+ for (const codeBlock of codeBlocks) {
+ // We need to look for the code inside it
+ const codeChild = codeBlock.childNodes[0]
+ if (!codeChild) continue
+
+ const codeElement = parse(codeChild.toString())
+
+ // Pull out the language from the original code block
+ let lang = "text"
+ if (codeChild.rawText.startsWith('