From 62540ca364405657d88859a691d7779b7fc80a9a Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Mon, 24 Jul 2023 04:05:14 -0700 Subject: [PATCH 1/3] chore: make rehype and remark plugins more type strict --- .../shared-post-preview-png.ts | 11 +-- src/utils/get-all-posts.ts | 16 +++- .../markdown/file-tree/rehype-file-tree.ts | 18 ++-- src/utils/markdown/rehype-astro-image-md.ts | 84 ++++++++++--------- src/utils/markdown/rehype-excerpt.ts | 7 +- .../rehype-unicorn-get-suggested-posts.ts | 11 +-- .../markdown/rehype-unicorn-populate-post.ts | 12 ++- src/utils/markdown/rehype-word-count.ts | 7 +- src/utils/markdown/types.ts | 8 ++ 9 files changed, 97 insertions(+), 77 deletions(-) create mode 100644 src/utils/markdown/types.ts diff --git a/build-scripts/social-previews/shared-post-preview-png.ts b/build-scripts/social-previews/shared-post-preview-png.ts index 1542b826..aeb957eb 100644 --- a/build-scripts/social-previews/shared-post-preview-png.ts +++ b/build-scripts/social-previews/shared-post-preview-png.ts @@ -11,6 +11,7 @@ import remarkToRehype from "remark-rehype"; import { findAllAfter } from "unist-util-find-all-after"; import rehypeStringify from "rehype-stringify"; import { Layout, PAGE_HEIGHT, PAGE_WIDTH } from "./base"; +import { Literal } from "unist"; // https://github.com/shikijs/twoslash/issues/147 const remarkTwoslash = @@ -27,7 +28,7 @@ const unifiedChain = () => { // join code parts into one element const value = nodes - .map((node) => (node as any).value) + .map((node) => (node as Literal).value) .join("\n") .trim() + "\n" + @@ -40,7 +41,7 @@ const unifiedChain = () => { children: [ { type: "code", - lang: (nodes[0] as any)?.lang || "javascript", + lang: (nodes[0] as { lang?: string })?.lang || "javascript", value, }, ], @@ -61,13 +62,13 @@ const shikiSCSS = fs.readFileSync("src/styles/shiki.scss", "utf8"); export const renderPostPreviewToString = async ( layout: Layout, - post: ExtendedPostInfo + post: ExtendedPostInfo, ) => { const authorImageMap = Object.fromEntries( post.authorsMeta.map((author) => [ author.id, readFileAsBase64(author.profileImgMeta.absoluteFSPath), - ]) + ]), ); const postHtml = await markdownToHtml(post.contentMeta); @@ -100,7 +101,7 @@ export const renderPostPreviewToString = async ( width: PAGE_WIDTH, height: PAGE_HEIGHT, authorImageMap, - }) + }), )} diff --git a/src/utils/get-all-posts.ts b/src/utils/get-all-posts.ts index 3a86e0f7..12988c4f 100644 --- a/src/utils/get-all-posts.ts +++ b/src/utils/get-all-posts.ts @@ -8,6 +8,12 @@ import { rehypeUnicornPopulatePost } from "./markdown/rehype-unicorn-populate-po import { postsDirectory, posts } from "./data"; import { Languages, ExtendedPostInfo } from "types/index"; import * as path from "path"; +import { Plugin } from "unified"; +import { AstroVFile } from "utils/markdown/types"; + +type UnwrapPlugin = ( + ...params: Parameters> +) => Exclude>, void>; const getIndexPath = (lang: Languages) => { const indexPath = lang !== "en" ? `index.${lang}.md` : `index.md`; @@ -16,7 +22,7 @@ const getIndexPath = (lang: Languages) => { export function getExtendedPost( slug: string, - lang: Languages + lang: Languages, ): ExtendedPostInfo { const indexFile = path.resolve(postsDirectory, slug, getIndexPath(lang)); const file = { @@ -26,12 +32,14 @@ export function getExtendedPost( frontmatter: {}, }, }, - }; + } as AstroVFile; - (rehypeUnicornPopulatePost as any)()(undefined, file); + ( + rehypeUnicornPopulatePost as UnwrapPlugin + )()(undefined, file); return { - ...((file.data.astro.frontmatter as any) || {}).frontmatterBackup, + ...(file.data.astro.frontmatter || {}).frontmatterBackup, ...file.data.astro.frontmatter, }; } diff --git a/src/utils/markdown/file-tree/rehype-file-tree.ts b/src/utils/markdown/file-tree/rehype-file-tree.ts index 8865e362..15085766 100644 --- a/src/utils/markdown/file-tree/rehype-file-tree.ts +++ b/src/utils/markdown/file-tree/rehype-file-tree.ts @@ -38,8 +38,8 @@ export const rehypeFileTree = () => { function replaceFiletreeNodes(nodes: Node[]) { const items: Array = []; - const isNodeElement = (node: any): node is Element => - typeof node === "object" && node.type === "element"; + const isNodeElement = (node: unknown): node is Element => + typeof node === "object" && node["type"] === "element"; function traverseUl(listNode: Element, listItems: typeof items) { if (listNode.children.length === 0) return; @@ -53,7 +53,7 @@ export const rehypeFileTree = () => { (child) => child.type === "comment" || child.type !== "text" || - !/^\n+$/.test(child.value) + !/^\n+$/.test(child.value), ); const [firstChild, ...otherChildren] = listItem.children; @@ -81,7 +81,7 @@ export const rehypeFileTree = () => { comment.push(fragments.join(" ")); } const subTreeIndex = otherChildren.findIndex( - (child) => child.type === "element" && child.tagName === "ul" + (child) => child.type === "element" && child.tagName === "ul", ); const commentNodes = subTreeIndex > -1 @@ -89,7 +89,7 @@ export const rehypeFileTree = () => { : [...otherChildren]; otherChildren.splice( 0, - subTreeIndex > -1 ? subTreeIndex : otherChildren.length + subTreeIndex > -1 ? subTreeIndex : otherChildren.length, ); comment.push(...commentNodes); @@ -97,7 +97,7 @@ export const rehypeFileTree = () => { // Decide a node is a directory if it ends in a `/` or contains another list. const directoryNode = otherChildren.find( - (child) => child.type === "element" && child.tagName === "ul" + (child) => child.type === "element" && child.tagName === "ul", ); const isDirectory = @@ -154,7 +154,7 @@ export const rehypeFileTree = () => { } const list = nodes.find( - (node) => isNodeElement(node) && node.tagName === "ul" + (node) => isNodeElement(node) && node.tagName === "ul", ) as Element; if (!list) throw "No list found in filetree"; @@ -168,13 +168,13 @@ export const rehypeFileTree = () => { tree, { type: "raw", value: "" } as never, { type: "raw", value: "" } as never, - replaceFiletreeNodes + replaceFiletreeNodes, ); replaceAllBetween( tree, { type: "comment", value: " filetree:start " } as never, { type: "comment", value: " filetree:end " } as never, - replaceFiletreeNodes + replaceFiletreeNodes, ); }; }; diff --git a/src/utils/markdown/rehype-astro-image-md.ts b/src/utils/markdown/rehype-astro-image-md.ts index cabf508d..9a4293ac 100644 --- a/src/utils/markdown/rehype-astro-image-md.ts +++ b/src/utils/markdown/rehype-astro-image-md.ts @@ -1,4 +1,4 @@ -import { Root } from "hast"; +import { Root, Element } from "hast"; import { Plugin } from "unified"; import { h } from "hastscript"; @@ -22,8 +22,8 @@ const MAX_HEIGHT = 768; export const rehypeAstroImageMd: Plugin<[], Root> = () => { return async (tree, file) => { - const imgNodes: any[] = []; - visit(tree, (node: any) => { + const imgNodes: Element[] = []; + visit(tree, (node: Element) => { if (node.tagName === "img") { imgNodes.push(node); } @@ -39,17 +39,16 @@ export const rehypeAstroImageMd: Plugin<[], Root> = () => { const filePathDir = path.resolve( __dirname, `../../../public/content/${parentFolder}`, - slug + slug, ); const rootFileDir = path.resolve(__dirname, `../../../public/`); + const nodeSrc = node.properties.src as string; + const nodeAlt = node.properties.alt as string; + // TODO: How should remote images be handled? - const dimensions = getImageSize( - node.properties.src, - filePathDir, - rootFileDir - ) || { + const dimensions = getImageSize(nodeSrc, filePathDir, rootFileDir) || { height: undefined, width: undefined, }; @@ -58,12 +57,12 @@ export const rehypeAstroImageMd: Plugin<[], Root> = () => { if (!dimensions.height || !dimensions.width) return; let src: string; - if (node.properties.src.startsWith("/")) { - src = node.properties.src; + if (nodeSrc.startsWith("/")) { + src = nodeSrc; } else { src = getFullRelativePath( `/content/${parentFolder}/${slug}/`, - node.properties.src + nodeSrc, ); } @@ -90,7 +89,7 @@ export const rehypeAstroImageMd: Plugin<[], Root> = () => { widths: [dimensions.width], formats: ["avif", "webp", "png"], aspectRatio: imgRatioWidth, - alt: node.properties.alt || "", + alt: nodeAlt || "", }); let pngSource = { @@ -110,35 +109,38 @@ export const rehypeAstroImageMd: Plugin<[], Root> = () => { widths: [originalDimensions.width], formats: ["png"], aspectRatio: imgRatioWidth, - alt: node.properties.alt || "", + alt: nodeAlt || "", }); - pngSource = originalPictureResult.sources.reduce((prev, source) => { - const largestSrc = getLargestSourceSetSrc(source.srcset); - // select first option - if (!prev) return largestSrc; - // SVG first - if (prev.src.endsWith(".svg")) return prev; - if (largestSrc.src.endsWith(".svg")) return prev; - // Prefer `w` - if (prev.sizeType === "w" && largestSrc.sizeType === "x") - return prev; - if (largestSrc.sizeType === "w" && prev.sizeType === "x") - return largestSrc; - // Get the bigger of the two - if (largestSrc.size > prev.size) return largestSrc; - // Prefer PNG and JPG - if (largestSrc.size === prev.size) { - if ( - prev.src.endsWith(".webp") && - (largestSrc.src.endsWith(".png") || - largestSrc.src.endsWith(".jpg") || - largestSrc.src.endsWith(".jpeg")) - ) + pngSource = originalPictureResult.sources.reduce( + (prev, source) => { + const largestSrc = getLargestSourceSetSrc(source.srcset); + // select first option + if (!prev) return largestSrc; + // SVG first + if (prev.src.endsWith(".svg")) return prev; + if (largestSrc.src.endsWith(".svg")) return prev; + // Prefer `w` + if (prev.sizeType === "w" && largestSrc.sizeType === "x") + return prev; + if (largestSrc.sizeType === "w" && prev.sizeType === "x") return largestSrc; - } - return prev; - }, null as ReturnType); + // Get the bigger of the two + if (largestSrc.size > prev.size) return largestSrc; + // Prefer PNG and JPG + if (largestSrc.size === prev.size) { + if ( + prev.src.endsWith(".webp") && + (largestSrc.src.endsWith(".png") || + largestSrc.src.endsWith(".jpg") || + largestSrc.src.endsWith(".jpeg")) + ) + return largestSrc; + } + return prev; + }, + null as ReturnType, + ); } const sources = pictureResult.sources.map((attrs) => { @@ -156,9 +158,9 @@ export const rehypeAstroImageMd: Plugin<[], Root> = () => { "data-zoom-src": pngSource.src, style: `width: ${pngSource.size}px`, }), - ]) + ]), ); - }) + }), ); }; }; diff --git a/src/utils/markdown/rehype-excerpt.ts b/src/utils/markdown/rehype-excerpt.ts index d2140ba7..c8b9617e 100644 --- a/src/utils/markdown/rehype-excerpt.ts +++ b/src/utils/markdown/rehype-excerpt.ts @@ -1,6 +1,7 @@ import { Root } from "hast"; import { Plugin } from "unified"; import { visit } from "unist-util-visit"; +import { AstroVFile } from "utils/markdown/types"; interface RehypeExcerptProps { maxLength: number; @@ -9,11 +10,11 @@ interface RehypeExcerptProps { export const rehypeExcerpt: Plugin<[RehypeExcerptProps | never], Root> = ({ maxLength, }) => { - return (tree, file) => { + return (tree, file: AstroVFile) => { const getFileExcerpt = () => - (file?.data?.astro as any)?.frontmatter?.excerpt as string; + file?.data?.astro?.frontmatter?.excerpt as string; const setFileExcerpt = (val) => { - (file.data.astro as any).frontmatter.excerpt = val; + file.data.astro.frontmatter.excerpt = val; }; if (!getFileExcerpt()) { setFileExcerpt(""); diff --git a/src/utils/markdown/rehype-unicorn-get-suggested-posts.ts b/src/utils/markdown/rehype-unicorn-get-suggested-posts.ts index 05f28388..7413aee4 100644 --- a/src/utils/markdown/rehype-unicorn-get-suggested-posts.ts +++ b/src/utils/markdown/rehype-unicorn-get-suggested-posts.ts @@ -2,6 +2,7 @@ import { Root } from "hast"; import { Plugin } from "unified"; import { getSuggestedArticles } from "../get-suggested-articles"; import path from "path"; +import { AstroVFile } from "utils/markdown/types"; interface RehypeUnicornGetSuggestedPostsProps {} @@ -9,20 +10,20 @@ export const rehypeUnicornGetSuggestedPosts: Plugin< [RehypeUnicornGetSuggestedPostsProps | never], Root > = () => { - return (_, file) => { + return (_, file: AstroVFile) => { const splitFilePath = path.dirname(file.path).split(path.sep); // "collections" | "blog" const parentFolder = splitFilePath.at(-2); if (parentFolder === "collections") return; - function setData(key: string, val: any) { - (file.data.astro as any).frontmatter[key] = val; + function setData(key: string, val: unknown) { + file.data.astro.frontmatter[key] = val; } const post = { - ...(file.data.astro as any).frontmatter.frontmatterBackup, - ...(file.data.astro as any).frontmatter, + ...file.data.astro.frontmatter.frontmatterBackup, + ...file.data.astro.frontmatter, }; const suggestedArticles = getSuggestedArticles(post, post.locale); diff --git a/src/utils/markdown/rehype-unicorn-populate-post.ts b/src/utils/markdown/rehype-unicorn-populate-post.ts index 689694b0..5fd2642d 100644 --- a/src/utils/markdown/rehype-unicorn-populate-post.ts +++ b/src/utils/markdown/rehype-unicorn-populate-post.ts @@ -5,14 +5,12 @@ import { readFileSync } from "fs"; import * as path from "path"; import { collections, posts } from "../data"; import { getLanguageFromFilename } from ".."; +import { AstroVFile } from "utils/markdown/types"; interface RehypeUnicornPopulatePostProps {} -export const rehypeUnicornPopulatePost: Plugin< - [RehypeUnicornPopulatePostProps | never], - Root -> = () => { - return (_, file) => { +export const rehypeUnicornPopulatePost = (() => { + return (_, file: AstroVFile) => { const fileContents = readFileSync(file.path, "utf8"); const { data: frontmatter, content } = matter(fileContents); @@ -40,7 +38,7 @@ export const rehypeUnicornPopulatePost: Plugin< } // Write the data to Astro's frontmatter - Object.assign((file.data.astro as any).frontmatter, { + Object.assign(file.data.astro.frontmatter, { slug, locale, ...data, @@ -48,4 +46,4 @@ export const rehypeUnicornPopulatePost: Plugin< contentMeta: content, }); }; -}; +}) satisfies Plugin<[RehypeUnicornPopulatePostProps | never], Root>; diff --git a/src/utils/markdown/rehype-word-count.ts b/src/utils/markdown/rehype-word-count.ts index a62b2c1d..94284fc7 100644 --- a/src/utils/markdown/rehype-word-count.ts +++ b/src/utils/markdown/rehype-word-count.ts @@ -28,6 +28,7 @@ import { visit } from "unist-util-visit"; import { unified } from "unified"; import english from "retext-english"; import rehypeRetext from "rehype-retext"; +import { AstroVFile } from "utils/markdown/types"; interface RemarkCountProps {} @@ -67,7 +68,7 @@ export type WordCounts = { }; export const rehypeWordCount: Plugin<[RemarkCountProps | never], Root> = () => { - return async (tree, file) => { + return async (tree, file: AstroVFile) => { const counts = {} as WordCounts; /** @@ -80,7 +81,7 @@ export const rehypeWordCount: Plugin<[RemarkCountProps | never], Root> = () => { * Plus, there's weird syntax parsing issues. */ if (file.path.includes(".mdx")) { - (file.data.astro as any).frontmatter.wordCount = 0; + file.data.astro.frontmatter.wordCount = 0; return; } @@ -88,7 +89,7 @@ export const rehypeWordCount: Plugin<[RemarkCountProps | never], Root> = () => { .use(rehypeRetext, unified().use(english).use(count(counts))) .run(tree); - (file.data.astro as any).frontmatter.wordCount = + file.data.astro.frontmatter.wordCount = (counts.InlineCodeWords || 0) + (counts.WordNode || 0); }; }; diff --git a/src/utils/markdown/types.ts b/src/utils/markdown/types.ts new file mode 100644 index 00000000..8c8c2126 --- /dev/null +++ b/src/utils/markdown/types.ts @@ -0,0 +1,8 @@ +import { VFile } from "vfile"; +import { MarkdownAstroData } from "@astrojs/markdown-remark"; + +export type AstroVFile = VFile & { + data: { + astro: MarkdownAstroData; + }; +}; From 7f52e32bc7d04f2ebdef033bbb6a712d6dc6a58d Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Mon, 24 Jul 2023 04:00:44 -0700 Subject: [PATCH 2/3] chore: delete unused types --- src/types/ffg.d.ts | 9 --------- src/types/modules/gatsby-remark-embedder.d.ts | 6 ------ 2 files changed, 15 deletions(-) delete mode 100644 src/types/ffg.d.ts delete mode 100644 src/types/modules/gatsby-remark-embedder.d.ts diff --git a/src/types/ffg.d.ts b/src/types/ffg.d.ts deleted file mode 100644 index d71ad9dc..00000000 --- a/src/types/ffg.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export {}; - -declare global { - interface Window { - rafThrottle any>( - callback: T - ): (...args: any[]) => void; - } -} diff --git a/src/types/modules/gatsby-remark-embedder.d.ts b/src/types/modules/gatsby-remark-embedder.d.ts deleted file mode 100644 index 27929bdd..00000000 --- a/src/types/modules/gatsby-remark-embedder.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare module "gatsby-remark-embedder/dist/transformers/Twitch.js" { - import { Transformer } from "@remark-embedder/core"; - - const transformer: Transformer; - export = transformer; -} From 40bcd25e13a6d7a42d7a99399348e28f7a3a5469 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Mon, 24 Jul 2023 04:04:08 -0700 Subject: [PATCH 3/3] chore: make disqus typings more strict --- src/types/disqus.d.ts | 7 +++++++ .../base/navigation/dark-light-button/theme-toggle.ts | 7 ++----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/types/disqus.d.ts diff --git a/src/types/disqus.d.ts b/src/types/disqus.d.ts new file mode 100644 index 00000000..6ffb9a1b --- /dev/null +++ b/src/types/disqus.d.ts @@ -0,0 +1,7 @@ +export {}; + +declare global { + interface Window { + reloadDisqus: () => void; + } +} diff --git a/src/views/base/navigation/dark-light-button/theme-toggle.ts b/src/views/base/navigation/dark-light-button/theme-toggle.ts index 811c31ea..2995d1a8 100644 --- a/src/views/base/navigation/dark-light-button/theme-toggle.ts +++ b/src/views/base/navigation/dark-light-button/theme-toggle.ts @@ -2,7 +2,7 @@ const COLOR_MODE_STORAGE_KEY = "currentTheme"; export const themeToggle = () => { const themeToggleBtn: HTMLElement = document.querySelector( - "#theme-toggle-button" + "#theme-toggle-button", ); if (!themeToggleBtn) return; const darkIconEl: HTMLElement = document.querySelector("#dark-icon"); @@ -16,10 +16,7 @@ export const themeToggle = () => { lightIconEl.style.display = "none"; darkIconEl.style.display = null; } - setTimeout( - () => (window as any).reloadDisqus && (window as any).reloadDisqus(), - 100 - ); + setTimeout(() => window.reloadDisqus && window.reloadDisqus(), 100); } // TODO: Migrate to `classList`