From c42e58a54d7046982b7619943a0b10e3a77f167f Mon Sep 17 00:00:00 2001 From: Ashelyn Rose Date: Mon, 31 Jul 2023 15:48:23 -0600 Subject: [PATCH] Added code structure for posts including scripts --- app/[slug]/PageScript.tsx | 39 +++++++++++++++++++++++ app/[slug]/page.tsx | 6 ++-- app/page.tsx | 8 ++--- package-lock.json | 19 ++++++++++- package.json | 3 +- utils/post.ts | 66 ++++++++++++++++++++++++++++++++++----- 6 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 app/[slug]/PageScript.tsx diff --git a/app/[slug]/PageScript.tsx b/app/[slug]/PageScript.tsx new file mode 100644 index 0000000..d3d1572 --- /dev/null +++ b/app/[slug]/PageScript.tsx @@ -0,0 +1,39 @@ +'use client' + +import { useEffect, useRef } from "react" + +interface Module { + setup?: (wabt?: any) => Promise + cleanup?: () => Promise +} + +export default function PageScript({ script, wasm }: { script?: string, wasm?: Uint8Array }) { + const moduleRef = useRef(null) + + useEffect(() => { + if (!script) return undefined + + import(/* webpackIgnore: true */ script) + .then(scriptModule => { + if (!wasm) return [scriptModule] + + return WebAssembly.instantiate(Uint8Array.from(wasm)).then(({ instance }) => [scriptModule, instance]) + }) + .then(([scriptModule, wasmModule]) => { + moduleRef.current = scriptModule + + if (scriptModule.setup && typeof scriptModule.setup === 'function') + scriptModule.setup(wasmModule) + }) + + return () => { + const mod = moduleRef.current + + if (mod?.cleanup && typeof mod.cleanup === 'function') { + mod.cleanup() + } + } + }, []) + + return null +} diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index 19b14bc..d92a704 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -1,13 +1,14 @@ import { Metadata } from "next" import { Code } from "bright" -import { getPostSlugs, loadSinglePage } from "~/utils/post" +import { getPostSlugs, loadPageMetada, loadSinglePage } from "~/utils/post" import MarkdownToJSX from 'markdown-to-jsx' import styles from "~/styles/post.module.css" import { ReactElement } from "react" +import PageScript from "./PageScript" export async function generateMetadata({ params: { slug } }: { params: { slug: string } }): Promise { - const post = await loadSinglePage(slug) + const post = await loadPageMetada(slug) return { title: post?.title @@ -33,6 +34,7 @@ export default async function Post({ params: { slug } }: { params: { slug: strin

{post.subtitle}

{post.body} + {post.script && } ); diff --git a/app/page.tsx b/app/page.tsx index 8847280..0438ad4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next' import Link from 'next/link' -import { Post, getPostSlugs, loadSinglePage } from "~/utils/post" +import { PostMeta, getPostSlugs, loadPageMetada } from "~/utils/post" import styles from "~/styles/index.module.css" @@ -10,18 +10,18 @@ export const metadata: Metadata = { export default async function Index() { const slugs = await getPostSlugs() - const posts = await Promise.all(slugs.map(loadSinglePage)) + const posts = await Promise.all(slugs.map(loadPageMetada)) const sortedPosts = posts .filter((post) => post !== null) .filter((post) => !post?.unlisted) - .sort((a: Post, b: Post) => b.date.valueOf() - a.date.valueOf()) + .sort((a: PostMeta, b: PostMeta) => b.date.valueOf() - a.date.valueOf()) return ( <>

All posts

- {sortedPosts.map((post: Post, i: number) => ( + {sortedPosts.map((post: PostMeta, i: number) => ( <> {i >= 1 && sortedPosts[i - 1]?.date.getFullYear() != post.date.getFullYear() && (

{post.date.getFullYear()}

diff --git a/package-lock.json b/package-lock.json index 6dc3144..36edf00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "markdown-to-jsx": "^7.2.0", "next": "^13.4.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "wabt": "1.0.19" }, "devDependencies": { "@types/node": "20.1.0", @@ -521,6 +522,22 @@ "node": ">=12.20" } }, + "node_modules/wabt": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/wabt/-/wabt-1.0.19.tgz", + "integrity": "sha512-z/7XRZB8tPRW0XdE8HYbA95w2kjus5AwOHnJ5NT9PqzaNZ7z/zHnUdpNdB78TFMgWt+D/u01lg1jG6nWiFmyEw==", + "bin": { + "wasm-decompile": "bin/wasm-decompile", + "wasm-interp": "bin/wasm-interp", + "wasm-objdump": "bin/wasm-objdump", + "wasm-opcodecnt": "bin/wasm-opcodecnt", + "wasm-strip": "bin/wasm-strip", + "wasm-validate": "bin/wasm-validate", + "wasm2c": "bin/wasm2c", + "wasm2wat": "bin/wasm2wat", + "wat2wasm": "bin/wat2wasm" + } + }, "node_modules/zod": { "version": "3.21.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", diff --git a/package.json b/package.json index dcc0c89..f4da2c2 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "markdown-to-jsx": "^7.2.0", "next": "^13.4.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "wabt": "1.0.19" }, "devDependencies": { "@types/node": "20.1.0", diff --git a/utils/post.ts b/utils/post.ts index e2cbe79..864325b 100644 --- a/utils/post.ts +++ b/utils/post.ts @@ -1,17 +1,26 @@ import { promises as fs } from 'fs' import path from 'path' +import wabtModule from 'wabt' import frontmatter from 'front-matter' -export interface Post { +export interface PostMeta { slug: string, date: Date, title: string, subtitle?: string, - body: string, unlisted: boolean, + filePath: string, +} + +export interface Post extends Omit { + body: string, + script?: string, + wasm?: Uint8Array, } +type Attributes = { [key: string]: any } + const POST_FILE_PATTERN = /^(?[0-9]{4}-[0-9]{2}-[0-9]{2}(-[0-9]{1,2})?)_(?[^\.]+)\.md$/ export async function getPostSlugs(): Promise { @@ -26,7 +35,7 @@ export function getSlugFromFilePath(postPath: string): string | null { return regexResult.groups.slug } -export async function loadSinglePage(slug: string): Promise { +export async function loadPageMetada(slug: string): Promise { const postsDir = path.join(process.cwd(), 'posts') const postPaths = await fs.readdir(postsDir) const postMatch: RegExpExecArray | null | undefined = postPaths @@ -37,10 +46,9 @@ export async function loadSinglePage(slug: string): Promise { if (!postMatch) return null; const fileName = `${postMatch.groups?.date}_${postMatch.groups?.slug}.md` - const fileContents = (await fs.readFile(path.join(process.cwd(), 'posts', fileName))).toString('utf8') - const { attributes, body } = frontmatter(fileContents) - - type Attributes = { [key: string]: any } + const filePath = path.join(process.cwd(), 'posts', fileName) + const fileContents = (await fs.readFile(filePath)).toString('utf8') + const { attributes } = frontmatter(fileContents) const data: Attributes = attributes as Attributes if (!data.title) return null; @@ -49,11 +57,53 @@ export async function loadSinglePage(slug: string): Promise { const date = new Date(Date.UTC(parseInt(year), parseInt(month) - 1, parseInt(day))) return { - slug: postMatch.groups?.slug || '', date, + filePath, + slug: postMatch.groups?.slug || '', title: data.title, subtitle: data.subtitle, unlisted: typeof data.unlisted === 'boolean' ? data.unlisted : false, + } +} + +export async function loadSinglePage(slug: string): Promise { + const postMeta = await loadPageMetada(slug) + + if (!postMeta) return null + + const fileContents = (await fs.readFile(postMeta.filePath)).toString('utf8') + const { attributes, body } = frontmatter(fileContents) + const data: Attributes = attributes as Attributes + + if (data.script) { + const scriptPath = path.join(process.cwd(), 'public/scripts', data.script) + try { + await fs.readFile(scriptPath) + } catch { + throw new Error(`Could not find script ${scriptPath} referenced in post ${slug}`) + } finally { + data.script = `/scripts/${data.script}` + } + } + + if (data.wasm) { + const wabt = await wabtModule() + const wasmPath = path.join(process.cwd(), 'public/scripts', data.wasm) + + const fileName = data.wasm.replace(/^.*\//, '') + const wasmText = (await fs.readFile(wasmPath)) + const wasmIntermed = wabt.parseWat(fileName, wasmText) + const { buffer: wasmBinary } = wasmIntermed.toBinary({ write_debug_names: true }) + + data.wasm = wasmBinary + } + + const { filePath, ...otherMeta } = postMeta + + return { + ...otherMeta, + script: data.script, + wasm: data.wasm, body } }