From 65416fcc1f1d38df77f04932df8929a96a3cb33f Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Sun, 9 Jan 2022 13:45:38 -0500 Subject: [PATCH] next-mdx-remote -> mdx-bundler (#729) --- components/embeds/Gist.tsx | 3 + components/embeds/Octocat.tsx | 28 + components/embeds/Tweet.tsx | 13 + components/embeds/Video.tsx | 9 + components/loading/Loading.tsx | 4 +- components/mdxComponents.tsx | 53 +- components/notes/Comments.module.css | 5 - components/notes/Comments.tsx | 18 +- components/projects/RepoCard.tsx | 34 +- components/video/Video.module.css | 10 - components/video/Video.tsx | 14 - lib/parse-notes.ts | 69 +- notes/bernie-sanders-bern-app-data.mdx | 4 +- notes/cloudflare-dns-archive-is-blocked.mdx | 4 +- notes/coronavirus-open-source.mdx | 51 +- notes/css-waving-hand-emoji.mdx | 2 +- notes/dark-mode.mdx | 6 +- notes/dropping-dropbox.mdx | 9 +- notes/github-actions.mdx | 8 +- notes/github-rename-master.mdx | 4 +- notes/how-to-backup-linux-server.mdx | 4 +- notes/millenial-with-hillary-clinton.mdx | 20 +- notes/my-first-code.mdx | 2 +- notes/no-homo-still-raps-motto.mdx | 7 +- notes/presidential-candidates-404-pages.mdx | 8 +- notes/security-headers-cloudflare-workers.mdx | 7 +- notes/shodan-search-queries.mdx | 2 +- notes/y2k-sandbox.mdx | 2 +- package.json | 7 +- pages/birthday.tsx | 2 +- pages/hillary.tsx | 2 +- pages/leo.tsx | 2 +- pages/notes/[slug].tsx | 126 +- styles/index.css | 1 + yarn.lock | 1905 ++++++++++++----- 35 files changed, 1624 insertions(+), 821 deletions(-) create mode 100644 components/embeds/Gist.tsx create mode 100644 components/embeds/Octocat.tsx create mode 100644 components/embeds/Tweet.tsx create mode 100644 components/embeds/Video.tsx delete mode 100644 components/notes/Comments.module.css delete mode 100644 components/video/Video.module.css delete mode 100644 components/video/Video.tsx diff --git a/components/embeds/Gist.tsx b/components/embeds/Gist.tsx new file mode 100644 index 00000000..2a88f310 --- /dev/null +++ b/components/embeds/Gist.tsx @@ -0,0 +1,3 @@ +import Gist from "react-gist"; + +export default Gist; diff --git a/components/embeds/Octocat.tsx b/components/embeds/Octocat.tsx new file mode 100644 index 00000000..21155b76 --- /dev/null +++ b/components/embeds/Octocat.tsx @@ -0,0 +1,28 @@ +const Octocat = (props: { repo: string }) => { + return ( + + + + ); +}; + +export default Octocat; diff --git a/components/embeds/Tweet.tsx b/components/embeds/Tweet.tsx new file mode 100644 index 00000000..112680f5 --- /dev/null +++ b/components/embeds/Tweet.tsx @@ -0,0 +1,13 @@ +import TweetEmbed from "react-tweet-embed"; + +const Tweet = (props: { id: string }) => ( + +); + +export default Tweet; diff --git a/components/embeds/Video.tsx b/components/embeds/Video.tsx new file mode 100644 index 00000000..ebe1a377 --- /dev/null +++ b/components/embeds/Video.tsx @@ -0,0 +1,9 @@ +import ReactPlayer, { ReactPlayerProps } from "react-player/lazy"; + +const Video = (props: ReactPlayerProps) => ( +
+ +
+); + +export default Video; diff --git a/components/loading/Loading.tsx b/components/loading/Loading.tsx index 151e13ba..e669eff1 100644 --- a/components/loading/Loading.tsx +++ b/components/loading/Loading.tsx @@ -1,3 +1,5 @@ +import { memo } from "react"; + type Props = { boxes?: number; timing?: number; @@ -54,4 +56,4 @@ const Loading = ({ boxes = 3, timing = 0.1, width }: Props) => { ); }; -export default Loading; +export default memo(Loading); diff --git a/components/mdxComponents.tsx b/components/mdxComponents.tsx index 5e795b27..1d5586df 100644 --- a/components/mdxComponents.tsx +++ b/components/mdxComponents.tsx @@ -1,13 +1,11 @@ import dynamic from "next/dynamic"; import Link from "next/link"; import Image from "next/image"; -import { useTheme } from "next-themes"; import type { LinkProps } from "next/link"; import type { ImageProps } from "next/image"; -// The following components are all passed into as replacement HTML tags or drop-in React components -// available in .mdx files containing post content, since they're not directly aware of the components in this folder. +// The following components are all passed into in [slug].tsx as replacements for vanilla HTML tags. type CustomLinkProps = LinkProps & { target?: string; @@ -23,7 +21,10 @@ const CustomLink = ({ href, target, rel, className, children }: CustomLinkProps) ); -const CustomImg = (props: ImageProps) => ( +type CustomImgProps = ImageProps & { + caption?: unknown; +}; +const CustomImg = (props: CustomImgProps) => ( // the required height and width are part of the props, so they get automatically passed here with {...props}
{/* eslint-disable-next-line jsx-a11y/alt-text */} @@ -56,55 +57,11 @@ const CustomCode = (props: any) => { } }; -const CustomTweet = (props: { id: string }) => { - const TweetEmbed = dynamic(() => import("react-tweet-embed")); - const { resolvedTheme } = useTheme(); - - return ( - - ); -}; - -const CustomGist = dynamic(() => import("react-gist")); - -const CustomVideo = dynamic(() => import("./video/Video")); - -const CustomGitHubLink = (props: { repo: string }) => { - const OctocatOcticon: any = dynamic(() => import("./icons/octicons").then((mod) => mod.OctocatOcticon)); - - return ( - - - - - ); -}; - // These are the actual tags referenced in mdx files: const mdxComponents = { a: CustomLink, img: CustomImg, code: CustomCode, - video: CustomVideo, - tweet: CustomTweet, - gist: CustomGist, - octocat: CustomGitHubLink, }; export default mdxComponents; diff --git a/components/notes/Comments.module.css b/components/notes/Comments.module.css deleted file mode 100644 index 1807fab5..00000000 --- a/components/notes/Comments.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.wrapper { - margin-top: 2em; - padding-top: 1em; - border-top: 2px solid var(--light); -} diff --git a/components/notes/Comments.tsx b/components/notes/Comments.tsx index d5d4404b..3f10f9ec 100644 --- a/components/notes/Comments.tsx +++ b/components/notes/Comments.tsx @@ -3,9 +3,11 @@ import Head from "next/head"; import { useTheme } from "next-themes"; import { githubRepo } from "../../lib/config"; -import styles from "./Comments.module.css"; +type Props = { + slug: string; +}; -const Comments = ({ slug }) => { +const Comments = (props: Props) => { const [injected, setInjected] = useState(false); const scriptRef = useRef(null); const { resolvedTheme } = useTheme(); @@ -25,7 +27,7 @@ const Comments = ({ slug }) => { // https://utteranc.es/ utterances.setAttribute("repo", githubRepo); - utterances.setAttribute("issue-term", `notes/${slug}`); + utterances.setAttribute("issue-term", `notes/${props.slug}`); utterances.setAttribute("theme", resolvedTheme === "dark" ? "github-dark" : "github-light"); utterances.setAttribute("label", "๐Ÿ’ฌ comments"); @@ -43,7 +45,15 @@ const Comments = ({ slug }) => { -
+
+ + ); }; diff --git a/components/projects/RepoCard.tsx b/components/projects/RepoCard.tsx index 080e6cca..4cb5e26b 100644 --- a/components/projects/RepoCard.tsx +++ b/components/projects/RepoCard.tsx @@ -16,52 +16,52 @@ type Props = { updatedAt: string; }; -const RepoCard = ({ name, url, description, language, stars, forks, updatedAt }: Props) => ( +const RepoCard = (props: Props) => (
- - {name} + + {props.name} - {description &&

{description}

} + {props.description &&

{props.description}

}
- {language && ( + {props.language && (
- {language.name} + {props.language.name}
)} - {stars > 0 && ( + {props.stars > 0 && ( )} - {forks > 0 && ( + {props.forks > 0 && ( )} @@ -69,7 +69,7 @@ const RepoCard = ({ name, url, description, language, stars, forks, updatedAt }:
- Updated {formatDistanceToNowStrict(new Date(updatedAt), { addSuffix: true })} + Updated {formatDistanceToNowStrict(new Date(props.updatedAt), { addSuffix: true })}
diff --git a/components/video/Video.module.css b/components/video/Video.module.css deleted file mode 100644 index 053cb9ea..00000000 --- a/components/video/Video.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.wrapper { - position: relative; - padding-top: 56.25%; /* 100 / (1280 / 720) */ -} - -.react_player { - position: absolute; - top: 0; - left: 0; -} diff --git a/components/video/Video.tsx b/components/video/Video.tsx deleted file mode 100644 index 1fc42334..00000000 --- a/components/video/Video.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import dynamic from "next/dynamic"; -import type { ReactPlayerProps } from "react-player"; - -import styles from "./Video.module.css"; - -const ReactPlayer = dynamic(() => import("react-player/lazy")); - -const Video = (props: ReactPlayerProps) => ( -
- -
-); - -export default Video; diff --git a/lib/parse-notes.ts b/lib/parse-notes.ts index 7fd63063..61511774 100644 --- a/lib/parse-notes.ts +++ b/lib/parse-notes.ts @@ -1,8 +1,24 @@ import fs from "fs"; import path from "path"; import matter from "gray-matter"; +import { bundleMDX } from "mdx-bundler"; +import readingTime from "reading-time"; import { NOTES_DIR, baseUrl } from "./config"; +// remark/rehype markdown plugins +import remarkGfm from "remark-gfm"; +import rehypeExternalLinks from "rehype-external-links"; +import rehypeSlug from "rehype-slug"; +import rehypeAutolinkHeadings from "rehype-autolink-headings"; +import rehypeHighlight from "rehype-highlight"; + +// returns all .mdx files in NOTES_DIR (without .mdx extension) +export const getNoteSlugs = () => + fs + .readdirSync(path.join(process.cwd(), NOTES_DIR)) + .filter((file) => /\.mdx$/.test(file)) + .map((noteFile) => noteFile.replace(/\.mdx$/, "")); + // returns front matter and/or *raw* markdown contents of a given slug export const getNoteData = (slug: string) => { const fullPath = path.join(process.cwd(), NOTES_DIR, `${slug}.mdx`); @@ -15,17 +31,58 @@ export const getNoteData = (slug: string) => { slug, permalink: `${baseUrl}/notes/${slug}/`, date: new Date(data.date).toISOString(), // validate/normalize the date string provided from front matter + readingTime: readingTime(content).minutes, }, content, }; }; -// returns all .mdx files in NOTES_DIR (without .mdx extension) -export const getNoteSlugs = () => - fs - .readdirSync(path.join(process.cwd(), NOTES_DIR)) - .filter((file) => /\.mdx$/.test(file)) - .map((noteFile) => noteFile.replace(/\.mdx$/, "")); +export const getNote = async (slug: string) => { + // https://github.com/kentcdodds/mdx-bundler#nextjs-esbuild-enoent + if (process.platform === "win32") { + process.env.ESBUILD_BINARY_PATH = path.join(process.cwd(), "node_modules", "esbuild", "esbuild.exe"); + } else { + process.env.ESBUILD_BINARY_PATH = path.join(process.cwd(), "node_modules", "esbuild", "bin", "esbuild"); + } + + const { frontMatter, content } = getNoteData(slug); + const { code: mdxSource } = await bundleMDX({ + source: content, + cwd: process.cwd(), + // cwd: path.join("/Users/jake/source/jarv.is", "components"), + xdmOptions: (options) => { + options.remarkPlugins = [...(options.remarkPlugins ?? []), [remarkGfm, { singleTilde: false }]]; + options.rehypePlugins = [ + ...(options.rehypePlugins ?? []), + [rehypeExternalLinks, { target: "_blank", rel: ["noopener", "noreferrer"] }], + [rehypeSlug, {}], + [ + rehypeAutolinkHeadings, + { behavior: "append", properties: { className: "h-anchor" }, content: [], test: ["h2", "h3"] }, + ], + [rehypeHighlight, {}], + ]; + + return options; + }, + esbuildOptions: (options) => { + options.minify = true; + options.target = ["es2018"]; + options.loader = { + ...options.loader, + ".js": "jsx", + ".ts": "tsx", + }; + + return options; + }, + }); + + return { + frontMatter, + mdxSource, + }; +}; // returns the front matter of ALL notes, sorted reverse chronologically export const getAllNotes = () => diff --git a/notes/bernie-sanders-bern-app-data.mdx b/notes/bernie-sanders-bern-app-data.mdx index 16c1ac13..5b61b3ab 100644 --- a/notes/bernie-sanders-bern-app-data.mdx +++ b/notes/bernie-sanders-bern-app-data.mdx @@ -11,6 +11,8 @@ tags: image: "/static/images/notes/bernie-sanders-bern-app-data/sad-bernie.jpg" --- +import Video from "./components/embeds/Video"; + The team behind Bernie Sanders' 2020 campaign [released a new web app](https://www.nbcnews.com/politics/2020-election/bernie-sanders-2020-campaign-unveils-app-increase-its-voter-database-n999206) last month named [BERN](https://app.berniesanders.com/). The goal of BERN is simple: to gather as much information as they can on as many voters in the United States as they can, and make their grassroots army of enthusiastic supporters do the work. It's undoubtedly a smart strategy, but also a concerning one for myself and other privacy advocates. Sad Bernie @@ -40,7 +42,7 @@ Using either feature, a volunteer starts with a search of the database for the v Here's one of the instructional videos provided internally to volunteers: -