my blog https://overreacted.io
53
fork

Configure Feed

Select the types of activity you want to include in your feed.

blog

+15126 -546
-3
.eslintrc.json
··· 1 - { 2 - "extends": "next/core-web-vitals" 3 - }
-36
README.md
··· 1 - This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 - 3 - ## Getting Started 4 - 5 - First, run the development server: 6 - 7 - ```bash 8 - npm run dev 9 - # or 10 - yarn dev 11 - # or 12 - pnpm dev 13 - # or 14 - bun dev 15 - ``` 16 - 17 - Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 - 19 - You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 20 - 21 - This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 - 23 - ## Learn More 24 - 25 - To learn more about Next.js, take a look at the following resources: 26 - 27 - - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 - - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 - 30 - You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 - 32 - ## Deploy on Vercel 33 - 34 - The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 - 36 - Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+35
app/HomeLink.js
··· 1 + "use client"; 2 + 3 + import { sans } from "./fonts"; 4 + import { usePathname } from "next/navigation"; 5 + import Link from "./Link"; 6 + 7 + export default function () { 8 + const pathname = usePathname(); 9 + const isActive = pathname === "/"; 10 + return ( 11 + <Link 12 + href="/" 13 + className={[ 14 + sans.className, 15 + "inline-block text-2xl font-black", 16 + isActive ? "" : "hover:scale-[1.02]", 17 + ].join(" ")} 18 + > 19 + <span 20 + style={{ 21 + "--myColor1": isActive ? "var(--text)" : "var(--pink)", 22 + "--myColor2": isActive ? "var(--text)" : "var(--purple)", 23 + backgroundImage: 24 + "linear-gradient(45deg, var(--myColor1), var(--myColor2))", 25 + backgroundClip: "text", 26 + WebkitBackgroundClip: "text", 27 + color: "transparent", 28 + transition: "--myColor1 0.2s ease-out, --myColor2 0.2s ease-in-out", 29 + }} 30 + > 31 + overreacted 32 + </span> 33 + </Link> 34 + ); 35 + }
+57
app/Link.js
··· 1 + "use client"; 2 + 3 + import { useTransition } from "react"; 4 + import NextLink from "next/link"; 5 + import { useRouter } from "next/navigation"; 6 + 7 + function isModifiedEvent(event) { 8 + const eventTarget = event.currentTarget; 9 + const target = eventTarget.getAttribute("target"); 10 + return ( 11 + (target && target !== "_self") || 12 + event.metaKey || 13 + event.ctrlKey || 14 + event.shiftKey || 15 + event.altKey || 16 + (event.nativeEvent && event.nativeEvent.which === 2) 17 + ); 18 + } 19 + 20 + export default function Link({ 21 + className, 22 + children, 23 + style, 24 + href, 25 + target, 26 + ...rest 27 + }) { 28 + const router = useRouter(); 29 + const [isNavigating, trackNavigation] = useTransition(); 30 + if (!target && !href.startsWith("/")) { 31 + target = "_blank"; 32 + } 33 + return ( 34 + <NextLink 35 + {...rest} 36 + target={target} 37 + href={href} 38 + onClick={(e) => { 39 + if (!isModifiedEvent(e)) { 40 + e.preventDefault(); 41 + trackNavigation(() => { 42 + router.push(e.currentTarget.href); 43 + }); 44 + } 45 + }} 46 + className={[className, `scale-100 active:scale-100`].join(" ")} 47 + style={{ 48 + ...style, 49 + transform: isNavigating ? "scale(1)" : "", 50 + opacity: isNavigating ? 0.85 : 1, 51 + transition: "transform 0.2s ease-in-out, opacity 0.2s 0.4s linear", 52 + }} 53 + > 54 + {children} 55 + </NextLink> 56 + ); 57 + }
+12
app/[slug]/layout.js
··· 1 + import HomeLink from "../HomeLink"; 2 + 3 + export default function Layout({ children }) { 4 + return ( 5 + <> 6 + {children} 7 + <footer className="mt-12"> 8 + <HomeLink /> 9 + </footer> 10 + </> 11 + ); 12 + }
+77
app/[slug]/markdown.css
··· 1 + .markdown { 2 + line-height: 28px; 3 + } 4 + 5 + .markdown p { 6 + @apply pb-8; 7 + } 8 + 9 + .markdown a { 10 + @apply border-b-[1px] border-[--link] text-[--link]; 11 + } 12 + 13 + .markdown hr { 14 + @apply pt-8 opacity-60 dark:opacity-10; 15 + } 16 + 17 + .markdown h2 { 18 + @apply mt-2 pb-8 text-3xl font-bold; 19 + } 20 + 21 + .markdown h3 { 22 + @apply mt-2 pb-8 text-2xl font-bold; 23 + } 24 + 25 + .markdown h4 { 26 + @apply mt-2 pb-8 text-xl font-bold; 27 + } 28 + 29 + .markdown :not(pre) > code { 30 + border-radius: 10px; 31 + background: var(--inlineCode-bg); 32 + color: var(--inlineCode-text); 33 + padding: 0.15em 0.2em 0.05em; 34 + white-space: normal; 35 + } 36 + 37 + .markdown pre { 38 + @apply -mx-4 mb-8 overflow-y-auto rounded-xl p-4 text-sm; 39 + } 40 + 41 + .markdown pre code { 42 + width: auto; 43 + } 44 + 45 + .markdown blockquote { 46 + @apply relative -left-2 -ml-4 mb-8 pl-4; 47 + font-style: italic; 48 + border-left: 3px solid hsla(0, 0%, 0%, 0.9); 49 + border-left-color: inherit; 50 + opacity: 0.8; 51 + } 52 + 53 + .markdown blockquote p { 54 + margin: 0; 55 + padding: 0; 56 + } 57 + 58 + .markdown ul { 59 + margin-top: 0; 60 + padding-bottom: 0; 61 + padding-left: 0; 62 + padding-right: 0; 63 + padding-top: 0; 64 + margin-bottom: 1.75rem; 65 + list-style-position: outside; 66 + list-style-image: none; 67 + list-style: disc; 68 + } 69 + 70 + .markdown li { 71 + margin-bottom: calc(1.75rem / 2); 72 + } 73 + 74 + .markdown img { 75 + @apply mb-8; 76 + max-width: 100%; 77 + }
+83
app/[slug]/page.js
··· 1 + import { readdir, readFile } from "fs/promises"; 2 + import matter from "gray-matter"; 3 + import { MDXRemote } from "next-mdx-remote/rsc"; 4 + import Link from "../Link"; 5 + import { sans } from "../fonts"; 6 + import rehypePrettyCode from "rehype-pretty-code"; 7 + import overnight from "overnight/themes/Overnight-Slumber.json"; 8 + import "./markdown.css"; 9 + 10 + export async function generateMetadata({ params }) { 11 + const file = await readFile("./public/" + params.slug + "/index.md", "utf8"); 12 + let { data } = matter(file); 13 + return { 14 + title: data.title + " — overreacted", 15 + description: data.spoiler, 16 + }; 17 + } 18 + 19 + export default async function PostPage({ params }) { 20 + const file = await readFile("./public/" + params.slug + "/index.md", "utf8"); 21 + let { content, data } = matter(file); 22 + // TODO: change the source instead of this hack. 23 + content = content.replaceAll("```jsx{", "```jsx {"); 24 + const discussUrl = `https://x.com/search?q=${encodeURIComponent( 25 + `https://overreacted.io/${params.slug}/`, 26 + )}`; 27 + const editUrl = `https://github.com/gaearon/overreacted.io/edit/master/public/${encodeURIComponent( 28 + params.slug, 29 + )}/index.md`; 30 + return ( 31 + <article> 32 + <h1 33 + className={[ 34 + sans.className, 35 + "text-[40px] font-black leading-[44px] text-[--title]", 36 + ].join(" ")} 37 + > 38 + {data.title} 39 + </h1> 40 + <p className="mt-2 text-[13px] text-gray-700 dark:text-gray-300"> 41 + {new Date(data.date).toLocaleDateString("en", { 42 + day: "numeric", 43 + month: "long", 44 + year: "numeric", 45 + })} 46 + </p> 47 + <div className="markdown mt-10"> 48 + <MDXRemote 49 + source={content} 50 + components={{ 51 + a: Link, 52 + }} 53 + options={{ 54 + mdxOptions: { 55 + rehypePlugins: [ 56 + [ 57 + rehypePrettyCode, 58 + { 59 + theme: overnight, 60 + }, 61 + ], 62 + ], 63 + }, 64 + }} 65 + /> 66 + <hr /> 67 + <p> 68 + <Link href={discussUrl}>Discuss on 𝕏</Link> 69 + &nbsp;&nbsp;&middot;&nbsp;&nbsp; 70 + <Link href={editUrl}>Edit on GitHub</Link> 71 + </p> 72 + </div> 73 + </article> 74 + ); 75 + } 76 + 77 + export async function generateStaticParams() { 78 + const entries = await readdir("./public/", { withFileTypes: true }); 79 + const dirs = entries 80 + .filter((entry) => entry.isDirectory()) 81 + .map((entry) => entry.name); 82 + return dirs.map((dir) => ({ slug: dir })); 83 + }
app/favicon.ico

This is a binary file and will not be displayed.

+15
app/fonts.js
··· 1 + import { Montserrat, Merriweather } from 'next/font/google' 2 + 3 + export const sans = Montserrat({ 4 + subsets: ['latin'], 5 + display: 'swap', 6 + weight: ['400', '700', '900'], 7 + style: ['normal'], 8 + }) 9 + 10 + export const serif = Merriweather({ 11 + subsets: ['latin'], 12 + display: 'swap', 13 + weight: ['400', '700'], 14 + style: ['normal', 'italic'], 15 + })
+62
app/global.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities; 4 + 5 + :root { 6 + --text: #222; 7 + --title: #222; 8 + --bg: white; 9 + --link: #d23669; 10 + --inlineCode-bg: rgba(255, 229, 100, 0.2); 11 + --inlineCode-text: #1a1a1a; 12 + --pink: lab(63 59.32 -1.47); 13 + --purple: lab(33 42.09 -43.19); 14 + } 15 + @media (prefers-color-scheme: dark) { 16 + :root { 17 + --text: rgba(255, 255, 255, 0.88); 18 + --title: white; 19 + --bg: rgb(40, 44, 53); 20 + --link: #ffa7c4; 21 + --inlineCode-bg: rgba(115, 124, 153, 0.2); 22 + --inlineCode-text: #e6e6e6; 23 + --pink: lab(81 32.36 -7.02); 24 + --purple: lab(78 19.97 -36.75); 25 + } 26 + } 27 + @property --myColor1 { 28 + syntax: "<color>"; 29 + initial-value: #222; 30 + inherits: false; 31 + } 32 + @property --myColor2 { 33 + syntax: "<color>"; 34 + initial-value: #222; 35 + inherits: false; 36 + } 37 + @media (prefers-color-scheme: dark) { 38 + @property --myColor1 { 39 + syntax: "<color>"; 40 + initial-value: rgba(255, 255, 255, 0.88); 41 + inherits: false; 42 + } 43 + @property --myColor2 { 44 + syntax: "<color>"; 45 + initial-value: rgba(255, 255, 255, 0.88); 46 + inherits: false; 47 + } 48 + } 49 + 50 + @media (prefers-color-scheme: dark) { 51 + body { 52 + -webkit-font-smoothing: antialiased; 53 + -moz-osx-font-smoothing: grayscale; 54 + } 55 + } 56 + 57 + @media (prefers-reduced-motion) { 58 + * { 59 + transition: none !important; 60 + transform: none !important; 61 + } 62 + }
-107
app/globals.css
··· 1 - :root { 2 - --max-width: 1100px; 3 - --border-radius: 12px; 4 - --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 5 - 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 6 - 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 7 - 8 - --foreground-rgb: 0, 0, 0; 9 - --background-start-rgb: 214, 219, 220; 10 - --background-end-rgb: 255, 255, 255; 11 - 12 - --primary-glow: conic-gradient( 13 - from 180deg at 50% 50%, 14 - #16abff33 0deg, 15 - #0885ff33 55deg, 16 - #54d6ff33 120deg, 17 - #0071ff33 160deg, 18 - transparent 360deg 19 - ); 20 - --secondary-glow: radial-gradient( 21 - rgba(255, 255, 255, 1), 22 - rgba(255, 255, 255, 0) 23 - ); 24 - 25 - --tile-start-rgb: 239, 245, 249; 26 - --tile-end-rgb: 228, 232, 233; 27 - --tile-border: conic-gradient( 28 - #00000080, 29 - #00000040, 30 - #00000030, 31 - #00000020, 32 - #00000010, 33 - #00000010, 34 - #00000080 35 - ); 36 - 37 - --callout-rgb: 238, 240, 241; 38 - --callout-border-rgb: 172, 175, 176; 39 - --card-rgb: 180, 185, 188; 40 - --card-border-rgb: 131, 134, 135; 41 - } 42 - 43 - @media (prefers-color-scheme: dark) { 44 - :root { 45 - --foreground-rgb: 255, 255, 255; 46 - --background-start-rgb: 0, 0, 0; 47 - --background-end-rgb: 0, 0, 0; 48 - 49 - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 50 - --secondary-glow: linear-gradient( 51 - to bottom right, 52 - rgba(1, 65, 255, 0), 53 - rgba(1, 65, 255, 0), 54 - rgba(1, 65, 255, 0.3) 55 - ); 56 - 57 - --tile-start-rgb: 2, 13, 46; 58 - --tile-end-rgb: 2, 5, 19; 59 - --tile-border: conic-gradient( 60 - #ffffff80, 61 - #ffffff40, 62 - #ffffff30, 63 - #ffffff20, 64 - #ffffff10, 65 - #ffffff10, 66 - #ffffff80 67 - ); 68 - 69 - --callout-rgb: 20, 20, 20; 70 - --callout-border-rgb: 108, 108, 108; 71 - --card-rgb: 100, 100, 100; 72 - --card-border-rgb: 200, 200, 200; 73 - } 74 - } 75 - 76 - * { 77 - box-sizing: border-box; 78 - padding: 0; 79 - margin: 0; 80 - } 81 - 82 - html, 83 - body { 84 - max-width: 100vw; 85 - overflow-x: hidden; 86 - } 87 - 88 - body { 89 - color: rgb(var(--foreground-rgb)); 90 - background: linear-gradient( 91 - to bottom, 92 - transparent, 93 - rgb(var(--background-end-rgb)) 94 - ) 95 - rgb(var(--background-start-rgb)); 96 - } 97 - 98 - a { 99 - color: inherit; 100 - text-decoration: none; 101 - } 102 - 103 - @media (prefers-color-scheme: dark) { 104 - html { 105 - color-scheme: dark; 106 - } 107 - }
app/icon.png

This is a binary file and will not be displayed.

+22 -12
app/layout.js
··· 1 - import { Inter } from 'next/font/google' 2 - import './globals.css' 3 - 4 - const inter = Inter({ subsets: ['latin'] }) 5 - 6 - export const metadata = { 7 - title: 'Create Next App', 8 - description: 'Generated by create next app', 9 - } 1 + import Link from "./Link"; 2 + import HomeLink from "./HomeLink"; 3 + import { serif } from "./fonts"; 4 + import "./global.css"; 10 5 11 6 export default function RootLayout({ children }) { 12 7 return ( 13 - <html lang="en"> 14 - <body className={inter.className}>{children}</body> 8 + <html lang="en" className={serif.className}> 9 + <body className="mx-auto max-w-2xl bg-[--bg] px-5 py-12 text-[--text]"> 10 + <header className="mb-14 flex flex-row place-content-between"> 11 + <HomeLink /> 12 + <span className="relative top-[4px] italic"> 13 + by{" "} 14 + <Link href="https://danabra.mov" target="_blank"> 15 + <img 16 + alt="Dan Abramov" 17 + src="https://pbs.twimg.com/profile_images/1545194945161707520/rqkwPViA_400x400.jpg" 18 + className="relative -top-1 mx-1 inline h-8 w-8 rounded-full" 19 + /> 20 + </Link> 21 + </span> 22 + </header> 23 + <main>{children}</main> 24 + </body> 15 25 </html> 16 - ) 26 + ); 17 27 }
+10
app/not-found.js
··· 1 + export default function NotFound() { 2 + return ( 3 + <> 4 + <h1>Not found.</h1> 5 + <p> 6 + If you're looking for translated posts, they're not on the site anymore but <a href="https://github.com/gaearon/overreacted.io/tree/archive/src/pages">you can find an archive of them here.</a> 7 + </p> 8 + </> 9 + ) 10 + }
+82 -88
app/page.js
··· 1 - import Image from 'next/image' 2 - import styles from './page.module.css' 1 + import { readdir, readFile } from "fs/promises"; 2 + import matter from "gray-matter"; 3 + import Link from "./Link"; 4 + import Color from "colorjs.io"; 5 + import { sans } from "./fonts"; 3 6 4 - export default function Home() { 7 + export const metadata = { 8 + title: "overreacted — A blog by Dan Abramov", 9 + description: "A personal blog by Dan Abramov", 10 + }; 11 + 12 + export default async function Home() { 13 + const entries = await readdir("./public/", { withFileTypes: true }); 14 + const dirs = entries 15 + .filter((entry) => entry.isDirectory()) 16 + .map((entry) => entry.name); 17 + const fileContents = await Promise.all( 18 + dirs.map((dir) => readFile("./public/" + dir + "/index.md", "utf8")), 19 + ); 20 + const posts = dirs.map((slug, i) => { 21 + const fileContent = fileContents[i]; 22 + const { data } = matter(fileContent); 23 + return { slug, ...data }; 24 + }); 25 + posts.sort((a, b) => { 26 + return Date.parse(a.date) < Date.parse(b.date) ? 1 : -1; 27 + }); 5 28 return ( 6 - <main className={styles.main}> 7 - <div className={styles.description}> 8 - <p> 9 - Get started by editing&nbsp; 10 - <code className={styles.code}>app/page.js</code> 11 - </p> 12 - <div> 13 - <a 14 - href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" 15 - target="_blank" 16 - rel="noopener noreferrer" 17 - > 18 - By{' '} 19 - <Image 20 - src="/vercel.svg" 21 - alt="Vercel Logo" 22 - className={styles.vercelLogo} 23 - width={100} 24 - height={24} 25 - priority 26 - /> 27 - </a> 28 - </div> 29 - </div> 30 - 31 - <div className={styles.center}> 32 - <Image 33 - className={styles.logo} 34 - src="/next.svg" 35 - alt="Next.js Logo" 36 - width={180} 37 - height={37} 38 - priority 39 - /> 40 - </div> 41 - 42 - <div className={styles.grid}> 43 - <a 44 - href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" 45 - className={styles.card} 46 - target="_blank" 47 - rel="noopener noreferrer" 29 + <div className="relative -top-[10px] flex flex-col gap-8"> 30 + {posts.map((post) => ( 31 + <Link 32 + className="block py-4 hover:scale-[1.005]" 33 + href={"/" + post.slug + "/"} 48 34 > 49 - <h2> 50 - Docs <span>-&gt;</span> 51 - </h2> 52 - <p>Find in-depth information about Next.js features and API.</p> 53 - </a> 35 + <article key={post.slug}> 36 + <PostTitle post={post} /> 37 + <PostMeta post={post} /> 38 + <PostSubtitle post={post} /> 39 + </article> 40 + </Link> 41 + ))} 42 + </div> 43 + ); 44 + } 54 45 55 - <a 56 - href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" 57 - className={styles.card} 58 - target="_blank" 59 - rel="noopener noreferrer" 60 - > 61 - <h2> 62 - Learn <span>-&gt;</span> 63 - </h2> 64 - <p>Learn about Next.js in an interactive course with&nbsp;quizzes!</p> 65 - </a> 46 + function PostTitle({ post }) { 47 + let lightStart = new Color("lab(63 59.32 -1.47)"); 48 + let lightEnd = new Color("lab(33 42.09 -43.19)"); 49 + let lightRange = lightStart.range(lightEnd); 50 + let darkStart = new Color("lab(81 32.36 -7.02)"); 51 + let darkEnd = new Color("lab(78 19.97 -36.75)"); 52 + let darkRange = darkStart.range(darkEnd); 53 + let today = new Date(); 54 + let timeSinceFirstPost = (today - new Date(2018, 10, 30)).valueOf(); 55 + let timeSinceThisPost = (today - new Date(post.date)).valueOf(); 56 + let staleness = timeSinceThisPost / timeSinceFirstPost; 57 + 58 + return ( 59 + <h2 60 + className={[ 61 + sans.className, 62 + "text-[28px] font-black", 63 + "text-[--lightLink] dark:text-[--darkLink]", 64 + ].join(" ")} 65 + style={{ 66 + "--lightLink": lightRange(staleness).toString(), 67 + "--darkLink": darkRange(staleness).toString(), 68 + }} 69 + > 70 + {post.title} 71 + </h2> 72 + ); 73 + } 66 74 67 - <a 68 - href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" 69 - className={styles.card} 70 - target="_blank" 71 - rel="noopener noreferrer" 72 - > 73 - <h2> 74 - Templates <span>-&gt;</span> 75 - </h2> 76 - <p>Explore the Next.js 13 playground.</p> 77 - </a> 75 + function PostMeta({ post }) { 76 + return ( 77 + <p className="text-[13px] text-gray-700 dark:text-gray-300"> 78 + {new Date(post.date).toLocaleDateString("en", { 79 + day: "numeric", 80 + month: "long", 81 + year: "numeric", 82 + })} 83 + </p> 84 + ); 85 + } 78 86 79 - <a 80 - href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" 81 - className={styles.card} 82 - target="_blank" 83 - rel="noopener noreferrer" 84 - > 85 - <h2> 86 - Deploy <span>-&gt;</span> 87 - </h2> 88 - <p> 89 - Instantly deploy your Next.js site to a shareable URL with Vercel. 90 - </p> 91 - </a> 92 - </div> 93 - </main> 94 - ) 87 + function PostSubtitle({ post }) { 88 + return <p className="mt-1">{post.spoiler}</p>; 95 89 }
-229
app/page.module.css
··· 1 - .main { 2 - display: flex; 3 - flex-direction: column; 4 - justify-content: space-between; 5 - align-items: center; 6 - padding: 6rem; 7 - min-height: 100vh; 8 - } 9 - 10 - .description { 11 - display: inherit; 12 - justify-content: inherit; 13 - align-items: inherit; 14 - font-size: 0.85rem; 15 - max-width: var(--max-width); 16 - width: 100%; 17 - z-index: 2; 18 - font-family: var(--font-mono); 19 - } 20 - 21 - .description a { 22 - display: flex; 23 - justify-content: center; 24 - align-items: center; 25 - gap: 0.5rem; 26 - } 27 - 28 - .description p { 29 - position: relative; 30 - margin: 0; 31 - padding: 1rem; 32 - background-color: rgba(var(--callout-rgb), 0.5); 33 - border: 1px solid rgba(var(--callout-border-rgb), 0.3); 34 - border-radius: var(--border-radius); 35 - } 36 - 37 - .code { 38 - font-weight: 700; 39 - font-family: var(--font-mono); 40 - } 41 - 42 - .grid { 43 - display: grid; 44 - grid-template-columns: repeat(4, minmax(25%, auto)); 45 - max-width: 100%; 46 - width: var(--max-width); 47 - } 48 - 49 - .card { 50 - padding: 1rem 1.2rem; 51 - border-radius: var(--border-radius); 52 - background: rgba(var(--card-rgb), 0); 53 - border: 1px solid rgba(var(--card-border-rgb), 0); 54 - transition: background 200ms, border 200ms; 55 - } 56 - 57 - .card span { 58 - display: inline-block; 59 - transition: transform 200ms; 60 - } 61 - 62 - .card h2 { 63 - font-weight: 600; 64 - margin-bottom: 0.7rem; 65 - } 66 - 67 - .card p { 68 - margin: 0; 69 - opacity: 0.6; 70 - font-size: 0.9rem; 71 - line-height: 1.5; 72 - max-width: 30ch; 73 - } 74 - 75 - .center { 76 - display: flex; 77 - justify-content: center; 78 - align-items: center; 79 - position: relative; 80 - padding: 4rem 0; 81 - } 82 - 83 - .center::before { 84 - background: var(--secondary-glow); 85 - border-radius: 50%; 86 - width: 480px; 87 - height: 360px; 88 - margin-left: -400px; 89 - } 90 - 91 - .center::after { 92 - background: var(--primary-glow); 93 - width: 240px; 94 - height: 180px; 95 - z-index: -1; 96 - } 97 - 98 - .center::before, 99 - .center::after { 100 - content: ''; 101 - left: 50%; 102 - position: absolute; 103 - filter: blur(45px); 104 - transform: translateZ(0); 105 - } 106 - 107 - .logo { 108 - position: relative; 109 - } 110 - /* Enable hover only on non-touch devices */ 111 - @media (hover: hover) and (pointer: fine) { 112 - .card:hover { 113 - background: rgba(var(--card-rgb), 0.1); 114 - border: 1px solid rgba(var(--card-border-rgb), 0.15); 115 - } 116 - 117 - .card:hover span { 118 - transform: translateX(4px); 119 - } 120 - } 121 - 122 - @media (prefers-reduced-motion) { 123 - .card:hover span { 124 - transform: none; 125 - } 126 - } 127 - 128 - /* Mobile */ 129 - @media (max-width: 700px) { 130 - .content { 131 - padding: 4rem; 132 - } 133 - 134 - .grid { 135 - grid-template-columns: 1fr; 136 - margin-bottom: 120px; 137 - max-width: 320px; 138 - text-align: center; 139 - } 140 - 141 - .card { 142 - padding: 1rem 2.5rem; 143 - } 144 - 145 - .card h2 { 146 - margin-bottom: 0.5rem; 147 - } 148 - 149 - .center { 150 - padding: 8rem 0 6rem; 151 - } 152 - 153 - .center::before { 154 - transform: none; 155 - height: 300px; 156 - } 157 - 158 - .description { 159 - font-size: 0.8rem; 160 - } 161 - 162 - .description a { 163 - padding: 1rem; 164 - } 165 - 166 - .description p, 167 - .description div { 168 - display: flex; 169 - justify-content: center; 170 - position: fixed; 171 - width: 100%; 172 - } 173 - 174 - .description p { 175 - align-items: center; 176 - inset: 0 0 auto; 177 - padding: 2rem 1rem 1.4rem; 178 - border-radius: 0; 179 - border: none; 180 - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); 181 - background: linear-gradient( 182 - to bottom, 183 - rgba(var(--background-start-rgb), 1), 184 - rgba(var(--callout-rgb), 0.5) 185 - ); 186 - background-clip: padding-box; 187 - backdrop-filter: blur(24px); 188 - } 189 - 190 - .description div { 191 - align-items: flex-end; 192 - pointer-events: none; 193 - inset: auto 0 0; 194 - padding: 2rem; 195 - height: 200px; 196 - background: linear-gradient( 197 - to bottom, 198 - transparent 0%, 199 - rgb(var(--background-end-rgb)) 40% 200 - ); 201 - z-index: 1; 202 - } 203 - } 204 - 205 - /* Tablet and Smaller Desktop */ 206 - @media (min-width: 701px) and (max-width: 1120px) { 207 - .grid { 208 - grid-template-columns: repeat(2, 50%); 209 - } 210 - } 211 - 212 - @media (prefers-color-scheme: dark) { 213 - .vercelLogo { 214 - filter: invert(1); 215 - } 216 - 217 - .logo { 218 - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); 219 - } 220 - } 221 - 222 - @keyframes rotate { 223 - from { 224 - transform: rotate(360deg); 225 - } 226 - to { 227 - transform: rotate(0deg); 228 - } 229 - }
-7
jsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "paths": { 4 - "@/*": ["./*"] 5 - } 6 - } 7 - }
+4 -4
next.config.js
··· 1 - /** @type {import('next').NextConfig} */ 2 - const nextConfig = {} 3 - 4 - module.exports = nextConfig 1 + module.exports = { 2 + output: "export", 3 + trailingSlash: true, 4 + };
+5232 -56
package-lock.json
··· 8 8 "name": "overreacted", 9 9 "version": "0.1.0", 10 10 "dependencies": { 11 - "next": "13.5.6", 11 + "colorjs.io": "^0.4.5", 12 + "gray-matter": "^4.0.3", 13 + "next": "^14.0.1-canary.2", 14 + "next-mdx-remote": "^4.4.1", 15 + "overnight": "^1.8.0", 12 16 "react": "^18", 13 - "react-dom": "^18" 17 + "react-dom": "^18", 18 + "rehype-pretty-code": "^0.10.2", 19 + "shiki": "^0.14.5" 14 20 }, 15 21 "devDependencies": { 22 + "autoprefixer": "^10.4.16", 16 23 "eslint": "^8", 17 - "eslint-config-next": "13.5.6" 24 + "eslint-config-next": "13.5.6", 25 + "postcss": "^8.4.31", 26 + "tailwindcss": "^3.3.5" 18 27 } 19 28 }, 20 29 "node_modules/@aashutoshrathi/word-wrap": { ··· 24 33 "dev": true, 25 34 "engines": { 26 35 "node": ">=0.10.0" 36 + } 37 + }, 38 + "node_modules/@alloc/quick-lru": { 39 + "version": "5.2.0", 40 + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", 41 + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", 42 + "dev": true, 43 + "engines": { 44 + "node": ">=10" 45 + }, 46 + "funding": { 47 + "url": "https://github.com/sponsors/sindresorhus" 27 48 } 28 49 }, 29 50 "node_modules/@babel/runtime": { ··· 127 148 "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", 128 149 "dev": true 129 150 }, 151 + "node_modules/@jridgewell/gen-mapping": { 152 + "version": "0.3.3", 153 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 154 + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 155 + "dev": true, 156 + "dependencies": { 157 + "@jridgewell/set-array": "^1.0.1", 158 + "@jridgewell/sourcemap-codec": "^1.4.10", 159 + "@jridgewell/trace-mapping": "^0.3.9" 160 + }, 161 + "engines": { 162 + "node": ">=6.0.0" 163 + } 164 + }, 165 + "node_modules/@jridgewell/resolve-uri": { 166 + "version": "3.1.1", 167 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 168 + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 169 + "dev": true, 170 + "engines": { 171 + "node": ">=6.0.0" 172 + } 173 + }, 174 + "node_modules/@jridgewell/set-array": { 175 + "version": "1.1.2", 176 + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 177 + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 178 + "dev": true, 179 + "engines": { 180 + "node": ">=6.0.0" 181 + } 182 + }, 183 + "node_modules/@jridgewell/sourcemap-codec": { 184 + "version": "1.4.15", 185 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 186 + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 187 + "dev": true 188 + }, 189 + "node_modules/@jridgewell/trace-mapping": { 190 + "version": "0.3.20", 191 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", 192 + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", 193 + "dev": true, 194 + "dependencies": { 195 + "@jridgewell/resolve-uri": "^3.1.0", 196 + "@jridgewell/sourcemap-codec": "^1.4.14" 197 + } 198 + }, 199 + "node_modules/@mdx-js/mdx": { 200 + "version": "2.3.0", 201 + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz", 202 + "integrity": "sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==", 203 + "dependencies": { 204 + "@types/estree-jsx": "^1.0.0", 205 + "@types/mdx": "^2.0.0", 206 + "estree-util-build-jsx": "^2.0.0", 207 + "estree-util-is-identifier-name": "^2.0.0", 208 + "estree-util-to-js": "^1.1.0", 209 + "estree-walker": "^3.0.0", 210 + "hast-util-to-estree": "^2.0.0", 211 + "markdown-extensions": "^1.0.0", 212 + "periscopic": "^3.0.0", 213 + "remark-mdx": "^2.0.0", 214 + "remark-parse": "^10.0.0", 215 + "remark-rehype": "^10.0.0", 216 + "unified": "^10.0.0", 217 + "unist-util-position-from-estree": "^1.0.0", 218 + "unist-util-stringify-position": "^3.0.0", 219 + "unist-util-visit": "^4.0.0", 220 + "vfile": "^5.0.0" 221 + }, 222 + "funding": { 223 + "type": "opencollective", 224 + "url": "https://opencollective.com/unified" 225 + } 226 + }, 227 + "node_modules/@mdx-js/mdx/node_modules/@types/mdast": { 228 + "version": "3.0.14", 229 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 230 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 231 + "dependencies": { 232 + "@types/unist": "^2" 233 + } 234 + }, 235 + "node_modules/@mdx-js/mdx/node_modules/mdast-util-from-markdown": { 236 + "version": "1.3.1", 237 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", 238 + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", 239 + "dependencies": { 240 + "@types/mdast": "^3.0.0", 241 + "@types/unist": "^2.0.0", 242 + "decode-named-character-reference": "^1.0.0", 243 + "mdast-util-to-string": "^3.1.0", 244 + "micromark": "^3.0.0", 245 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 246 + "micromark-util-decode-string": "^1.0.0", 247 + "micromark-util-normalize-identifier": "^1.0.0", 248 + "micromark-util-symbol": "^1.0.0", 249 + "micromark-util-types": "^1.0.0", 250 + "unist-util-stringify-position": "^3.0.0", 251 + "uvu": "^0.5.0" 252 + }, 253 + "funding": { 254 + "type": "opencollective", 255 + "url": "https://opencollective.com/unified" 256 + } 257 + }, 258 + "node_modules/@mdx-js/mdx/node_modules/mdast-util-to-string": { 259 + "version": "3.2.0", 260 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", 261 + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", 262 + "dependencies": { 263 + "@types/mdast": "^3.0.0" 264 + }, 265 + "funding": { 266 + "type": "opencollective", 267 + "url": "https://opencollective.com/unified" 268 + } 269 + }, 270 + "node_modules/@mdx-js/mdx/node_modules/micromark": { 271 + "version": "3.2.0", 272 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", 273 + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", 274 + "funding": [ 275 + { 276 + "type": "GitHub Sponsors", 277 + "url": "https://github.com/sponsors/unifiedjs" 278 + }, 279 + { 280 + "type": "OpenCollective", 281 + "url": "https://opencollective.com/unified" 282 + } 283 + ], 284 + "dependencies": { 285 + "@types/debug": "^4.0.0", 286 + "debug": "^4.0.0", 287 + "decode-named-character-reference": "^1.0.0", 288 + "micromark-core-commonmark": "^1.0.1", 289 + "micromark-factory-space": "^1.0.0", 290 + "micromark-util-character": "^1.0.0", 291 + "micromark-util-chunked": "^1.0.0", 292 + "micromark-util-combine-extensions": "^1.0.0", 293 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 294 + "micromark-util-encode": "^1.0.0", 295 + "micromark-util-normalize-identifier": "^1.0.0", 296 + "micromark-util-resolve-all": "^1.0.0", 297 + "micromark-util-sanitize-uri": "^1.0.0", 298 + "micromark-util-subtokenize": "^1.0.0", 299 + "micromark-util-symbol": "^1.0.0", 300 + "micromark-util-types": "^1.0.1", 301 + "uvu": "^0.5.0" 302 + } 303 + }, 304 + "node_modules/@mdx-js/mdx/node_modules/micromark-core-commonmark": { 305 + "version": "1.1.0", 306 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", 307 + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", 308 + "funding": [ 309 + { 310 + "type": "GitHub Sponsors", 311 + "url": "https://github.com/sponsors/unifiedjs" 312 + }, 313 + { 314 + "type": "OpenCollective", 315 + "url": "https://opencollective.com/unified" 316 + } 317 + ], 318 + "dependencies": { 319 + "decode-named-character-reference": "^1.0.0", 320 + "micromark-factory-destination": "^1.0.0", 321 + "micromark-factory-label": "^1.0.0", 322 + "micromark-factory-space": "^1.0.0", 323 + "micromark-factory-title": "^1.0.0", 324 + "micromark-factory-whitespace": "^1.0.0", 325 + "micromark-util-character": "^1.0.0", 326 + "micromark-util-chunked": "^1.0.0", 327 + "micromark-util-classify-character": "^1.0.0", 328 + "micromark-util-html-tag-name": "^1.0.0", 329 + "micromark-util-normalize-identifier": "^1.0.0", 330 + "micromark-util-resolve-all": "^1.0.0", 331 + "micromark-util-subtokenize": "^1.0.0", 332 + "micromark-util-symbol": "^1.0.0", 333 + "micromark-util-types": "^1.0.1", 334 + "uvu": "^0.5.0" 335 + } 336 + }, 337 + "node_modules/@mdx-js/mdx/node_modules/micromark-factory-destination": { 338 + "version": "1.1.0", 339 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", 340 + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", 341 + "funding": [ 342 + { 343 + "type": "GitHub Sponsors", 344 + "url": "https://github.com/sponsors/unifiedjs" 345 + }, 346 + { 347 + "type": "OpenCollective", 348 + "url": "https://opencollective.com/unified" 349 + } 350 + ], 351 + "dependencies": { 352 + "micromark-util-character": "^1.0.0", 353 + "micromark-util-symbol": "^1.0.0", 354 + "micromark-util-types": "^1.0.0" 355 + } 356 + }, 357 + "node_modules/@mdx-js/mdx/node_modules/micromark-factory-label": { 358 + "version": "1.1.0", 359 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", 360 + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", 361 + "funding": [ 362 + { 363 + "type": "GitHub Sponsors", 364 + "url": "https://github.com/sponsors/unifiedjs" 365 + }, 366 + { 367 + "type": "OpenCollective", 368 + "url": "https://opencollective.com/unified" 369 + } 370 + ], 371 + "dependencies": { 372 + "micromark-util-character": "^1.0.0", 373 + "micromark-util-symbol": "^1.0.0", 374 + "micromark-util-types": "^1.0.0", 375 + "uvu": "^0.5.0" 376 + } 377 + }, 378 + "node_modules/@mdx-js/mdx/node_modules/micromark-factory-space": { 379 + "version": "1.1.0", 380 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 381 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 382 + "funding": [ 383 + { 384 + "type": "GitHub Sponsors", 385 + "url": "https://github.com/sponsors/unifiedjs" 386 + }, 387 + { 388 + "type": "OpenCollective", 389 + "url": "https://opencollective.com/unified" 390 + } 391 + ], 392 + "dependencies": { 393 + "micromark-util-character": "^1.0.0", 394 + "micromark-util-types": "^1.0.0" 395 + } 396 + }, 397 + "node_modules/@mdx-js/mdx/node_modules/micromark-factory-title": { 398 + "version": "1.1.0", 399 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", 400 + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", 401 + "funding": [ 402 + { 403 + "type": "GitHub Sponsors", 404 + "url": "https://github.com/sponsors/unifiedjs" 405 + }, 406 + { 407 + "type": "OpenCollective", 408 + "url": "https://opencollective.com/unified" 409 + } 410 + ], 411 + "dependencies": { 412 + "micromark-factory-space": "^1.0.0", 413 + "micromark-util-character": "^1.0.0", 414 + "micromark-util-symbol": "^1.0.0", 415 + "micromark-util-types": "^1.0.0" 416 + } 417 + }, 418 + "node_modules/@mdx-js/mdx/node_modules/micromark-factory-whitespace": { 419 + "version": "1.1.0", 420 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", 421 + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", 422 + "funding": [ 423 + { 424 + "type": "GitHub Sponsors", 425 + "url": "https://github.com/sponsors/unifiedjs" 426 + }, 427 + { 428 + "type": "OpenCollective", 429 + "url": "https://opencollective.com/unified" 430 + } 431 + ], 432 + "dependencies": { 433 + "micromark-factory-space": "^1.0.0", 434 + "micromark-util-character": "^1.0.0", 435 + "micromark-util-symbol": "^1.0.0", 436 + "micromark-util-types": "^1.0.0" 437 + } 438 + }, 439 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-character": { 440 + "version": "1.2.0", 441 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 442 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 443 + "funding": [ 444 + { 445 + "type": "GitHub Sponsors", 446 + "url": "https://github.com/sponsors/unifiedjs" 447 + }, 448 + { 449 + "type": "OpenCollective", 450 + "url": "https://opencollective.com/unified" 451 + } 452 + ], 453 + "dependencies": { 454 + "micromark-util-symbol": "^1.0.0", 455 + "micromark-util-types": "^1.0.0" 456 + } 457 + }, 458 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-chunked": { 459 + "version": "1.1.0", 460 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 461 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 462 + "funding": [ 463 + { 464 + "type": "GitHub Sponsors", 465 + "url": "https://github.com/sponsors/unifiedjs" 466 + }, 467 + { 468 + "type": "OpenCollective", 469 + "url": "https://opencollective.com/unified" 470 + } 471 + ], 472 + "dependencies": { 473 + "micromark-util-symbol": "^1.0.0" 474 + } 475 + }, 476 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-classify-character": { 477 + "version": "1.1.0", 478 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", 479 + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", 480 + "funding": [ 481 + { 482 + "type": "GitHub Sponsors", 483 + "url": "https://github.com/sponsors/unifiedjs" 484 + }, 485 + { 486 + "type": "OpenCollective", 487 + "url": "https://opencollective.com/unified" 488 + } 489 + ], 490 + "dependencies": { 491 + "micromark-util-character": "^1.0.0", 492 + "micromark-util-symbol": "^1.0.0", 493 + "micromark-util-types": "^1.0.0" 494 + } 495 + }, 496 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-combine-extensions": { 497 + "version": "1.1.0", 498 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", 499 + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", 500 + "funding": [ 501 + { 502 + "type": "GitHub Sponsors", 503 + "url": "https://github.com/sponsors/unifiedjs" 504 + }, 505 + { 506 + "type": "OpenCollective", 507 + "url": "https://opencollective.com/unified" 508 + } 509 + ], 510 + "dependencies": { 511 + "micromark-util-chunked": "^1.0.0", 512 + "micromark-util-types": "^1.0.0" 513 + } 514 + }, 515 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-decode-numeric-character-reference": { 516 + "version": "1.1.0", 517 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", 518 + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", 519 + "funding": [ 520 + { 521 + "type": "GitHub Sponsors", 522 + "url": "https://github.com/sponsors/unifiedjs" 523 + }, 524 + { 525 + "type": "OpenCollective", 526 + "url": "https://opencollective.com/unified" 527 + } 528 + ], 529 + "dependencies": { 530 + "micromark-util-symbol": "^1.0.0" 531 + } 532 + }, 533 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-decode-string": { 534 + "version": "1.1.0", 535 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", 536 + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", 537 + "funding": [ 538 + { 539 + "type": "GitHub Sponsors", 540 + "url": "https://github.com/sponsors/unifiedjs" 541 + }, 542 + { 543 + "type": "OpenCollective", 544 + "url": "https://opencollective.com/unified" 545 + } 546 + ], 547 + "dependencies": { 548 + "decode-named-character-reference": "^1.0.0", 549 + "micromark-util-character": "^1.0.0", 550 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 551 + "micromark-util-symbol": "^1.0.0" 552 + } 553 + }, 554 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-encode": { 555 + "version": "1.1.0", 556 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", 557 + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", 558 + "funding": [ 559 + { 560 + "type": "GitHub Sponsors", 561 + "url": "https://github.com/sponsors/unifiedjs" 562 + }, 563 + { 564 + "type": "OpenCollective", 565 + "url": "https://opencollective.com/unified" 566 + } 567 + ] 568 + }, 569 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-html-tag-name": { 570 + "version": "1.2.0", 571 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", 572 + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", 573 + "funding": [ 574 + { 575 + "type": "GitHub Sponsors", 576 + "url": "https://github.com/sponsors/unifiedjs" 577 + }, 578 + { 579 + "type": "OpenCollective", 580 + "url": "https://opencollective.com/unified" 581 + } 582 + ] 583 + }, 584 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-normalize-identifier": { 585 + "version": "1.1.0", 586 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", 587 + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", 588 + "funding": [ 589 + { 590 + "type": "GitHub Sponsors", 591 + "url": "https://github.com/sponsors/unifiedjs" 592 + }, 593 + { 594 + "type": "OpenCollective", 595 + "url": "https://opencollective.com/unified" 596 + } 597 + ], 598 + "dependencies": { 599 + "micromark-util-symbol": "^1.0.0" 600 + } 601 + }, 602 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-resolve-all": { 603 + "version": "1.1.0", 604 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", 605 + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", 606 + "funding": [ 607 + { 608 + "type": "GitHub Sponsors", 609 + "url": "https://github.com/sponsors/unifiedjs" 610 + }, 611 + { 612 + "type": "OpenCollective", 613 + "url": "https://opencollective.com/unified" 614 + } 615 + ], 616 + "dependencies": { 617 + "micromark-util-types": "^1.0.0" 618 + } 619 + }, 620 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-sanitize-uri": { 621 + "version": "1.2.0", 622 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", 623 + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", 624 + "funding": [ 625 + { 626 + "type": "GitHub Sponsors", 627 + "url": "https://github.com/sponsors/unifiedjs" 628 + }, 629 + { 630 + "type": "OpenCollective", 631 + "url": "https://opencollective.com/unified" 632 + } 633 + ], 634 + "dependencies": { 635 + "micromark-util-character": "^1.0.0", 636 + "micromark-util-encode": "^1.0.0", 637 + "micromark-util-symbol": "^1.0.0" 638 + } 639 + }, 640 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-subtokenize": { 641 + "version": "1.1.0", 642 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", 643 + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", 644 + "funding": [ 645 + { 646 + "type": "GitHub Sponsors", 647 + "url": "https://github.com/sponsors/unifiedjs" 648 + }, 649 + { 650 + "type": "OpenCollective", 651 + "url": "https://opencollective.com/unified" 652 + } 653 + ], 654 + "dependencies": { 655 + "micromark-util-chunked": "^1.0.0", 656 + "micromark-util-symbol": "^1.0.0", 657 + "micromark-util-types": "^1.0.0", 658 + "uvu": "^0.5.0" 659 + } 660 + }, 661 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-symbol": { 662 + "version": "1.1.0", 663 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 664 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 665 + "funding": [ 666 + { 667 + "type": "GitHub Sponsors", 668 + "url": "https://github.com/sponsors/unifiedjs" 669 + }, 670 + { 671 + "type": "OpenCollective", 672 + "url": "https://opencollective.com/unified" 673 + } 674 + ] 675 + }, 676 + "node_modules/@mdx-js/mdx/node_modules/micromark-util-types": { 677 + "version": "1.1.0", 678 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 679 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 680 + "funding": [ 681 + { 682 + "type": "GitHub Sponsors", 683 + "url": "https://github.com/sponsors/unifiedjs" 684 + }, 685 + { 686 + "type": "OpenCollective", 687 + "url": "https://opencollective.com/unified" 688 + } 689 + ] 690 + }, 691 + "node_modules/@mdx-js/mdx/node_modules/remark-parse": { 692 + "version": "10.0.2", 693 + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", 694 + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", 695 + "dependencies": { 696 + "@types/mdast": "^3.0.0", 697 + "mdast-util-from-markdown": "^1.0.0", 698 + "unified": "^10.0.0" 699 + }, 700 + "funding": { 701 + "type": "opencollective", 702 + "url": "https://opencollective.com/unified" 703 + } 704 + }, 705 + "node_modules/@mdx-js/react": { 706 + "version": "2.3.0", 707 + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", 708 + "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", 709 + "dependencies": { 710 + "@types/mdx": "^2.0.0", 711 + "@types/react": ">=16" 712 + }, 713 + "funding": { 714 + "type": "opencollective", 715 + "url": "https://opencollective.com/unified" 716 + }, 717 + "peerDependencies": { 718 + "react": ">=16" 719 + } 720 + }, 130 721 "node_modules/@next/env": { 131 - "version": "13.5.6", 132 - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz", 133 - "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==" 722 + "version": "14.0.1-canary.2", 723 + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.1-canary.2.tgz", 724 + "integrity": "sha512-rd3KHpYqKt+pac8m7A7iRifg6wn5fhlQ+Zpp+hK3OWJ45PJIWUa7lJPN91iuie0Os6e/zglH0c79AZNRqGucsQ==" 134 725 }, 135 726 "node_modules/@next/eslint-plugin-next": { 136 727 "version": "13.5.6", ··· 142 733 } 143 734 }, 144 735 "node_modules/@next/swc-darwin-arm64": { 145 - "version": "13.5.6", 146 - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz", 147 - "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==", 736 + "version": "14.0.1-canary.2", 737 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.1-canary.2.tgz", 738 + "integrity": "sha512-FmyL6SbNQeNNyGlywveBL6aVPG2k5SzHb61QI78ZGexPnNUFI26Gdh5KtvMuPMUFlJO3sdaQCmgSU6uc5pUgNg==", 148 739 "cpu": [ 149 740 "arm64" 150 741 ], ··· 157 748 } 158 749 }, 159 750 "node_modules/@next/swc-darwin-x64": { 160 - "version": "13.5.6", 161 - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", 162 - "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", 751 + "version": "14.0.1-canary.2", 752 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.1-canary.2.tgz", 753 + "integrity": "sha512-taL9ueq0mQX3c9nBaFswr/JpAlMS8U/1XhBe4W8Ikaf9RLbv+56/WcKnY3CXGSuENZaJSNG26LmLsFJz2scFWQ==", 163 754 "cpu": [ 164 755 "x64" 165 756 ], ··· 172 763 } 173 764 }, 174 765 "node_modules/@next/swc-linux-arm64-gnu": { 175 - "version": "13.5.6", 176 - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", 177 - "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", 766 + "version": "14.0.1-canary.2", 767 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.1-canary.2.tgz", 768 + "integrity": "sha512-7yFBA3y3yKtLfu5IVQz2jS448iiYz1AVM8M0ejn5hmW1/P54dFFBOLnVw2EJ3AWXbGDw/Se6c04wpvxrGqCvnQ==", 178 769 "cpu": [ 179 770 "arm64" 180 771 ], ··· 187 778 } 188 779 }, 189 780 "node_modules/@next/swc-linux-arm64-musl": { 190 - "version": "13.5.6", 191 - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", 192 - "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", 781 + "version": "14.0.1-canary.2", 782 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.1-canary.2.tgz", 783 + "integrity": "sha512-7kkVdw1SNH9tLGVdBT9BoXh14Qeoxg+KalzrYlU7jwtqaygTI/4rXWC8i6CDa1BVOFmtZpPLc/MUjj3XXsKQjg==", 193 784 "cpu": [ 194 785 "arm64" 195 786 ], ··· 202 793 } 203 794 }, 204 795 "node_modules/@next/swc-linux-x64-gnu": { 205 - "version": "13.5.6", 206 - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", 207 - "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", 796 + "version": "14.0.1-canary.2", 797 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.1-canary.2.tgz", 798 + "integrity": "sha512-1m88jV9iXNqN7Bi8T0w0x8kTPQ0pZS94O6ShwNGmJI6KLmB7JR9Gu5uWhnRTTPg6iohFj+x3Nsw5S6OGi3rjKQ==", 208 799 "cpu": [ 209 800 "x64" 210 801 ], ··· 217 808 } 218 809 }, 219 810 "node_modules/@next/swc-linux-x64-musl": { 220 - "version": "13.5.6", 221 - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", 222 - "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", 811 + "version": "14.0.1-canary.2", 812 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.1-canary.2.tgz", 813 + "integrity": "sha512-1EldiS8Z+DYV9y6dinQb9TDvHKVfC68TspTyCoOrsiDybTaMYkg9HRB3frgXDLgwrT+buDj+MTezZwaR/m3Sng==", 223 814 "cpu": [ 224 815 "x64" 225 816 ], ··· 232 823 } 233 824 }, 234 825 "node_modules/@next/swc-win32-arm64-msvc": { 235 - "version": "13.5.6", 236 - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", 237 - "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", 826 + "version": "14.0.1-canary.2", 827 + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.1-canary.2.tgz", 828 + "integrity": "sha512-x1Wsy3ItbsemqSkxQUbZOwBHaQHOs1v6+vy247KZtNO1wVVBVz773mvYTFBgixgAqw2k43aOubJMUl+jw4EDAA==", 238 829 "cpu": [ 239 830 "arm64" 240 831 ], ··· 247 838 } 248 839 }, 249 840 "node_modules/@next/swc-win32-ia32-msvc": { 250 - "version": "13.5.6", 251 - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", 252 - "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", 841 + "version": "14.0.1-canary.2", 842 + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.1-canary.2.tgz", 843 + "integrity": "sha512-rupAcuUL+QRICYc+MpGoigxpgWFWTuBWp/ZPlo4vPsbkzfST5ItkUCxocFLJvpBtcr7cGz0KFnP3JUc0Be2K/w==", 253 844 "cpu": [ 254 845 "ia32" 255 846 ], ··· 262 853 } 263 854 }, 264 855 "node_modules/@next/swc-win32-x64-msvc": { 265 - "version": "13.5.6", 266 - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", 267 - "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", 856 + "version": "14.0.1-canary.2", 857 + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.1-canary.2.tgz", 858 + "integrity": "sha512-hYojGg4iT4pWri3Car2XU691Z6yJ8/nx9/U5AJmKlReExpaESDQHRvNoqF/gaKIy6+2vR1ybaIgBPHsl95tPvg==", 268 859 "cpu": [ 269 860 "x64" 270 861 ], ··· 325 916 "tslib": "^2.4.0" 326 917 } 327 918 }, 919 + "node_modules/@types/acorn": { 920 + "version": "4.0.6", 921 + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", 922 + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", 923 + "dependencies": { 924 + "@types/estree": "*" 925 + } 926 + }, 927 + "node_modules/@types/debug": { 928 + "version": "4.1.10", 929 + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.10.tgz", 930 + "integrity": "sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==", 931 + "dependencies": { 932 + "@types/ms": "*" 933 + } 934 + }, 935 + "node_modules/@types/estree": { 936 + "version": "1.0.3", 937 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", 938 + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" 939 + }, 940 + "node_modules/@types/estree-jsx": { 941 + "version": "1.0.2", 942 + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.2.tgz", 943 + "integrity": "sha512-GNBWlGBMjiiiL5TSkvPtOteuXsiVitw5MYGY1UYlrAq0SKyczsls6sCD7TZ8fsjRsvCVxml7EbyjJezPb3DrSA==", 944 + "dependencies": { 945 + "@types/estree": "*" 946 + } 947 + }, 948 + "node_modules/@types/hast": { 949 + "version": "2.3.7", 950 + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.7.tgz", 951 + "integrity": "sha512-EVLigw5zInURhzfXUM65eixfadfsHKomGKUakToXo84t8gGIJuTcD2xooM2See7GyQ7DRtYjhCHnSUQez8JaLw==", 952 + "dependencies": { 953 + "@types/unist": "^2" 954 + } 955 + }, 956 + "node_modules/@types/js-yaml": { 957 + "version": "4.0.8", 958 + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.8.tgz", 959 + "integrity": "sha512-m6jnPk1VhlYRiLFm3f8X9Uep761f+CK8mHyS65LutH2OhmBF0BeMEjHgg05usH8PLZMWWc/BUR9RPmkvpWnyRA==" 960 + }, 328 961 "node_modules/@types/json5": { 329 962 "version": "0.0.29", 330 963 "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", 331 964 "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", 332 965 "dev": true 333 966 }, 967 + "node_modules/@types/mdx": { 968 + "version": "2.0.9", 969 + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.9.tgz", 970 + "integrity": "sha512-OKMdj17y8Cs+k1r0XFyp59ChSOwf8ODGtMQ4mnpfz5eFDk1aO41yN3pSKGuvVzmWAkFp37seubY1tzOVpwfWwg==" 971 + }, 972 + "node_modules/@types/ms": { 973 + "version": "0.7.33", 974 + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", 975 + "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==" 976 + }, 977 + "node_modules/@types/prop-types": { 978 + "version": "15.7.9", 979 + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", 980 + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" 981 + }, 982 + "node_modules/@types/react": { 983 + "version": "18.2.33", 984 + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.33.tgz", 985 + "integrity": "sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg==", 986 + "dependencies": { 987 + "@types/prop-types": "*", 988 + "@types/scheduler": "*", 989 + "csstype": "^3.0.2" 990 + } 991 + }, 992 + "node_modules/@types/scheduler": { 993 + "version": "0.16.5", 994 + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", 995 + "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" 996 + }, 997 + "node_modules/@types/unist": { 998 + "version": "2.0.9", 999 + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.9.tgz", 1000 + "integrity": "sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==" 1001 + }, 334 1002 "node_modules/@typescript-eslint/parser": { 335 1003 "version": "6.8.0", 336 1004 "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", ··· 443 1111 "version": "8.10.0", 444 1112 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", 445 1113 "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", 446 - "dev": true, 447 1114 "bin": { 448 1115 "acorn": "bin/acorn" 449 1116 }, ··· 455 1122 "version": "5.3.2", 456 1123 "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 457 1124 "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 458 - "dev": true, 459 1125 "peerDependencies": { 460 1126 "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 461 1127 } ··· 485 1151 "node": ">=8" 486 1152 } 487 1153 }, 1154 + "node_modules/ansi-sequence-parser": { 1155 + "version": "1.1.1", 1156 + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", 1157 + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" 1158 + }, 488 1159 "node_modules/ansi-styles": { 489 1160 "version": "4.3.0", 490 1161 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", ··· 500 1171 "url": "https://github.com/chalk/ansi-styles?sponsor=1" 501 1172 } 502 1173 }, 1174 + "node_modules/any-promise": { 1175 + "version": "1.3.0", 1176 + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 1177 + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", 1178 + "dev": true 1179 + }, 1180 + "node_modules/anymatch": { 1181 + "version": "3.1.3", 1182 + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 1183 + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 1184 + "dev": true, 1185 + "dependencies": { 1186 + "normalize-path": "^3.0.0", 1187 + "picomatch": "^2.0.4" 1188 + }, 1189 + "engines": { 1190 + "node": ">= 8" 1191 + } 1192 + }, 1193 + "node_modules/arg": { 1194 + "version": "5.0.2", 1195 + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", 1196 + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", 1197 + "dev": true 1198 + }, 503 1199 "node_modules/argparse": { 504 1200 "version": "2.0.1", 505 1201 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 506 - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 507 - "dev": true 1202 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 508 1203 }, 509 1204 "node_modules/aria-query": { 510 1205 "version": "5.3.0", ··· 651 1346 "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", 652 1347 "dev": true 653 1348 }, 1349 + "node_modules/astring": { 1350 + "version": "1.8.6", 1351 + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", 1352 + "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", 1353 + "bin": { 1354 + "astring": "bin/astring" 1355 + } 1356 + }, 654 1357 "node_modules/asynciterator.prototype": { 655 1358 "version": "1.0.0", 656 1359 "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", ··· 660 1363 "has-symbols": "^1.0.3" 661 1364 } 662 1365 }, 1366 + "node_modules/autoprefixer": { 1367 + "version": "10.4.16", 1368 + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", 1369 + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", 1370 + "dev": true, 1371 + "funding": [ 1372 + { 1373 + "type": "opencollective", 1374 + "url": "https://opencollective.com/postcss/" 1375 + }, 1376 + { 1377 + "type": "tidelift", 1378 + "url": "https://tidelift.com/funding/github/npm/autoprefixer" 1379 + }, 1380 + { 1381 + "type": "github", 1382 + "url": "https://github.com/sponsors/ai" 1383 + } 1384 + ], 1385 + "dependencies": { 1386 + "browserslist": "^4.21.10", 1387 + "caniuse-lite": "^1.0.30001538", 1388 + "fraction.js": "^4.3.6", 1389 + "normalize-range": "^0.1.2", 1390 + "picocolors": "^1.0.0", 1391 + "postcss-value-parser": "^4.2.0" 1392 + }, 1393 + "bin": { 1394 + "autoprefixer": "bin/autoprefixer" 1395 + }, 1396 + "engines": { 1397 + "node": "^10 || ^12 || >=14" 1398 + }, 1399 + "peerDependencies": { 1400 + "postcss": "^8.1.0" 1401 + } 1402 + }, 663 1403 "node_modules/available-typed-arrays": { 664 1404 "version": "1.0.5", 665 1405 "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", ··· 690 1430 "dequal": "^2.0.3" 691 1431 } 692 1432 }, 1433 + "node_modules/bail": { 1434 + "version": "2.0.2", 1435 + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", 1436 + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", 1437 + "funding": { 1438 + "type": "github", 1439 + "url": "https://github.com/sponsors/wooorm" 1440 + } 1441 + }, 693 1442 "node_modules/balanced-match": { 694 1443 "version": "1.0.2", 695 1444 "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 696 1445 "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 697 1446 "dev": true 698 1447 }, 1448 + "node_modules/binary-extensions": { 1449 + "version": "2.2.0", 1450 + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 1451 + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 1452 + "dev": true, 1453 + "engines": { 1454 + "node": ">=8" 1455 + } 1456 + }, 699 1457 "node_modules/brace-expansion": { 700 1458 "version": "1.1.11", 701 1459 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", ··· 718 1476 "node": ">=8" 719 1477 } 720 1478 }, 1479 + "node_modules/browserslist": { 1480 + "version": "4.22.1", 1481 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", 1482 + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", 1483 + "dev": true, 1484 + "funding": [ 1485 + { 1486 + "type": "opencollective", 1487 + "url": "https://opencollective.com/browserslist" 1488 + }, 1489 + { 1490 + "type": "tidelift", 1491 + "url": "https://tidelift.com/funding/github/npm/browserslist" 1492 + }, 1493 + { 1494 + "type": "github", 1495 + "url": "https://github.com/sponsors/ai" 1496 + } 1497 + ], 1498 + "dependencies": { 1499 + "caniuse-lite": "^1.0.30001541", 1500 + "electron-to-chromium": "^1.4.535", 1501 + "node-releases": "^2.0.13", 1502 + "update-browserslist-db": "^1.0.13" 1503 + }, 1504 + "bin": { 1505 + "browserslist": "cli.js" 1506 + }, 1507 + "engines": { 1508 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1509 + } 1510 + }, 721 1511 "node_modules/busboy": { 722 1512 "version": "1.6.0", 723 1513 "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", ··· 752 1542 "node": ">=6" 753 1543 } 754 1544 }, 1545 + "node_modules/camelcase-css": { 1546 + "version": "2.0.1", 1547 + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", 1548 + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", 1549 + "dev": true, 1550 + "engines": { 1551 + "node": ">= 6" 1552 + } 1553 + }, 755 1554 "node_modules/caniuse-lite": { 756 1555 "version": "1.0.30001551", 757 1556 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", ··· 771 1570 } 772 1571 ] 773 1572 }, 1573 + "node_modules/ccount": { 1574 + "version": "2.0.1", 1575 + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", 1576 + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", 1577 + "funding": { 1578 + "type": "github", 1579 + "url": "https://github.com/sponsors/wooorm" 1580 + } 1581 + }, 774 1582 "node_modules/chalk": { 775 1583 "version": "4.1.2", 776 1584 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", ··· 787 1595 "url": "https://github.com/chalk/chalk?sponsor=1" 788 1596 } 789 1597 }, 1598 + "node_modules/character-entities": { 1599 + "version": "2.0.2", 1600 + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", 1601 + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", 1602 + "funding": { 1603 + "type": "github", 1604 + "url": "https://github.com/sponsors/wooorm" 1605 + } 1606 + }, 1607 + "node_modules/character-entities-html4": { 1608 + "version": "2.1.0", 1609 + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", 1610 + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", 1611 + "funding": { 1612 + "type": "github", 1613 + "url": "https://github.com/sponsors/wooorm" 1614 + } 1615 + }, 1616 + "node_modules/character-entities-legacy": { 1617 + "version": "3.0.0", 1618 + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", 1619 + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", 1620 + "funding": { 1621 + "type": "github", 1622 + "url": "https://github.com/sponsors/wooorm" 1623 + } 1624 + }, 1625 + "node_modules/character-reference-invalid": { 1626 + "version": "2.0.1", 1627 + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", 1628 + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", 1629 + "funding": { 1630 + "type": "github", 1631 + "url": "https://github.com/sponsors/wooorm" 1632 + } 1633 + }, 1634 + "node_modules/chokidar": { 1635 + "version": "3.5.3", 1636 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 1637 + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 1638 + "dev": true, 1639 + "funding": [ 1640 + { 1641 + "type": "individual", 1642 + "url": "https://paulmillr.com/funding/" 1643 + } 1644 + ], 1645 + "dependencies": { 1646 + "anymatch": "~3.1.2", 1647 + "braces": "~3.0.2", 1648 + "glob-parent": "~5.1.2", 1649 + "is-binary-path": "~2.1.0", 1650 + "is-glob": "~4.0.1", 1651 + "normalize-path": "~3.0.0", 1652 + "readdirp": "~3.6.0" 1653 + }, 1654 + "engines": { 1655 + "node": ">= 8.10.0" 1656 + }, 1657 + "optionalDependencies": { 1658 + "fsevents": "~2.3.2" 1659 + } 1660 + }, 1661 + "node_modules/chokidar/node_modules/glob-parent": { 1662 + "version": "5.1.2", 1663 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1664 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1665 + "dev": true, 1666 + "dependencies": { 1667 + "is-glob": "^4.0.1" 1668 + }, 1669 + "engines": { 1670 + "node": ">= 6" 1671 + } 1672 + }, 790 1673 "node_modules/client-only": { 791 1674 "version": "0.0.1", 792 1675 "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", ··· 810 1693 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 811 1694 "dev": true 812 1695 }, 1696 + "node_modules/colorjs.io": { 1697 + "version": "0.4.5", 1698 + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.4.5.tgz", 1699 + "integrity": "sha512-yCtUNCmge7llyfd/Wou19PMAcf5yC3XXhgFoAh6zsO2pGswhUPBaaUh8jzgHnXtXuZyFKzXZNAnyF5i+apICow==" 1700 + }, 1701 + "node_modules/comma-separated-tokens": { 1702 + "version": "2.0.3", 1703 + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", 1704 + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", 1705 + "funding": { 1706 + "type": "github", 1707 + "url": "https://github.com/sponsors/wooorm" 1708 + } 1709 + }, 1710 + "node_modules/commander": { 1711 + "version": "4.1.1", 1712 + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 1713 + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 1714 + "dev": true, 1715 + "engines": { 1716 + "node": ">= 6" 1717 + } 1718 + }, 813 1719 "node_modules/concat-map": { 814 1720 "version": "0.0.1", 815 1721 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", ··· 830 1736 "node": ">= 8" 831 1737 } 832 1738 }, 1739 + "node_modules/cssesc": { 1740 + "version": "3.0.0", 1741 + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 1742 + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 1743 + "dev": true, 1744 + "bin": { 1745 + "cssesc": "bin/cssesc" 1746 + }, 1747 + "engines": { 1748 + "node": ">=4" 1749 + } 1750 + }, 1751 + "node_modules/csstype": { 1752 + "version": "3.1.2", 1753 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", 1754 + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" 1755 + }, 833 1756 "node_modules/damerau-levenshtein": { 834 1757 "version": "1.0.8", 835 1758 "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", ··· 840 1763 "version": "4.3.4", 841 1764 "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 842 1765 "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 843 - "dev": true, 844 1766 "dependencies": { 845 1767 "ms": "2.1.2" 846 1768 }, ··· 853 1775 } 854 1776 } 855 1777 }, 1778 + "node_modules/decode-named-character-reference": { 1779 + "version": "1.0.2", 1780 + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", 1781 + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", 1782 + "dependencies": { 1783 + "character-entities": "^2.0.0" 1784 + }, 1785 + "funding": { 1786 + "type": "github", 1787 + "url": "https://github.com/sponsors/wooorm" 1788 + } 1789 + }, 856 1790 "node_modules/deep-is": { 857 1791 "version": "0.1.4", 858 1792 "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", ··· 894 1828 "version": "2.0.3", 895 1829 "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 896 1830 "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 897 - "dev": true, 898 1831 "engines": { 899 1832 "node": ">=6" 900 1833 } 901 1834 }, 1835 + "node_modules/didyoumean": { 1836 + "version": "1.2.2", 1837 + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", 1838 + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", 1839 + "dev": true 1840 + }, 1841 + "node_modules/diff": { 1842 + "version": "5.1.0", 1843 + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", 1844 + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", 1845 + "engines": { 1846 + "node": ">=0.3.1" 1847 + } 1848 + }, 902 1849 "node_modules/dir-glob": { 903 1850 "version": "3.0.1", 904 1851 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", ··· 911 1858 "node": ">=8" 912 1859 } 913 1860 }, 1861 + "node_modules/dlv": { 1862 + "version": "1.1.3", 1863 + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 1864 + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", 1865 + "dev": true 1866 + }, 914 1867 "node_modules/doctrine": { 915 1868 "version": "3.0.0", 916 1869 "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", ··· 922 1875 "engines": { 923 1876 "node": ">=6.0.0" 924 1877 } 1878 + }, 1879 + "node_modules/electron-to-chromium": { 1880 + "version": "1.4.569", 1881 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.569.tgz", 1882 + "integrity": "sha512-LsrJjZ0IbVy12ApW3gpYpcmHS3iRxH4bkKOW98y1/D+3cvDUWGcbzbsFinfUS8knpcZk/PG/2p/RnkMCYN7PVg==", 1883 + "dev": true 925 1884 }, 926 1885 "node_modules/emoji-regex": { 927 1886 "version": "9.2.2", ··· 1055 2014 }, 1056 2015 "funding": { 1057 2016 "url": "https://github.com/sponsors/ljharb" 2017 + } 2018 + }, 2019 + "node_modules/escalade": { 2020 + "version": "3.1.1", 2021 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 2022 + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 2023 + "dev": true, 2024 + "engines": { 2025 + "node": ">=6" 1058 2026 } 1059 2027 }, 1060 2028 "node_modules/escape-string-regexp": { ··· 1446 2414 "url": "https://opencollective.com/eslint" 1447 2415 } 1448 2416 }, 2417 + "node_modules/esprima": { 2418 + "version": "4.0.1", 2419 + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 2420 + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 2421 + "bin": { 2422 + "esparse": "bin/esparse.js", 2423 + "esvalidate": "bin/esvalidate.js" 2424 + }, 2425 + "engines": { 2426 + "node": ">=4" 2427 + } 2428 + }, 1449 2429 "node_modules/esquery": { 1450 2430 "version": "1.5.0", 1451 2431 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", ··· 1479 2459 "node": ">=4.0" 1480 2460 } 1481 2461 }, 2462 + "node_modules/estree-util-attach-comments": { 2463 + "version": "2.1.1", 2464 + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-2.1.1.tgz", 2465 + "integrity": "sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==", 2466 + "dependencies": { 2467 + "@types/estree": "^1.0.0" 2468 + }, 2469 + "funding": { 2470 + "type": "opencollective", 2471 + "url": "https://opencollective.com/unified" 2472 + } 2473 + }, 2474 + "node_modules/estree-util-build-jsx": { 2475 + "version": "2.2.2", 2476 + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.2.tgz", 2477 + "integrity": "sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==", 2478 + "dependencies": { 2479 + "@types/estree-jsx": "^1.0.0", 2480 + "estree-util-is-identifier-name": "^2.0.0", 2481 + "estree-walker": "^3.0.0" 2482 + }, 2483 + "funding": { 2484 + "type": "opencollective", 2485 + "url": "https://opencollective.com/unified" 2486 + } 2487 + }, 2488 + "node_modules/estree-util-is-identifier-name": { 2489 + "version": "2.1.0", 2490 + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.1.0.tgz", 2491 + "integrity": "sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==", 2492 + "funding": { 2493 + "type": "opencollective", 2494 + "url": "https://opencollective.com/unified" 2495 + } 2496 + }, 2497 + "node_modules/estree-util-to-js": { 2498 + "version": "1.2.0", 2499 + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-1.2.0.tgz", 2500 + "integrity": "sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==", 2501 + "dependencies": { 2502 + "@types/estree-jsx": "^1.0.0", 2503 + "astring": "^1.8.0", 2504 + "source-map": "^0.7.0" 2505 + }, 2506 + "funding": { 2507 + "type": "opencollective", 2508 + "url": "https://opencollective.com/unified" 2509 + } 2510 + }, 2511 + "node_modules/estree-util-visit": { 2512 + "version": "1.2.1", 2513 + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-1.2.1.tgz", 2514 + "integrity": "sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==", 2515 + "dependencies": { 2516 + "@types/estree-jsx": "^1.0.0", 2517 + "@types/unist": "^2.0.0" 2518 + }, 2519 + "funding": { 2520 + "type": "opencollective", 2521 + "url": "https://opencollective.com/unified" 2522 + } 2523 + }, 2524 + "node_modules/estree-walker": { 2525 + "version": "3.0.3", 2526 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 2527 + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 2528 + "dependencies": { 2529 + "@types/estree": "^1.0.0" 2530 + } 2531 + }, 1482 2532 "node_modules/esutils": { 1483 2533 "version": "2.0.3", 1484 2534 "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1485 2535 "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1486 2536 "dev": true, 2537 + "engines": { 2538 + "node": ">=0.10.0" 2539 + } 2540 + }, 2541 + "node_modules/extend": { 2542 + "version": "3.0.2", 2543 + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 2544 + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 2545 + }, 2546 + "node_modules/extend-shallow": { 2547 + "version": "2.0.1", 2548 + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 2549 + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", 2550 + "dependencies": { 2551 + "is-extendable": "^0.1.0" 2552 + }, 1487 2553 "engines": { 1488 2554 "node": ">=0.10.0" 1489 2555 } ··· 1610 2676 "dev": true, 1611 2677 "dependencies": { 1612 2678 "is-callable": "^1.1.3" 2679 + } 2680 + }, 2681 + "node_modules/fraction.js": { 2682 + "version": "4.3.7", 2683 + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", 2684 + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", 2685 + "dev": true, 2686 + "engines": { 2687 + "node": "*" 2688 + }, 2689 + "funding": { 2690 + "type": "patreon", 2691 + "url": "https://github.com/sponsors/rawify" 1613 2692 } 1614 2693 }, 1615 2694 "node_modules/fs.realpath": { ··· 1618 2697 "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1619 2698 "dev": true 1620 2699 }, 2700 + "node_modules/fsevents": { 2701 + "version": "2.3.3", 2702 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 2703 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 2704 + "dev": true, 2705 + "hasInstallScript": true, 2706 + "optional": true, 2707 + "os": [ 2708 + "darwin" 2709 + ], 2710 + "engines": { 2711 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2712 + } 2713 + }, 1621 2714 "node_modules/function-bind": { 1622 2715 "version": "1.1.2", 1623 2716 "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", ··· 1807 2900 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1808 2901 "dev": true 1809 2902 }, 2903 + "node_modules/gray-matter": { 2904 + "version": "4.0.3", 2905 + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", 2906 + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", 2907 + "dependencies": { 2908 + "js-yaml": "^3.13.1", 2909 + "kind-of": "^6.0.2", 2910 + "section-matter": "^1.0.0", 2911 + "strip-bom-string": "^1.0.0" 2912 + }, 2913 + "engines": { 2914 + "node": ">=6.0" 2915 + } 2916 + }, 2917 + "node_modules/gray-matter/node_modules/argparse": { 2918 + "version": "1.0.10", 2919 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 2920 + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 2921 + "dependencies": { 2922 + "sprintf-js": "~1.0.2" 2923 + } 2924 + }, 2925 + "node_modules/gray-matter/node_modules/js-yaml": { 2926 + "version": "3.14.1", 2927 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 2928 + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 2929 + "dependencies": { 2930 + "argparse": "^1.0.7", 2931 + "esprima": "^4.0.0" 2932 + }, 2933 + "bin": { 2934 + "js-yaml": "bin/js-yaml.js" 2935 + } 2936 + }, 1810 2937 "node_modules/has": { 1811 2938 "version": "1.0.4", 1812 2939 "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", ··· 1885 3012 "url": "https://github.com/sponsors/ljharb" 1886 3013 } 1887 3014 }, 3015 + "node_modules/hash-obj": { 3016 + "version": "4.0.0", 3017 + "resolved": "https://registry.npmjs.org/hash-obj/-/hash-obj-4.0.0.tgz", 3018 + "integrity": "sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==", 3019 + "dependencies": { 3020 + "is-obj": "^3.0.0", 3021 + "sort-keys": "^5.0.0", 3022 + "type-fest": "^1.0.2" 3023 + }, 3024 + "engines": { 3025 + "node": ">=12" 3026 + }, 3027 + "funding": { 3028 + "url": "https://github.com/sponsors/sindresorhus" 3029 + } 3030 + }, 3031 + "node_modules/hash-obj/node_modules/type-fest": { 3032 + "version": "1.4.0", 3033 + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", 3034 + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", 3035 + "engines": { 3036 + "node": ">=10" 3037 + }, 3038 + "funding": { 3039 + "url": "https://github.com/sponsors/sindresorhus" 3040 + } 3041 + }, 1888 3042 "node_modules/hasown": { 1889 3043 "version": "2.0.0", 1890 3044 "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", ··· 1897 3051 "node": ">= 0.4" 1898 3052 } 1899 3053 }, 3054 + "node_modules/hast-util-from-parse5": { 3055 + "version": "7.1.2", 3056 + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", 3057 + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", 3058 + "dependencies": { 3059 + "@types/hast": "^2.0.0", 3060 + "@types/unist": "^2.0.0", 3061 + "hastscript": "^7.0.0", 3062 + "property-information": "^6.0.0", 3063 + "vfile": "^5.0.0", 3064 + "vfile-location": "^4.0.0", 3065 + "web-namespaces": "^2.0.0" 3066 + }, 3067 + "funding": { 3068 + "type": "opencollective", 3069 + "url": "https://opencollective.com/unified" 3070 + } 3071 + }, 3072 + "node_modules/hast-util-parse-selector": { 3073 + "version": "3.1.1", 3074 + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", 3075 + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", 3076 + "dependencies": { 3077 + "@types/hast": "^2.0.0" 3078 + }, 3079 + "funding": { 3080 + "type": "opencollective", 3081 + "url": "https://opencollective.com/unified" 3082 + } 3083 + }, 3084 + "node_modules/hast-util-to-estree": { 3085 + "version": "2.3.3", 3086 + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-2.3.3.tgz", 3087 + "integrity": "sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==", 3088 + "dependencies": { 3089 + "@types/estree": "^1.0.0", 3090 + "@types/estree-jsx": "^1.0.0", 3091 + "@types/hast": "^2.0.0", 3092 + "@types/unist": "^2.0.0", 3093 + "comma-separated-tokens": "^2.0.0", 3094 + "estree-util-attach-comments": "^2.0.0", 3095 + "estree-util-is-identifier-name": "^2.0.0", 3096 + "hast-util-whitespace": "^2.0.0", 3097 + "mdast-util-mdx-expression": "^1.0.0", 3098 + "mdast-util-mdxjs-esm": "^1.0.0", 3099 + "property-information": "^6.0.0", 3100 + "space-separated-tokens": "^2.0.0", 3101 + "style-to-object": "^0.4.1", 3102 + "unist-util-position": "^4.0.0", 3103 + "zwitch": "^2.0.0" 3104 + }, 3105 + "funding": { 3106 + "type": "opencollective", 3107 + "url": "https://opencollective.com/unified" 3108 + } 3109 + }, 3110 + "node_modules/hast-util-to-estree/node_modules/hast-util-whitespace": { 3111 + "version": "2.0.1", 3112 + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", 3113 + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", 3114 + "funding": { 3115 + "type": "opencollective", 3116 + "url": "https://opencollective.com/unified" 3117 + } 3118 + }, 3119 + "node_modules/hast-util-to-estree/node_modules/unist-util-position": { 3120 + "version": "4.0.4", 3121 + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", 3122 + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", 3123 + "dependencies": { 3124 + "@types/unist": "^2.0.0" 3125 + }, 3126 + "funding": { 3127 + "type": "opencollective", 3128 + "url": "https://opencollective.com/unified" 3129 + } 3130 + }, 3131 + "node_modules/hast-util-to-string": { 3132 + "version": "2.0.0", 3133 + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", 3134 + "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", 3135 + "dependencies": { 3136 + "@types/hast": "^2.0.0" 3137 + }, 3138 + "funding": { 3139 + "type": "opencollective", 3140 + "url": "https://opencollective.com/unified" 3141 + } 3142 + }, 3143 + "node_modules/hastscript": { 3144 + "version": "7.2.0", 3145 + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", 3146 + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", 3147 + "dependencies": { 3148 + "@types/hast": "^2.0.0", 3149 + "comma-separated-tokens": "^2.0.0", 3150 + "hast-util-parse-selector": "^3.0.0", 3151 + "property-information": "^6.0.0", 3152 + "space-separated-tokens": "^2.0.0" 3153 + }, 3154 + "funding": { 3155 + "type": "opencollective", 3156 + "url": "https://opencollective.com/unified" 3157 + } 3158 + }, 1900 3159 "node_modules/ignore": { 1901 3160 "version": "5.2.4", 1902 3161 "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", ··· 1947 3206 "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1948 3207 "dev": true 1949 3208 }, 3209 + "node_modules/inline-style-parser": { 3210 + "version": "0.1.1", 3211 + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", 3212 + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" 3213 + }, 1950 3214 "node_modules/internal-slot": { 1951 3215 "version": "1.0.6", 1952 3216 "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", ··· 1959 3223 }, 1960 3224 "engines": { 1961 3225 "node": ">= 0.4" 3226 + } 3227 + }, 3228 + "node_modules/is-alphabetical": { 3229 + "version": "2.0.1", 3230 + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", 3231 + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", 3232 + "funding": { 3233 + "type": "github", 3234 + "url": "https://github.com/sponsors/wooorm" 3235 + } 3236 + }, 3237 + "node_modules/is-alphanumerical": { 3238 + "version": "2.0.1", 3239 + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", 3240 + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", 3241 + "dependencies": { 3242 + "is-alphabetical": "^2.0.0", 3243 + "is-decimal": "^2.0.0" 3244 + }, 3245 + "funding": { 3246 + "type": "github", 3247 + "url": "https://github.com/sponsors/wooorm" 1962 3248 } 1963 3249 }, 1964 3250 "node_modules/is-array-buffer": { ··· 2002 3288 "url": "https://github.com/sponsors/ljharb" 2003 3289 } 2004 3290 }, 3291 + "node_modules/is-binary-path": { 3292 + "version": "2.1.0", 3293 + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 3294 + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 3295 + "dev": true, 3296 + "dependencies": { 3297 + "binary-extensions": "^2.0.0" 3298 + }, 3299 + "engines": { 3300 + "node": ">=8" 3301 + } 3302 + }, 2005 3303 "node_modules/is-boolean-object": { 2006 3304 "version": "1.1.2", 2007 3305 "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", ··· 2018 3316 "url": "https://github.com/sponsors/ljharb" 2019 3317 } 2020 3318 }, 3319 + "node_modules/is-buffer": { 3320 + "version": "2.0.5", 3321 + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", 3322 + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", 3323 + "funding": [ 3324 + { 3325 + "type": "github", 3326 + "url": "https://github.com/sponsors/feross" 3327 + }, 3328 + { 3329 + "type": "patreon", 3330 + "url": "https://www.patreon.com/feross" 3331 + }, 3332 + { 3333 + "type": "consulting", 3334 + "url": "https://feross.org/support" 3335 + } 3336 + ], 3337 + "engines": { 3338 + "node": ">=4" 3339 + } 3340 + }, 2021 3341 "node_modules/is-callable": { 2022 3342 "version": "1.2.7", 2023 3343 "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", ··· 2057 3377 "url": "https://github.com/sponsors/ljharb" 2058 3378 } 2059 3379 }, 3380 + "node_modules/is-decimal": { 3381 + "version": "2.0.1", 3382 + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", 3383 + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", 3384 + "funding": { 3385 + "type": "github", 3386 + "url": "https://github.com/sponsors/wooorm" 3387 + } 3388 + }, 3389 + "node_modules/is-extendable": { 3390 + "version": "0.1.1", 3391 + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 3392 + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", 3393 + "engines": { 3394 + "node": ">=0.10.0" 3395 + } 3396 + }, 2060 3397 "node_modules/is-extglob": { 2061 3398 "version": "2.1.1", 2062 3399 "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", ··· 2105 3442 "node": ">=0.10.0" 2106 3443 } 2107 3444 }, 3445 + "node_modules/is-hexadecimal": { 3446 + "version": "2.0.1", 3447 + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", 3448 + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", 3449 + "funding": { 3450 + "type": "github", 3451 + "url": "https://github.com/sponsors/wooorm" 3452 + } 3453 + }, 2108 3454 "node_modules/is-map": { 2109 3455 "version": "2.0.2", 2110 3456 "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", ··· 2150 3496 "url": "https://github.com/sponsors/ljharb" 2151 3497 } 2152 3498 }, 3499 + "node_modules/is-obj": { 3500 + "version": "3.0.0", 3501 + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", 3502 + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", 3503 + "engines": { 3504 + "node": ">=12" 3505 + }, 3506 + "funding": { 3507 + "url": "https://github.com/sponsors/sindresorhus" 3508 + } 3509 + }, 2153 3510 "node_modules/is-path-inside": { 2154 3511 "version": "3.0.3", 2155 3512 "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", ··· 2159 3516 "node": ">=8" 2160 3517 } 2161 3518 }, 3519 + "node_modules/is-plain-obj": { 3520 + "version": "4.1.0", 3521 + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", 3522 + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", 3523 + "engines": { 3524 + "node": ">=12" 3525 + }, 3526 + "funding": { 3527 + "url": "https://github.com/sponsors/sindresorhus" 3528 + } 3529 + }, 3530 + "node_modules/is-reference": { 3531 + "version": "3.0.2", 3532 + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", 3533 + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", 3534 + "dependencies": { 3535 + "@types/estree": "*" 3536 + } 3537 + }, 2162 3538 "node_modules/is-regex": { 2163 3539 "version": "1.1.4", 2164 3540 "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", ··· 2300 3676 "set-function-name": "^2.0.1" 2301 3677 } 2302 3678 }, 3679 + "node_modules/jiti": { 3680 + "version": "1.20.0", 3681 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", 3682 + "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", 3683 + "dev": true, 3684 + "bin": { 3685 + "jiti": "bin/jiti.js" 3686 + } 3687 + }, 2303 3688 "node_modules/js-tokens": { 2304 3689 "version": "4.0.0", 2305 3690 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", ··· 2309 3694 "version": "4.1.0", 2310 3695 "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 2311 3696 "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 2312 - "dev": true, 2313 3697 "dependencies": { 2314 3698 "argparse": "^2.0.1" 2315 3699 }, ··· 2347 3731 "json5": "lib/cli.js" 2348 3732 } 2349 3733 }, 3734 + "node_modules/jsonc-parser": { 3735 + "version": "3.2.0", 3736 + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", 3737 + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" 3738 + }, 2350 3739 "node_modules/jsx-ast-utils": { 2351 3740 "version": "3.3.5", 2352 3741 "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", ··· 2371 3760 "json-buffer": "3.0.1" 2372 3761 } 2373 3762 }, 3763 + "node_modules/kind-of": { 3764 + "version": "6.0.3", 3765 + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 3766 + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 3767 + "engines": { 3768 + "node": ">=0.10.0" 3769 + } 3770 + }, 3771 + "node_modules/kleur": { 3772 + "version": "4.1.5", 3773 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 3774 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 3775 + "engines": { 3776 + "node": ">=6" 3777 + } 3778 + }, 2374 3779 "node_modules/language-subtag-registry": { 2375 3780 "version": "0.3.22", 2376 3781 "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", ··· 2399 3804 "node": ">= 0.8.0" 2400 3805 } 2401 3806 }, 3807 + "node_modules/lilconfig": { 3808 + "version": "2.1.0", 3809 + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", 3810 + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", 3811 + "dev": true, 3812 + "engines": { 3813 + "node": ">=10" 3814 + } 3815 + }, 3816 + "node_modules/lines-and-columns": { 3817 + "version": "1.2.4", 3818 + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 3819 + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 3820 + "dev": true 3821 + }, 2402 3822 "node_modules/locate-path": { 2403 3823 "version": "6.0.0", 2404 3824 "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", ··· 2419 3839 "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 2420 3840 "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 2421 3841 "dev": true 3842 + }, 3843 + "node_modules/longest-streak": { 3844 + "version": "3.1.0", 3845 + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", 3846 + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", 3847 + "funding": { 3848 + "type": "github", 3849 + "url": "https://github.com/sponsors/wooorm" 3850 + } 2422 3851 }, 2423 3852 "node_modules/loose-envify": { 2424 3853 "version": "1.4.0", ··· 2443 3872 "node": ">=10" 2444 3873 } 2445 3874 }, 3875 + "node_modules/markdown-extensions": { 3876 + "version": "1.1.1", 3877 + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", 3878 + "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==", 3879 + "engines": { 3880 + "node": ">=0.10.0" 3881 + } 3882 + }, 3883 + "node_modules/mdast-util-definitions": { 3884 + "version": "5.1.2", 3885 + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", 3886 + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", 3887 + "dependencies": { 3888 + "@types/mdast": "^3.0.0", 3889 + "@types/unist": "^2.0.0", 3890 + "unist-util-visit": "^4.0.0" 3891 + }, 3892 + "funding": { 3893 + "type": "opencollective", 3894 + "url": "https://opencollective.com/unified" 3895 + } 3896 + }, 3897 + "node_modules/mdast-util-definitions/node_modules/@types/mdast": { 3898 + "version": "3.0.14", 3899 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 3900 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 3901 + "dependencies": { 3902 + "@types/unist": "^2" 3903 + } 3904 + }, 3905 + "node_modules/mdast-util-mdx": { 3906 + "version": "2.0.1", 3907 + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-2.0.1.tgz", 3908 + "integrity": "sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==", 3909 + "dependencies": { 3910 + "mdast-util-from-markdown": "^1.0.0", 3911 + "mdast-util-mdx-expression": "^1.0.0", 3912 + "mdast-util-mdx-jsx": "^2.0.0", 3913 + "mdast-util-mdxjs-esm": "^1.0.0", 3914 + "mdast-util-to-markdown": "^1.0.0" 3915 + }, 3916 + "funding": { 3917 + "type": "opencollective", 3918 + "url": "https://opencollective.com/unified" 3919 + } 3920 + }, 3921 + "node_modules/mdast-util-mdx-expression": { 3922 + "version": "1.3.2", 3923 + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.3.2.tgz", 3924 + "integrity": "sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==", 3925 + "dependencies": { 3926 + "@types/estree-jsx": "^1.0.0", 3927 + "@types/hast": "^2.0.0", 3928 + "@types/mdast": "^3.0.0", 3929 + "mdast-util-from-markdown": "^1.0.0", 3930 + "mdast-util-to-markdown": "^1.0.0" 3931 + }, 3932 + "funding": { 3933 + "type": "opencollective", 3934 + "url": "https://opencollective.com/unified" 3935 + } 3936 + }, 3937 + "node_modules/mdast-util-mdx-expression/node_modules/@types/mdast": { 3938 + "version": "3.0.14", 3939 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 3940 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 3941 + "dependencies": { 3942 + "@types/unist": "^2" 3943 + } 3944 + }, 3945 + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-from-markdown": { 3946 + "version": "1.3.1", 3947 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", 3948 + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", 3949 + "dependencies": { 3950 + "@types/mdast": "^3.0.0", 3951 + "@types/unist": "^2.0.0", 3952 + "decode-named-character-reference": "^1.0.0", 3953 + "mdast-util-to-string": "^3.1.0", 3954 + "micromark": "^3.0.0", 3955 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 3956 + "micromark-util-decode-string": "^1.0.0", 3957 + "micromark-util-normalize-identifier": "^1.0.0", 3958 + "micromark-util-symbol": "^1.0.0", 3959 + "micromark-util-types": "^1.0.0", 3960 + "unist-util-stringify-position": "^3.0.0", 3961 + "uvu": "^0.5.0" 3962 + }, 3963 + "funding": { 3964 + "type": "opencollective", 3965 + "url": "https://opencollective.com/unified" 3966 + } 3967 + }, 3968 + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-phrasing": { 3969 + "version": "3.0.1", 3970 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", 3971 + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", 3972 + "dependencies": { 3973 + "@types/mdast": "^3.0.0", 3974 + "unist-util-is": "^5.0.0" 3975 + }, 3976 + "funding": { 3977 + "type": "opencollective", 3978 + "url": "https://opencollective.com/unified" 3979 + } 3980 + }, 3981 + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-markdown": { 3982 + "version": "1.5.0", 3983 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", 3984 + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", 3985 + "dependencies": { 3986 + "@types/mdast": "^3.0.0", 3987 + "@types/unist": "^2.0.0", 3988 + "longest-streak": "^3.0.0", 3989 + "mdast-util-phrasing": "^3.0.0", 3990 + "mdast-util-to-string": "^3.0.0", 3991 + "micromark-util-decode-string": "^1.0.0", 3992 + "unist-util-visit": "^4.0.0", 3993 + "zwitch": "^2.0.0" 3994 + }, 3995 + "funding": { 3996 + "type": "opencollective", 3997 + "url": "https://opencollective.com/unified" 3998 + } 3999 + }, 4000 + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-string": { 4001 + "version": "3.2.0", 4002 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", 4003 + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", 4004 + "dependencies": { 4005 + "@types/mdast": "^3.0.0" 4006 + }, 4007 + "funding": { 4008 + "type": "opencollective", 4009 + "url": "https://opencollective.com/unified" 4010 + } 4011 + }, 4012 + "node_modules/mdast-util-mdx-expression/node_modules/micromark": { 4013 + "version": "3.2.0", 4014 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", 4015 + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", 4016 + "funding": [ 4017 + { 4018 + "type": "GitHub Sponsors", 4019 + "url": "https://github.com/sponsors/unifiedjs" 4020 + }, 4021 + { 4022 + "type": "OpenCollective", 4023 + "url": "https://opencollective.com/unified" 4024 + } 4025 + ], 4026 + "dependencies": { 4027 + "@types/debug": "^4.0.0", 4028 + "debug": "^4.0.0", 4029 + "decode-named-character-reference": "^1.0.0", 4030 + "micromark-core-commonmark": "^1.0.1", 4031 + "micromark-factory-space": "^1.0.0", 4032 + "micromark-util-character": "^1.0.0", 4033 + "micromark-util-chunked": "^1.0.0", 4034 + "micromark-util-combine-extensions": "^1.0.0", 4035 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 4036 + "micromark-util-encode": "^1.0.0", 4037 + "micromark-util-normalize-identifier": "^1.0.0", 4038 + "micromark-util-resolve-all": "^1.0.0", 4039 + "micromark-util-sanitize-uri": "^1.0.0", 4040 + "micromark-util-subtokenize": "^1.0.0", 4041 + "micromark-util-symbol": "^1.0.0", 4042 + "micromark-util-types": "^1.0.1", 4043 + "uvu": "^0.5.0" 4044 + } 4045 + }, 4046 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-core-commonmark": { 4047 + "version": "1.1.0", 4048 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", 4049 + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", 4050 + "funding": [ 4051 + { 4052 + "type": "GitHub Sponsors", 4053 + "url": "https://github.com/sponsors/unifiedjs" 4054 + }, 4055 + { 4056 + "type": "OpenCollective", 4057 + "url": "https://opencollective.com/unified" 4058 + } 4059 + ], 4060 + "dependencies": { 4061 + "decode-named-character-reference": "^1.0.0", 4062 + "micromark-factory-destination": "^1.0.0", 4063 + "micromark-factory-label": "^1.0.0", 4064 + "micromark-factory-space": "^1.0.0", 4065 + "micromark-factory-title": "^1.0.0", 4066 + "micromark-factory-whitespace": "^1.0.0", 4067 + "micromark-util-character": "^1.0.0", 4068 + "micromark-util-chunked": "^1.0.0", 4069 + "micromark-util-classify-character": "^1.0.0", 4070 + "micromark-util-html-tag-name": "^1.0.0", 4071 + "micromark-util-normalize-identifier": "^1.0.0", 4072 + "micromark-util-resolve-all": "^1.0.0", 4073 + "micromark-util-subtokenize": "^1.0.0", 4074 + "micromark-util-symbol": "^1.0.0", 4075 + "micromark-util-types": "^1.0.1", 4076 + "uvu": "^0.5.0" 4077 + } 4078 + }, 4079 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-destination": { 4080 + "version": "1.1.0", 4081 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", 4082 + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", 4083 + "funding": [ 4084 + { 4085 + "type": "GitHub Sponsors", 4086 + "url": "https://github.com/sponsors/unifiedjs" 4087 + }, 4088 + { 4089 + "type": "OpenCollective", 4090 + "url": "https://opencollective.com/unified" 4091 + } 4092 + ], 4093 + "dependencies": { 4094 + "micromark-util-character": "^1.0.0", 4095 + "micromark-util-symbol": "^1.0.0", 4096 + "micromark-util-types": "^1.0.0" 4097 + } 4098 + }, 4099 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-label": { 4100 + "version": "1.1.0", 4101 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", 4102 + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", 4103 + "funding": [ 4104 + { 4105 + "type": "GitHub Sponsors", 4106 + "url": "https://github.com/sponsors/unifiedjs" 4107 + }, 4108 + { 4109 + "type": "OpenCollective", 4110 + "url": "https://opencollective.com/unified" 4111 + } 4112 + ], 4113 + "dependencies": { 4114 + "micromark-util-character": "^1.0.0", 4115 + "micromark-util-symbol": "^1.0.0", 4116 + "micromark-util-types": "^1.0.0", 4117 + "uvu": "^0.5.0" 4118 + } 4119 + }, 4120 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-space": { 4121 + "version": "1.1.0", 4122 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 4123 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 4124 + "funding": [ 4125 + { 4126 + "type": "GitHub Sponsors", 4127 + "url": "https://github.com/sponsors/unifiedjs" 4128 + }, 4129 + { 4130 + "type": "OpenCollective", 4131 + "url": "https://opencollective.com/unified" 4132 + } 4133 + ], 4134 + "dependencies": { 4135 + "micromark-util-character": "^1.0.0", 4136 + "micromark-util-types": "^1.0.0" 4137 + } 4138 + }, 4139 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-title": { 4140 + "version": "1.1.0", 4141 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", 4142 + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", 4143 + "funding": [ 4144 + { 4145 + "type": "GitHub Sponsors", 4146 + "url": "https://github.com/sponsors/unifiedjs" 4147 + }, 4148 + { 4149 + "type": "OpenCollective", 4150 + "url": "https://opencollective.com/unified" 4151 + } 4152 + ], 4153 + "dependencies": { 4154 + "micromark-factory-space": "^1.0.0", 4155 + "micromark-util-character": "^1.0.0", 4156 + "micromark-util-symbol": "^1.0.0", 4157 + "micromark-util-types": "^1.0.0" 4158 + } 4159 + }, 4160 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-whitespace": { 4161 + "version": "1.1.0", 4162 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", 4163 + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", 4164 + "funding": [ 4165 + { 4166 + "type": "GitHub Sponsors", 4167 + "url": "https://github.com/sponsors/unifiedjs" 4168 + }, 4169 + { 4170 + "type": "OpenCollective", 4171 + "url": "https://opencollective.com/unified" 4172 + } 4173 + ], 4174 + "dependencies": { 4175 + "micromark-factory-space": "^1.0.0", 4176 + "micromark-util-character": "^1.0.0", 4177 + "micromark-util-symbol": "^1.0.0", 4178 + "micromark-util-types": "^1.0.0" 4179 + } 4180 + }, 4181 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-character": { 4182 + "version": "1.2.0", 4183 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 4184 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 4185 + "funding": [ 4186 + { 4187 + "type": "GitHub Sponsors", 4188 + "url": "https://github.com/sponsors/unifiedjs" 4189 + }, 4190 + { 4191 + "type": "OpenCollective", 4192 + "url": "https://opencollective.com/unified" 4193 + } 4194 + ], 4195 + "dependencies": { 4196 + "micromark-util-symbol": "^1.0.0", 4197 + "micromark-util-types": "^1.0.0" 4198 + } 4199 + }, 4200 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-chunked": { 4201 + "version": "1.1.0", 4202 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 4203 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 4204 + "funding": [ 4205 + { 4206 + "type": "GitHub Sponsors", 4207 + "url": "https://github.com/sponsors/unifiedjs" 4208 + }, 4209 + { 4210 + "type": "OpenCollective", 4211 + "url": "https://opencollective.com/unified" 4212 + } 4213 + ], 4214 + "dependencies": { 4215 + "micromark-util-symbol": "^1.0.0" 4216 + } 4217 + }, 4218 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-classify-character": { 4219 + "version": "1.1.0", 4220 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", 4221 + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", 4222 + "funding": [ 4223 + { 4224 + "type": "GitHub Sponsors", 4225 + "url": "https://github.com/sponsors/unifiedjs" 4226 + }, 4227 + { 4228 + "type": "OpenCollective", 4229 + "url": "https://opencollective.com/unified" 4230 + } 4231 + ], 4232 + "dependencies": { 4233 + "micromark-util-character": "^1.0.0", 4234 + "micromark-util-symbol": "^1.0.0", 4235 + "micromark-util-types": "^1.0.0" 4236 + } 4237 + }, 4238 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-combine-extensions": { 4239 + "version": "1.1.0", 4240 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", 4241 + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", 4242 + "funding": [ 4243 + { 4244 + "type": "GitHub Sponsors", 4245 + "url": "https://github.com/sponsors/unifiedjs" 4246 + }, 4247 + { 4248 + "type": "OpenCollective", 4249 + "url": "https://opencollective.com/unified" 4250 + } 4251 + ], 4252 + "dependencies": { 4253 + "micromark-util-chunked": "^1.0.0", 4254 + "micromark-util-types": "^1.0.0" 4255 + } 4256 + }, 4257 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-decode-numeric-character-reference": { 4258 + "version": "1.1.0", 4259 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", 4260 + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", 4261 + "funding": [ 4262 + { 4263 + "type": "GitHub Sponsors", 4264 + "url": "https://github.com/sponsors/unifiedjs" 4265 + }, 4266 + { 4267 + "type": "OpenCollective", 4268 + "url": "https://opencollective.com/unified" 4269 + } 4270 + ], 4271 + "dependencies": { 4272 + "micromark-util-symbol": "^1.0.0" 4273 + } 4274 + }, 4275 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-decode-string": { 4276 + "version": "1.1.0", 4277 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", 4278 + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", 4279 + "funding": [ 4280 + { 4281 + "type": "GitHub Sponsors", 4282 + "url": "https://github.com/sponsors/unifiedjs" 4283 + }, 4284 + { 4285 + "type": "OpenCollective", 4286 + "url": "https://opencollective.com/unified" 4287 + } 4288 + ], 4289 + "dependencies": { 4290 + "decode-named-character-reference": "^1.0.0", 4291 + "micromark-util-character": "^1.0.0", 4292 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 4293 + "micromark-util-symbol": "^1.0.0" 4294 + } 4295 + }, 4296 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-encode": { 4297 + "version": "1.1.0", 4298 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", 4299 + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", 4300 + "funding": [ 4301 + { 4302 + "type": "GitHub Sponsors", 4303 + "url": "https://github.com/sponsors/unifiedjs" 4304 + }, 4305 + { 4306 + "type": "OpenCollective", 4307 + "url": "https://opencollective.com/unified" 4308 + } 4309 + ] 4310 + }, 4311 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-html-tag-name": { 4312 + "version": "1.2.0", 4313 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", 4314 + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", 4315 + "funding": [ 4316 + { 4317 + "type": "GitHub Sponsors", 4318 + "url": "https://github.com/sponsors/unifiedjs" 4319 + }, 4320 + { 4321 + "type": "OpenCollective", 4322 + "url": "https://opencollective.com/unified" 4323 + } 4324 + ] 4325 + }, 4326 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-normalize-identifier": { 4327 + "version": "1.1.0", 4328 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", 4329 + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", 4330 + "funding": [ 4331 + { 4332 + "type": "GitHub Sponsors", 4333 + "url": "https://github.com/sponsors/unifiedjs" 4334 + }, 4335 + { 4336 + "type": "OpenCollective", 4337 + "url": "https://opencollective.com/unified" 4338 + } 4339 + ], 4340 + "dependencies": { 4341 + "micromark-util-symbol": "^1.0.0" 4342 + } 4343 + }, 4344 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-resolve-all": { 4345 + "version": "1.1.0", 4346 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", 4347 + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", 4348 + "funding": [ 4349 + { 4350 + "type": "GitHub Sponsors", 4351 + "url": "https://github.com/sponsors/unifiedjs" 4352 + }, 4353 + { 4354 + "type": "OpenCollective", 4355 + "url": "https://opencollective.com/unified" 4356 + } 4357 + ], 4358 + "dependencies": { 4359 + "micromark-util-types": "^1.0.0" 4360 + } 4361 + }, 4362 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-sanitize-uri": { 4363 + "version": "1.2.0", 4364 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", 4365 + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", 4366 + "funding": [ 4367 + { 4368 + "type": "GitHub Sponsors", 4369 + "url": "https://github.com/sponsors/unifiedjs" 4370 + }, 4371 + { 4372 + "type": "OpenCollective", 4373 + "url": "https://opencollective.com/unified" 4374 + } 4375 + ], 4376 + "dependencies": { 4377 + "micromark-util-character": "^1.0.0", 4378 + "micromark-util-encode": "^1.0.0", 4379 + "micromark-util-symbol": "^1.0.0" 4380 + } 4381 + }, 4382 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-subtokenize": { 4383 + "version": "1.1.0", 4384 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", 4385 + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", 4386 + "funding": [ 4387 + { 4388 + "type": "GitHub Sponsors", 4389 + "url": "https://github.com/sponsors/unifiedjs" 4390 + }, 4391 + { 4392 + "type": "OpenCollective", 4393 + "url": "https://opencollective.com/unified" 4394 + } 4395 + ], 4396 + "dependencies": { 4397 + "micromark-util-chunked": "^1.0.0", 4398 + "micromark-util-symbol": "^1.0.0", 4399 + "micromark-util-types": "^1.0.0", 4400 + "uvu": "^0.5.0" 4401 + } 4402 + }, 4403 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-symbol": { 4404 + "version": "1.1.0", 4405 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 4406 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 4407 + "funding": [ 4408 + { 4409 + "type": "GitHub Sponsors", 4410 + "url": "https://github.com/sponsors/unifiedjs" 4411 + }, 4412 + { 4413 + "type": "OpenCollective", 4414 + "url": "https://opencollective.com/unified" 4415 + } 4416 + ] 4417 + }, 4418 + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-types": { 4419 + "version": "1.1.0", 4420 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 4421 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 4422 + "funding": [ 4423 + { 4424 + "type": "GitHub Sponsors", 4425 + "url": "https://github.com/sponsors/unifiedjs" 4426 + }, 4427 + { 4428 + "type": "OpenCollective", 4429 + "url": "https://opencollective.com/unified" 4430 + } 4431 + ] 4432 + }, 4433 + "node_modules/mdast-util-mdx-jsx": { 4434 + "version": "2.1.4", 4435 + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.1.4.tgz", 4436 + "integrity": "sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==", 4437 + "dependencies": { 4438 + "@types/estree-jsx": "^1.0.0", 4439 + "@types/hast": "^2.0.0", 4440 + "@types/mdast": "^3.0.0", 4441 + "@types/unist": "^2.0.0", 4442 + "ccount": "^2.0.0", 4443 + "mdast-util-from-markdown": "^1.1.0", 4444 + "mdast-util-to-markdown": "^1.3.0", 4445 + "parse-entities": "^4.0.0", 4446 + "stringify-entities": "^4.0.0", 4447 + "unist-util-remove-position": "^4.0.0", 4448 + "unist-util-stringify-position": "^3.0.0", 4449 + "vfile-message": "^3.0.0" 4450 + }, 4451 + "funding": { 4452 + "type": "opencollective", 4453 + "url": "https://opencollective.com/unified" 4454 + } 4455 + }, 4456 + "node_modules/mdast-util-mdx-jsx/node_modules/@types/mdast": { 4457 + "version": "3.0.14", 4458 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 4459 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 4460 + "dependencies": { 4461 + "@types/unist": "^2" 4462 + } 4463 + }, 4464 + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-from-markdown": { 4465 + "version": "1.3.1", 4466 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", 4467 + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", 4468 + "dependencies": { 4469 + "@types/mdast": "^3.0.0", 4470 + "@types/unist": "^2.0.0", 4471 + "decode-named-character-reference": "^1.0.0", 4472 + "mdast-util-to-string": "^3.1.0", 4473 + "micromark": "^3.0.0", 4474 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 4475 + "micromark-util-decode-string": "^1.0.0", 4476 + "micromark-util-normalize-identifier": "^1.0.0", 4477 + "micromark-util-symbol": "^1.0.0", 4478 + "micromark-util-types": "^1.0.0", 4479 + "unist-util-stringify-position": "^3.0.0", 4480 + "uvu": "^0.5.0" 4481 + }, 4482 + "funding": { 4483 + "type": "opencollective", 4484 + "url": "https://opencollective.com/unified" 4485 + } 4486 + }, 4487 + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-phrasing": { 4488 + "version": "3.0.1", 4489 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", 4490 + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", 4491 + "dependencies": { 4492 + "@types/mdast": "^3.0.0", 4493 + "unist-util-is": "^5.0.0" 4494 + }, 4495 + "funding": { 4496 + "type": "opencollective", 4497 + "url": "https://opencollective.com/unified" 4498 + } 4499 + }, 4500 + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-markdown": { 4501 + "version": "1.5.0", 4502 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", 4503 + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", 4504 + "dependencies": { 4505 + "@types/mdast": "^3.0.0", 4506 + "@types/unist": "^2.0.0", 4507 + "longest-streak": "^3.0.0", 4508 + "mdast-util-phrasing": "^3.0.0", 4509 + "mdast-util-to-string": "^3.0.0", 4510 + "micromark-util-decode-string": "^1.0.0", 4511 + "unist-util-visit": "^4.0.0", 4512 + "zwitch": "^2.0.0" 4513 + }, 4514 + "funding": { 4515 + "type": "opencollective", 4516 + "url": "https://opencollective.com/unified" 4517 + } 4518 + }, 4519 + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-string": { 4520 + "version": "3.2.0", 4521 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", 4522 + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", 4523 + "dependencies": { 4524 + "@types/mdast": "^3.0.0" 4525 + }, 4526 + "funding": { 4527 + "type": "opencollective", 4528 + "url": "https://opencollective.com/unified" 4529 + } 4530 + }, 4531 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark": { 4532 + "version": "3.2.0", 4533 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", 4534 + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", 4535 + "funding": [ 4536 + { 4537 + "type": "GitHub Sponsors", 4538 + "url": "https://github.com/sponsors/unifiedjs" 4539 + }, 4540 + { 4541 + "type": "OpenCollective", 4542 + "url": "https://opencollective.com/unified" 4543 + } 4544 + ], 4545 + "dependencies": { 4546 + "@types/debug": "^4.0.0", 4547 + "debug": "^4.0.0", 4548 + "decode-named-character-reference": "^1.0.0", 4549 + "micromark-core-commonmark": "^1.0.1", 4550 + "micromark-factory-space": "^1.0.0", 4551 + "micromark-util-character": "^1.0.0", 4552 + "micromark-util-chunked": "^1.0.0", 4553 + "micromark-util-combine-extensions": "^1.0.0", 4554 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 4555 + "micromark-util-encode": "^1.0.0", 4556 + "micromark-util-normalize-identifier": "^1.0.0", 4557 + "micromark-util-resolve-all": "^1.0.0", 4558 + "micromark-util-sanitize-uri": "^1.0.0", 4559 + "micromark-util-subtokenize": "^1.0.0", 4560 + "micromark-util-symbol": "^1.0.0", 4561 + "micromark-util-types": "^1.0.1", 4562 + "uvu": "^0.5.0" 4563 + } 4564 + }, 4565 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-core-commonmark": { 4566 + "version": "1.1.0", 4567 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", 4568 + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", 4569 + "funding": [ 4570 + { 4571 + "type": "GitHub Sponsors", 4572 + "url": "https://github.com/sponsors/unifiedjs" 4573 + }, 4574 + { 4575 + "type": "OpenCollective", 4576 + "url": "https://opencollective.com/unified" 4577 + } 4578 + ], 4579 + "dependencies": { 4580 + "decode-named-character-reference": "^1.0.0", 4581 + "micromark-factory-destination": "^1.0.0", 4582 + "micromark-factory-label": "^1.0.0", 4583 + "micromark-factory-space": "^1.0.0", 4584 + "micromark-factory-title": "^1.0.0", 4585 + "micromark-factory-whitespace": "^1.0.0", 4586 + "micromark-util-character": "^1.0.0", 4587 + "micromark-util-chunked": "^1.0.0", 4588 + "micromark-util-classify-character": "^1.0.0", 4589 + "micromark-util-html-tag-name": "^1.0.0", 4590 + "micromark-util-normalize-identifier": "^1.0.0", 4591 + "micromark-util-resolve-all": "^1.0.0", 4592 + "micromark-util-subtokenize": "^1.0.0", 4593 + "micromark-util-symbol": "^1.0.0", 4594 + "micromark-util-types": "^1.0.1", 4595 + "uvu": "^0.5.0" 4596 + } 4597 + }, 4598 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-destination": { 4599 + "version": "1.1.0", 4600 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", 4601 + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", 4602 + "funding": [ 4603 + { 4604 + "type": "GitHub Sponsors", 4605 + "url": "https://github.com/sponsors/unifiedjs" 4606 + }, 4607 + { 4608 + "type": "OpenCollective", 4609 + "url": "https://opencollective.com/unified" 4610 + } 4611 + ], 4612 + "dependencies": { 4613 + "micromark-util-character": "^1.0.0", 4614 + "micromark-util-symbol": "^1.0.0", 4615 + "micromark-util-types": "^1.0.0" 4616 + } 4617 + }, 4618 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-label": { 4619 + "version": "1.1.0", 4620 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", 4621 + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", 4622 + "funding": [ 4623 + { 4624 + "type": "GitHub Sponsors", 4625 + "url": "https://github.com/sponsors/unifiedjs" 4626 + }, 4627 + { 4628 + "type": "OpenCollective", 4629 + "url": "https://opencollective.com/unified" 4630 + } 4631 + ], 4632 + "dependencies": { 4633 + "micromark-util-character": "^1.0.0", 4634 + "micromark-util-symbol": "^1.0.0", 4635 + "micromark-util-types": "^1.0.0", 4636 + "uvu": "^0.5.0" 4637 + } 4638 + }, 4639 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-space": { 4640 + "version": "1.1.0", 4641 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 4642 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 4643 + "funding": [ 4644 + { 4645 + "type": "GitHub Sponsors", 4646 + "url": "https://github.com/sponsors/unifiedjs" 4647 + }, 4648 + { 4649 + "type": "OpenCollective", 4650 + "url": "https://opencollective.com/unified" 4651 + } 4652 + ], 4653 + "dependencies": { 4654 + "micromark-util-character": "^1.0.0", 4655 + "micromark-util-types": "^1.0.0" 4656 + } 4657 + }, 4658 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-title": { 4659 + "version": "1.1.0", 4660 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", 4661 + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", 4662 + "funding": [ 4663 + { 4664 + "type": "GitHub Sponsors", 4665 + "url": "https://github.com/sponsors/unifiedjs" 4666 + }, 4667 + { 4668 + "type": "OpenCollective", 4669 + "url": "https://opencollective.com/unified" 4670 + } 4671 + ], 4672 + "dependencies": { 4673 + "micromark-factory-space": "^1.0.0", 4674 + "micromark-util-character": "^1.0.0", 4675 + "micromark-util-symbol": "^1.0.0", 4676 + "micromark-util-types": "^1.0.0" 4677 + } 4678 + }, 4679 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-whitespace": { 4680 + "version": "1.1.0", 4681 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", 4682 + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", 4683 + "funding": [ 4684 + { 4685 + "type": "GitHub Sponsors", 4686 + "url": "https://github.com/sponsors/unifiedjs" 4687 + }, 4688 + { 4689 + "type": "OpenCollective", 4690 + "url": "https://opencollective.com/unified" 4691 + } 4692 + ], 4693 + "dependencies": { 4694 + "micromark-factory-space": "^1.0.0", 4695 + "micromark-util-character": "^1.0.0", 4696 + "micromark-util-symbol": "^1.0.0", 4697 + "micromark-util-types": "^1.0.0" 4698 + } 4699 + }, 4700 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-character": { 4701 + "version": "1.2.0", 4702 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 4703 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 4704 + "funding": [ 4705 + { 4706 + "type": "GitHub Sponsors", 4707 + "url": "https://github.com/sponsors/unifiedjs" 4708 + }, 4709 + { 4710 + "type": "OpenCollective", 4711 + "url": "https://opencollective.com/unified" 4712 + } 4713 + ], 4714 + "dependencies": { 4715 + "micromark-util-symbol": "^1.0.0", 4716 + "micromark-util-types": "^1.0.0" 4717 + } 4718 + }, 4719 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-chunked": { 4720 + "version": "1.1.0", 4721 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 4722 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 4723 + "funding": [ 4724 + { 4725 + "type": "GitHub Sponsors", 4726 + "url": "https://github.com/sponsors/unifiedjs" 4727 + }, 4728 + { 4729 + "type": "OpenCollective", 4730 + "url": "https://opencollective.com/unified" 4731 + } 4732 + ], 4733 + "dependencies": { 4734 + "micromark-util-symbol": "^1.0.0" 4735 + } 4736 + }, 4737 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-classify-character": { 4738 + "version": "1.1.0", 4739 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", 4740 + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", 4741 + "funding": [ 4742 + { 4743 + "type": "GitHub Sponsors", 4744 + "url": "https://github.com/sponsors/unifiedjs" 4745 + }, 4746 + { 4747 + "type": "OpenCollective", 4748 + "url": "https://opencollective.com/unified" 4749 + } 4750 + ], 4751 + "dependencies": { 4752 + "micromark-util-character": "^1.0.0", 4753 + "micromark-util-symbol": "^1.0.0", 4754 + "micromark-util-types": "^1.0.0" 4755 + } 4756 + }, 4757 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-combine-extensions": { 4758 + "version": "1.1.0", 4759 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", 4760 + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", 4761 + "funding": [ 4762 + { 4763 + "type": "GitHub Sponsors", 4764 + "url": "https://github.com/sponsors/unifiedjs" 4765 + }, 4766 + { 4767 + "type": "OpenCollective", 4768 + "url": "https://opencollective.com/unified" 4769 + } 4770 + ], 4771 + "dependencies": { 4772 + "micromark-util-chunked": "^1.0.0", 4773 + "micromark-util-types": "^1.0.0" 4774 + } 4775 + }, 4776 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-decode-numeric-character-reference": { 4777 + "version": "1.1.0", 4778 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", 4779 + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", 4780 + "funding": [ 4781 + { 4782 + "type": "GitHub Sponsors", 4783 + "url": "https://github.com/sponsors/unifiedjs" 4784 + }, 4785 + { 4786 + "type": "OpenCollective", 4787 + "url": "https://opencollective.com/unified" 4788 + } 4789 + ], 4790 + "dependencies": { 4791 + "micromark-util-symbol": "^1.0.0" 4792 + } 4793 + }, 4794 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-decode-string": { 4795 + "version": "1.1.0", 4796 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", 4797 + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", 4798 + "funding": [ 4799 + { 4800 + "type": "GitHub Sponsors", 4801 + "url": "https://github.com/sponsors/unifiedjs" 4802 + }, 4803 + { 4804 + "type": "OpenCollective", 4805 + "url": "https://opencollective.com/unified" 4806 + } 4807 + ], 4808 + "dependencies": { 4809 + "decode-named-character-reference": "^1.0.0", 4810 + "micromark-util-character": "^1.0.0", 4811 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 4812 + "micromark-util-symbol": "^1.0.0" 4813 + } 4814 + }, 4815 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-encode": { 4816 + "version": "1.1.0", 4817 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", 4818 + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", 4819 + "funding": [ 4820 + { 4821 + "type": "GitHub Sponsors", 4822 + "url": "https://github.com/sponsors/unifiedjs" 4823 + }, 4824 + { 4825 + "type": "OpenCollective", 4826 + "url": "https://opencollective.com/unified" 4827 + } 4828 + ] 4829 + }, 4830 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-html-tag-name": { 4831 + "version": "1.2.0", 4832 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", 4833 + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", 4834 + "funding": [ 4835 + { 4836 + "type": "GitHub Sponsors", 4837 + "url": "https://github.com/sponsors/unifiedjs" 4838 + }, 4839 + { 4840 + "type": "OpenCollective", 4841 + "url": "https://opencollective.com/unified" 4842 + } 4843 + ] 4844 + }, 4845 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-normalize-identifier": { 4846 + "version": "1.1.0", 4847 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", 4848 + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", 4849 + "funding": [ 4850 + { 4851 + "type": "GitHub Sponsors", 4852 + "url": "https://github.com/sponsors/unifiedjs" 4853 + }, 4854 + { 4855 + "type": "OpenCollective", 4856 + "url": "https://opencollective.com/unified" 4857 + } 4858 + ], 4859 + "dependencies": { 4860 + "micromark-util-symbol": "^1.0.0" 4861 + } 4862 + }, 4863 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-resolve-all": { 4864 + "version": "1.1.0", 4865 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", 4866 + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", 4867 + "funding": [ 4868 + { 4869 + "type": "GitHub Sponsors", 4870 + "url": "https://github.com/sponsors/unifiedjs" 4871 + }, 4872 + { 4873 + "type": "OpenCollective", 4874 + "url": "https://opencollective.com/unified" 4875 + } 4876 + ], 4877 + "dependencies": { 4878 + "micromark-util-types": "^1.0.0" 4879 + } 4880 + }, 4881 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-sanitize-uri": { 4882 + "version": "1.2.0", 4883 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", 4884 + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", 4885 + "funding": [ 4886 + { 4887 + "type": "GitHub Sponsors", 4888 + "url": "https://github.com/sponsors/unifiedjs" 4889 + }, 4890 + { 4891 + "type": "OpenCollective", 4892 + "url": "https://opencollective.com/unified" 4893 + } 4894 + ], 4895 + "dependencies": { 4896 + "micromark-util-character": "^1.0.0", 4897 + "micromark-util-encode": "^1.0.0", 4898 + "micromark-util-symbol": "^1.0.0" 4899 + } 4900 + }, 4901 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-subtokenize": { 4902 + "version": "1.1.0", 4903 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", 4904 + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", 4905 + "funding": [ 4906 + { 4907 + "type": "GitHub Sponsors", 4908 + "url": "https://github.com/sponsors/unifiedjs" 4909 + }, 4910 + { 4911 + "type": "OpenCollective", 4912 + "url": "https://opencollective.com/unified" 4913 + } 4914 + ], 4915 + "dependencies": { 4916 + "micromark-util-chunked": "^1.0.0", 4917 + "micromark-util-symbol": "^1.0.0", 4918 + "micromark-util-types": "^1.0.0", 4919 + "uvu": "^0.5.0" 4920 + } 4921 + }, 4922 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-symbol": { 4923 + "version": "1.1.0", 4924 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 4925 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 4926 + "funding": [ 4927 + { 4928 + "type": "GitHub Sponsors", 4929 + "url": "https://github.com/sponsors/unifiedjs" 4930 + }, 4931 + { 4932 + "type": "OpenCollective", 4933 + "url": "https://opencollective.com/unified" 4934 + } 4935 + ] 4936 + }, 4937 + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-types": { 4938 + "version": "1.1.0", 4939 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 4940 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 4941 + "funding": [ 4942 + { 4943 + "type": "GitHub Sponsors", 4944 + "url": "https://github.com/sponsors/unifiedjs" 4945 + }, 4946 + { 4947 + "type": "OpenCollective", 4948 + "url": "https://opencollective.com/unified" 4949 + } 4950 + ] 4951 + }, 4952 + "node_modules/mdast-util-mdx/node_modules/@types/mdast": { 4953 + "version": "3.0.14", 4954 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 4955 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 4956 + "dependencies": { 4957 + "@types/unist": "^2" 4958 + } 4959 + }, 4960 + "node_modules/mdast-util-mdx/node_modules/mdast-util-from-markdown": { 4961 + "version": "1.3.1", 4962 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", 4963 + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", 4964 + "dependencies": { 4965 + "@types/mdast": "^3.0.0", 4966 + "@types/unist": "^2.0.0", 4967 + "decode-named-character-reference": "^1.0.0", 4968 + "mdast-util-to-string": "^3.1.0", 4969 + "micromark": "^3.0.0", 4970 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 4971 + "micromark-util-decode-string": "^1.0.0", 4972 + "micromark-util-normalize-identifier": "^1.0.0", 4973 + "micromark-util-symbol": "^1.0.0", 4974 + "micromark-util-types": "^1.0.0", 4975 + "unist-util-stringify-position": "^3.0.0", 4976 + "uvu": "^0.5.0" 4977 + }, 4978 + "funding": { 4979 + "type": "opencollective", 4980 + "url": "https://opencollective.com/unified" 4981 + } 4982 + }, 4983 + "node_modules/mdast-util-mdx/node_modules/mdast-util-phrasing": { 4984 + "version": "3.0.1", 4985 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", 4986 + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", 4987 + "dependencies": { 4988 + "@types/mdast": "^3.0.0", 4989 + "unist-util-is": "^5.0.0" 4990 + }, 4991 + "funding": { 4992 + "type": "opencollective", 4993 + "url": "https://opencollective.com/unified" 4994 + } 4995 + }, 4996 + "node_modules/mdast-util-mdx/node_modules/mdast-util-to-markdown": { 4997 + "version": "1.5.0", 4998 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", 4999 + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", 5000 + "dependencies": { 5001 + "@types/mdast": "^3.0.0", 5002 + "@types/unist": "^2.0.0", 5003 + "longest-streak": "^3.0.0", 5004 + "mdast-util-phrasing": "^3.0.0", 5005 + "mdast-util-to-string": "^3.0.0", 5006 + "micromark-util-decode-string": "^1.0.0", 5007 + "unist-util-visit": "^4.0.0", 5008 + "zwitch": "^2.0.0" 5009 + }, 5010 + "funding": { 5011 + "type": "opencollective", 5012 + "url": "https://opencollective.com/unified" 5013 + } 5014 + }, 5015 + "node_modules/mdast-util-mdx/node_modules/mdast-util-to-string": { 5016 + "version": "3.2.0", 5017 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", 5018 + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", 5019 + "dependencies": { 5020 + "@types/mdast": "^3.0.0" 5021 + }, 5022 + "funding": { 5023 + "type": "opencollective", 5024 + "url": "https://opencollective.com/unified" 5025 + } 5026 + }, 5027 + "node_modules/mdast-util-mdx/node_modules/micromark": { 5028 + "version": "3.2.0", 5029 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", 5030 + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", 5031 + "funding": [ 5032 + { 5033 + "type": "GitHub Sponsors", 5034 + "url": "https://github.com/sponsors/unifiedjs" 5035 + }, 5036 + { 5037 + "type": "OpenCollective", 5038 + "url": "https://opencollective.com/unified" 5039 + } 5040 + ], 5041 + "dependencies": { 5042 + "@types/debug": "^4.0.0", 5043 + "debug": "^4.0.0", 5044 + "decode-named-character-reference": "^1.0.0", 5045 + "micromark-core-commonmark": "^1.0.1", 5046 + "micromark-factory-space": "^1.0.0", 5047 + "micromark-util-character": "^1.0.0", 5048 + "micromark-util-chunked": "^1.0.0", 5049 + "micromark-util-combine-extensions": "^1.0.0", 5050 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 5051 + "micromark-util-encode": "^1.0.0", 5052 + "micromark-util-normalize-identifier": "^1.0.0", 5053 + "micromark-util-resolve-all": "^1.0.0", 5054 + "micromark-util-sanitize-uri": "^1.0.0", 5055 + "micromark-util-subtokenize": "^1.0.0", 5056 + "micromark-util-symbol": "^1.0.0", 5057 + "micromark-util-types": "^1.0.1", 5058 + "uvu": "^0.5.0" 5059 + } 5060 + }, 5061 + "node_modules/mdast-util-mdx/node_modules/micromark-core-commonmark": { 5062 + "version": "1.1.0", 5063 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", 5064 + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", 5065 + "funding": [ 5066 + { 5067 + "type": "GitHub Sponsors", 5068 + "url": "https://github.com/sponsors/unifiedjs" 5069 + }, 5070 + { 5071 + "type": "OpenCollective", 5072 + "url": "https://opencollective.com/unified" 5073 + } 5074 + ], 5075 + "dependencies": { 5076 + "decode-named-character-reference": "^1.0.0", 5077 + "micromark-factory-destination": "^1.0.0", 5078 + "micromark-factory-label": "^1.0.0", 5079 + "micromark-factory-space": "^1.0.0", 5080 + "micromark-factory-title": "^1.0.0", 5081 + "micromark-factory-whitespace": "^1.0.0", 5082 + "micromark-util-character": "^1.0.0", 5083 + "micromark-util-chunked": "^1.0.0", 5084 + "micromark-util-classify-character": "^1.0.0", 5085 + "micromark-util-html-tag-name": "^1.0.0", 5086 + "micromark-util-normalize-identifier": "^1.0.0", 5087 + "micromark-util-resolve-all": "^1.0.0", 5088 + "micromark-util-subtokenize": "^1.0.0", 5089 + "micromark-util-symbol": "^1.0.0", 5090 + "micromark-util-types": "^1.0.1", 5091 + "uvu": "^0.5.0" 5092 + } 5093 + }, 5094 + "node_modules/mdast-util-mdx/node_modules/micromark-factory-destination": { 5095 + "version": "1.1.0", 5096 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", 5097 + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", 5098 + "funding": [ 5099 + { 5100 + "type": "GitHub Sponsors", 5101 + "url": "https://github.com/sponsors/unifiedjs" 5102 + }, 5103 + { 5104 + "type": "OpenCollective", 5105 + "url": "https://opencollective.com/unified" 5106 + } 5107 + ], 5108 + "dependencies": { 5109 + "micromark-util-character": "^1.0.0", 5110 + "micromark-util-symbol": "^1.0.0", 5111 + "micromark-util-types": "^1.0.0" 5112 + } 5113 + }, 5114 + "node_modules/mdast-util-mdx/node_modules/micromark-factory-label": { 5115 + "version": "1.1.0", 5116 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", 5117 + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", 5118 + "funding": [ 5119 + { 5120 + "type": "GitHub Sponsors", 5121 + "url": "https://github.com/sponsors/unifiedjs" 5122 + }, 5123 + { 5124 + "type": "OpenCollective", 5125 + "url": "https://opencollective.com/unified" 5126 + } 5127 + ], 5128 + "dependencies": { 5129 + "micromark-util-character": "^1.0.0", 5130 + "micromark-util-symbol": "^1.0.0", 5131 + "micromark-util-types": "^1.0.0", 5132 + "uvu": "^0.5.0" 5133 + } 5134 + }, 5135 + "node_modules/mdast-util-mdx/node_modules/micromark-factory-space": { 5136 + "version": "1.1.0", 5137 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 5138 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 5139 + "funding": [ 5140 + { 5141 + "type": "GitHub Sponsors", 5142 + "url": "https://github.com/sponsors/unifiedjs" 5143 + }, 5144 + { 5145 + "type": "OpenCollective", 5146 + "url": "https://opencollective.com/unified" 5147 + } 5148 + ], 5149 + "dependencies": { 5150 + "micromark-util-character": "^1.0.0", 5151 + "micromark-util-types": "^1.0.0" 5152 + } 5153 + }, 5154 + "node_modules/mdast-util-mdx/node_modules/micromark-factory-title": { 5155 + "version": "1.1.0", 5156 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", 5157 + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", 5158 + "funding": [ 5159 + { 5160 + "type": "GitHub Sponsors", 5161 + "url": "https://github.com/sponsors/unifiedjs" 5162 + }, 5163 + { 5164 + "type": "OpenCollective", 5165 + "url": "https://opencollective.com/unified" 5166 + } 5167 + ], 5168 + "dependencies": { 5169 + "micromark-factory-space": "^1.0.0", 5170 + "micromark-util-character": "^1.0.0", 5171 + "micromark-util-symbol": "^1.0.0", 5172 + "micromark-util-types": "^1.0.0" 5173 + } 5174 + }, 5175 + "node_modules/mdast-util-mdx/node_modules/micromark-factory-whitespace": { 5176 + "version": "1.1.0", 5177 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", 5178 + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", 5179 + "funding": [ 5180 + { 5181 + "type": "GitHub Sponsors", 5182 + "url": "https://github.com/sponsors/unifiedjs" 5183 + }, 5184 + { 5185 + "type": "OpenCollective", 5186 + "url": "https://opencollective.com/unified" 5187 + } 5188 + ], 5189 + "dependencies": { 5190 + "micromark-factory-space": "^1.0.0", 5191 + "micromark-util-character": "^1.0.0", 5192 + "micromark-util-symbol": "^1.0.0", 5193 + "micromark-util-types": "^1.0.0" 5194 + } 5195 + }, 5196 + "node_modules/mdast-util-mdx/node_modules/micromark-util-character": { 5197 + "version": "1.2.0", 5198 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 5199 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 5200 + "funding": [ 5201 + { 5202 + "type": "GitHub Sponsors", 5203 + "url": "https://github.com/sponsors/unifiedjs" 5204 + }, 5205 + { 5206 + "type": "OpenCollective", 5207 + "url": "https://opencollective.com/unified" 5208 + } 5209 + ], 5210 + "dependencies": { 5211 + "micromark-util-symbol": "^1.0.0", 5212 + "micromark-util-types": "^1.0.0" 5213 + } 5214 + }, 5215 + "node_modules/mdast-util-mdx/node_modules/micromark-util-chunked": { 5216 + "version": "1.1.0", 5217 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 5218 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 5219 + "funding": [ 5220 + { 5221 + "type": "GitHub Sponsors", 5222 + "url": "https://github.com/sponsors/unifiedjs" 5223 + }, 5224 + { 5225 + "type": "OpenCollective", 5226 + "url": "https://opencollective.com/unified" 5227 + } 5228 + ], 5229 + "dependencies": { 5230 + "micromark-util-symbol": "^1.0.0" 5231 + } 5232 + }, 5233 + "node_modules/mdast-util-mdx/node_modules/micromark-util-classify-character": { 5234 + "version": "1.1.0", 5235 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", 5236 + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", 5237 + "funding": [ 5238 + { 5239 + "type": "GitHub Sponsors", 5240 + "url": "https://github.com/sponsors/unifiedjs" 5241 + }, 5242 + { 5243 + "type": "OpenCollective", 5244 + "url": "https://opencollective.com/unified" 5245 + } 5246 + ], 5247 + "dependencies": { 5248 + "micromark-util-character": "^1.0.0", 5249 + "micromark-util-symbol": "^1.0.0", 5250 + "micromark-util-types": "^1.0.0" 5251 + } 5252 + }, 5253 + "node_modules/mdast-util-mdx/node_modules/micromark-util-combine-extensions": { 5254 + "version": "1.1.0", 5255 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", 5256 + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", 5257 + "funding": [ 5258 + { 5259 + "type": "GitHub Sponsors", 5260 + "url": "https://github.com/sponsors/unifiedjs" 5261 + }, 5262 + { 5263 + "type": "OpenCollective", 5264 + "url": "https://opencollective.com/unified" 5265 + } 5266 + ], 5267 + "dependencies": { 5268 + "micromark-util-chunked": "^1.0.0", 5269 + "micromark-util-types": "^1.0.0" 5270 + } 5271 + }, 5272 + "node_modules/mdast-util-mdx/node_modules/micromark-util-decode-numeric-character-reference": { 5273 + "version": "1.1.0", 5274 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", 5275 + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", 5276 + "funding": [ 5277 + { 5278 + "type": "GitHub Sponsors", 5279 + "url": "https://github.com/sponsors/unifiedjs" 5280 + }, 5281 + { 5282 + "type": "OpenCollective", 5283 + "url": "https://opencollective.com/unified" 5284 + } 5285 + ], 5286 + "dependencies": { 5287 + "micromark-util-symbol": "^1.0.0" 5288 + } 5289 + }, 5290 + "node_modules/mdast-util-mdx/node_modules/micromark-util-decode-string": { 5291 + "version": "1.1.0", 5292 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", 5293 + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", 5294 + "funding": [ 5295 + { 5296 + "type": "GitHub Sponsors", 5297 + "url": "https://github.com/sponsors/unifiedjs" 5298 + }, 5299 + { 5300 + "type": "OpenCollective", 5301 + "url": "https://opencollective.com/unified" 5302 + } 5303 + ], 5304 + "dependencies": { 5305 + "decode-named-character-reference": "^1.0.0", 5306 + "micromark-util-character": "^1.0.0", 5307 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 5308 + "micromark-util-symbol": "^1.0.0" 5309 + } 5310 + }, 5311 + "node_modules/mdast-util-mdx/node_modules/micromark-util-encode": { 5312 + "version": "1.1.0", 5313 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", 5314 + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", 5315 + "funding": [ 5316 + { 5317 + "type": "GitHub Sponsors", 5318 + "url": "https://github.com/sponsors/unifiedjs" 5319 + }, 5320 + { 5321 + "type": "OpenCollective", 5322 + "url": "https://opencollective.com/unified" 5323 + } 5324 + ] 5325 + }, 5326 + "node_modules/mdast-util-mdx/node_modules/micromark-util-html-tag-name": { 5327 + "version": "1.2.0", 5328 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", 5329 + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", 5330 + "funding": [ 5331 + { 5332 + "type": "GitHub Sponsors", 5333 + "url": "https://github.com/sponsors/unifiedjs" 5334 + }, 5335 + { 5336 + "type": "OpenCollective", 5337 + "url": "https://opencollective.com/unified" 5338 + } 5339 + ] 5340 + }, 5341 + "node_modules/mdast-util-mdx/node_modules/micromark-util-normalize-identifier": { 5342 + "version": "1.1.0", 5343 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", 5344 + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", 5345 + "funding": [ 5346 + { 5347 + "type": "GitHub Sponsors", 5348 + "url": "https://github.com/sponsors/unifiedjs" 5349 + }, 5350 + { 5351 + "type": "OpenCollective", 5352 + "url": "https://opencollective.com/unified" 5353 + } 5354 + ], 5355 + "dependencies": { 5356 + "micromark-util-symbol": "^1.0.0" 5357 + } 5358 + }, 5359 + "node_modules/mdast-util-mdx/node_modules/micromark-util-resolve-all": { 5360 + "version": "1.1.0", 5361 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", 5362 + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", 5363 + "funding": [ 5364 + { 5365 + "type": "GitHub Sponsors", 5366 + "url": "https://github.com/sponsors/unifiedjs" 5367 + }, 5368 + { 5369 + "type": "OpenCollective", 5370 + "url": "https://opencollective.com/unified" 5371 + } 5372 + ], 5373 + "dependencies": { 5374 + "micromark-util-types": "^1.0.0" 5375 + } 5376 + }, 5377 + "node_modules/mdast-util-mdx/node_modules/micromark-util-sanitize-uri": { 5378 + "version": "1.2.0", 5379 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", 5380 + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", 5381 + "funding": [ 5382 + { 5383 + "type": "GitHub Sponsors", 5384 + "url": "https://github.com/sponsors/unifiedjs" 5385 + }, 5386 + { 5387 + "type": "OpenCollective", 5388 + "url": "https://opencollective.com/unified" 5389 + } 5390 + ], 5391 + "dependencies": { 5392 + "micromark-util-character": "^1.0.0", 5393 + "micromark-util-encode": "^1.0.0", 5394 + "micromark-util-symbol": "^1.0.0" 5395 + } 5396 + }, 5397 + "node_modules/mdast-util-mdx/node_modules/micromark-util-subtokenize": { 5398 + "version": "1.1.0", 5399 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", 5400 + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", 5401 + "funding": [ 5402 + { 5403 + "type": "GitHub Sponsors", 5404 + "url": "https://github.com/sponsors/unifiedjs" 5405 + }, 5406 + { 5407 + "type": "OpenCollective", 5408 + "url": "https://opencollective.com/unified" 5409 + } 5410 + ], 5411 + "dependencies": { 5412 + "micromark-util-chunked": "^1.0.0", 5413 + "micromark-util-symbol": "^1.0.0", 5414 + "micromark-util-types": "^1.0.0", 5415 + "uvu": "^0.5.0" 5416 + } 5417 + }, 5418 + "node_modules/mdast-util-mdx/node_modules/micromark-util-symbol": { 5419 + "version": "1.1.0", 5420 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 5421 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 5422 + "funding": [ 5423 + { 5424 + "type": "GitHub Sponsors", 5425 + "url": "https://github.com/sponsors/unifiedjs" 5426 + }, 5427 + { 5428 + "type": "OpenCollective", 5429 + "url": "https://opencollective.com/unified" 5430 + } 5431 + ] 5432 + }, 5433 + "node_modules/mdast-util-mdx/node_modules/micromark-util-types": { 5434 + "version": "1.1.0", 5435 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 5436 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 5437 + "funding": [ 5438 + { 5439 + "type": "GitHub Sponsors", 5440 + "url": "https://github.com/sponsors/unifiedjs" 5441 + }, 5442 + { 5443 + "type": "OpenCollective", 5444 + "url": "https://opencollective.com/unified" 5445 + } 5446 + ] 5447 + }, 5448 + "node_modules/mdast-util-mdxjs-esm": { 5449 + "version": "1.3.1", 5450 + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.3.1.tgz", 5451 + "integrity": "sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==", 5452 + "dependencies": { 5453 + "@types/estree-jsx": "^1.0.0", 5454 + "@types/hast": "^2.0.0", 5455 + "@types/mdast": "^3.0.0", 5456 + "mdast-util-from-markdown": "^1.0.0", 5457 + "mdast-util-to-markdown": "^1.0.0" 5458 + }, 5459 + "funding": { 5460 + "type": "opencollective", 5461 + "url": "https://opencollective.com/unified" 5462 + } 5463 + }, 5464 + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/mdast": { 5465 + "version": "3.0.14", 5466 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 5467 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 5468 + "dependencies": { 5469 + "@types/unist": "^2" 5470 + } 5471 + }, 5472 + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-from-markdown": { 5473 + "version": "1.3.1", 5474 + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", 5475 + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", 5476 + "dependencies": { 5477 + "@types/mdast": "^3.0.0", 5478 + "@types/unist": "^2.0.0", 5479 + "decode-named-character-reference": "^1.0.0", 5480 + "mdast-util-to-string": "^3.1.0", 5481 + "micromark": "^3.0.0", 5482 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 5483 + "micromark-util-decode-string": "^1.0.0", 5484 + "micromark-util-normalize-identifier": "^1.0.0", 5485 + "micromark-util-symbol": "^1.0.0", 5486 + "micromark-util-types": "^1.0.0", 5487 + "unist-util-stringify-position": "^3.0.0", 5488 + "uvu": "^0.5.0" 5489 + }, 5490 + "funding": { 5491 + "type": "opencollective", 5492 + "url": "https://opencollective.com/unified" 5493 + } 5494 + }, 5495 + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-phrasing": { 5496 + "version": "3.0.1", 5497 + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", 5498 + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", 5499 + "dependencies": { 5500 + "@types/mdast": "^3.0.0", 5501 + "unist-util-is": "^5.0.0" 5502 + }, 5503 + "funding": { 5504 + "type": "opencollective", 5505 + "url": "https://opencollective.com/unified" 5506 + } 5507 + }, 5508 + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-markdown": { 5509 + "version": "1.5.0", 5510 + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", 5511 + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", 5512 + "dependencies": { 5513 + "@types/mdast": "^3.0.0", 5514 + "@types/unist": "^2.0.0", 5515 + "longest-streak": "^3.0.0", 5516 + "mdast-util-phrasing": "^3.0.0", 5517 + "mdast-util-to-string": "^3.0.0", 5518 + "micromark-util-decode-string": "^1.0.0", 5519 + "unist-util-visit": "^4.0.0", 5520 + "zwitch": "^2.0.0" 5521 + }, 5522 + "funding": { 5523 + "type": "opencollective", 5524 + "url": "https://opencollective.com/unified" 5525 + } 5526 + }, 5527 + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-string": { 5528 + "version": "3.2.0", 5529 + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", 5530 + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", 5531 + "dependencies": { 5532 + "@types/mdast": "^3.0.0" 5533 + }, 5534 + "funding": { 5535 + "type": "opencollective", 5536 + "url": "https://opencollective.com/unified" 5537 + } 5538 + }, 5539 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark": { 5540 + "version": "3.2.0", 5541 + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", 5542 + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", 5543 + "funding": [ 5544 + { 5545 + "type": "GitHub Sponsors", 5546 + "url": "https://github.com/sponsors/unifiedjs" 5547 + }, 5548 + { 5549 + "type": "OpenCollective", 5550 + "url": "https://opencollective.com/unified" 5551 + } 5552 + ], 5553 + "dependencies": { 5554 + "@types/debug": "^4.0.0", 5555 + "debug": "^4.0.0", 5556 + "decode-named-character-reference": "^1.0.0", 5557 + "micromark-core-commonmark": "^1.0.1", 5558 + "micromark-factory-space": "^1.0.0", 5559 + "micromark-util-character": "^1.0.0", 5560 + "micromark-util-chunked": "^1.0.0", 5561 + "micromark-util-combine-extensions": "^1.0.0", 5562 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 5563 + "micromark-util-encode": "^1.0.0", 5564 + "micromark-util-normalize-identifier": "^1.0.0", 5565 + "micromark-util-resolve-all": "^1.0.0", 5566 + "micromark-util-sanitize-uri": "^1.0.0", 5567 + "micromark-util-subtokenize": "^1.0.0", 5568 + "micromark-util-symbol": "^1.0.0", 5569 + "micromark-util-types": "^1.0.1", 5570 + "uvu": "^0.5.0" 5571 + } 5572 + }, 5573 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-core-commonmark": { 5574 + "version": "1.1.0", 5575 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", 5576 + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", 5577 + "funding": [ 5578 + { 5579 + "type": "GitHub Sponsors", 5580 + "url": "https://github.com/sponsors/unifiedjs" 5581 + }, 5582 + { 5583 + "type": "OpenCollective", 5584 + "url": "https://opencollective.com/unified" 5585 + } 5586 + ], 5587 + "dependencies": { 5588 + "decode-named-character-reference": "^1.0.0", 5589 + "micromark-factory-destination": "^1.0.0", 5590 + "micromark-factory-label": "^1.0.0", 5591 + "micromark-factory-space": "^1.0.0", 5592 + "micromark-factory-title": "^1.0.0", 5593 + "micromark-factory-whitespace": "^1.0.0", 5594 + "micromark-util-character": "^1.0.0", 5595 + "micromark-util-chunked": "^1.0.0", 5596 + "micromark-util-classify-character": "^1.0.0", 5597 + "micromark-util-html-tag-name": "^1.0.0", 5598 + "micromark-util-normalize-identifier": "^1.0.0", 5599 + "micromark-util-resolve-all": "^1.0.0", 5600 + "micromark-util-subtokenize": "^1.0.0", 5601 + "micromark-util-symbol": "^1.0.0", 5602 + "micromark-util-types": "^1.0.1", 5603 + "uvu": "^0.5.0" 5604 + } 5605 + }, 5606 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-destination": { 5607 + "version": "1.1.0", 5608 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", 5609 + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", 5610 + "funding": [ 5611 + { 5612 + "type": "GitHub Sponsors", 5613 + "url": "https://github.com/sponsors/unifiedjs" 5614 + }, 5615 + { 5616 + "type": "OpenCollective", 5617 + "url": "https://opencollective.com/unified" 5618 + } 5619 + ], 5620 + "dependencies": { 5621 + "micromark-util-character": "^1.0.0", 5622 + "micromark-util-symbol": "^1.0.0", 5623 + "micromark-util-types": "^1.0.0" 5624 + } 5625 + }, 5626 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-label": { 5627 + "version": "1.1.0", 5628 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", 5629 + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", 5630 + "funding": [ 5631 + { 5632 + "type": "GitHub Sponsors", 5633 + "url": "https://github.com/sponsors/unifiedjs" 5634 + }, 5635 + { 5636 + "type": "OpenCollective", 5637 + "url": "https://opencollective.com/unified" 5638 + } 5639 + ], 5640 + "dependencies": { 5641 + "micromark-util-character": "^1.0.0", 5642 + "micromark-util-symbol": "^1.0.0", 5643 + "micromark-util-types": "^1.0.0", 5644 + "uvu": "^0.5.0" 5645 + } 5646 + }, 5647 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-space": { 5648 + "version": "1.1.0", 5649 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 5650 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 5651 + "funding": [ 5652 + { 5653 + "type": "GitHub Sponsors", 5654 + "url": "https://github.com/sponsors/unifiedjs" 5655 + }, 5656 + { 5657 + "type": "OpenCollective", 5658 + "url": "https://opencollective.com/unified" 5659 + } 5660 + ], 5661 + "dependencies": { 5662 + "micromark-util-character": "^1.0.0", 5663 + "micromark-util-types": "^1.0.0" 5664 + } 5665 + }, 5666 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-title": { 5667 + "version": "1.1.0", 5668 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", 5669 + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", 5670 + "funding": [ 5671 + { 5672 + "type": "GitHub Sponsors", 5673 + "url": "https://github.com/sponsors/unifiedjs" 5674 + }, 5675 + { 5676 + "type": "OpenCollective", 5677 + "url": "https://opencollective.com/unified" 5678 + } 5679 + ], 5680 + "dependencies": { 5681 + "micromark-factory-space": "^1.0.0", 5682 + "micromark-util-character": "^1.0.0", 5683 + "micromark-util-symbol": "^1.0.0", 5684 + "micromark-util-types": "^1.0.0" 5685 + } 5686 + }, 5687 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-whitespace": { 5688 + "version": "1.1.0", 5689 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", 5690 + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", 5691 + "funding": [ 5692 + { 5693 + "type": "GitHub Sponsors", 5694 + "url": "https://github.com/sponsors/unifiedjs" 5695 + }, 5696 + { 5697 + "type": "OpenCollective", 5698 + "url": "https://opencollective.com/unified" 5699 + } 5700 + ], 5701 + "dependencies": { 5702 + "micromark-factory-space": "^1.0.0", 5703 + "micromark-util-character": "^1.0.0", 5704 + "micromark-util-symbol": "^1.0.0", 5705 + "micromark-util-types": "^1.0.0" 5706 + } 5707 + }, 5708 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-character": { 5709 + "version": "1.2.0", 5710 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 5711 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 5712 + "funding": [ 5713 + { 5714 + "type": "GitHub Sponsors", 5715 + "url": "https://github.com/sponsors/unifiedjs" 5716 + }, 5717 + { 5718 + "type": "OpenCollective", 5719 + "url": "https://opencollective.com/unified" 5720 + } 5721 + ], 5722 + "dependencies": { 5723 + "micromark-util-symbol": "^1.0.0", 5724 + "micromark-util-types": "^1.0.0" 5725 + } 5726 + }, 5727 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-chunked": { 5728 + "version": "1.1.0", 5729 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 5730 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 5731 + "funding": [ 5732 + { 5733 + "type": "GitHub Sponsors", 5734 + "url": "https://github.com/sponsors/unifiedjs" 5735 + }, 5736 + { 5737 + "type": "OpenCollective", 5738 + "url": "https://opencollective.com/unified" 5739 + } 5740 + ], 5741 + "dependencies": { 5742 + "micromark-util-symbol": "^1.0.0" 5743 + } 5744 + }, 5745 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-classify-character": { 5746 + "version": "1.1.0", 5747 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", 5748 + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", 5749 + "funding": [ 5750 + { 5751 + "type": "GitHub Sponsors", 5752 + "url": "https://github.com/sponsors/unifiedjs" 5753 + }, 5754 + { 5755 + "type": "OpenCollective", 5756 + "url": "https://opencollective.com/unified" 5757 + } 5758 + ], 5759 + "dependencies": { 5760 + "micromark-util-character": "^1.0.0", 5761 + "micromark-util-symbol": "^1.0.0", 5762 + "micromark-util-types": "^1.0.0" 5763 + } 5764 + }, 5765 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-combine-extensions": { 5766 + "version": "1.1.0", 5767 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", 5768 + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", 5769 + "funding": [ 5770 + { 5771 + "type": "GitHub Sponsors", 5772 + "url": "https://github.com/sponsors/unifiedjs" 5773 + }, 5774 + { 5775 + "type": "OpenCollective", 5776 + "url": "https://opencollective.com/unified" 5777 + } 5778 + ], 5779 + "dependencies": { 5780 + "micromark-util-chunked": "^1.0.0", 5781 + "micromark-util-types": "^1.0.0" 5782 + } 5783 + }, 5784 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-decode-numeric-character-reference": { 5785 + "version": "1.1.0", 5786 + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", 5787 + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", 5788 + "funding": [ 5789 + { 5790 + "type": "GitHub Sponsors", 5791 + "url": "https://github.com/sponsors/unifiedjs" 5792 + }, 5793 + { 5794 + "type": "OpenCollective", 5795 + "url": "https://opencollective.com/unified" 5796 + } 5797 + ], 5798 + "dependencies": { 5799 + "micromark-util-symbol": "^1.0.0" 5800 + } 5801 + }, 5802 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-decode-string": { 5803 + "version": "1.1.0", 5804 + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", 5805 + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", 5806 + "funding": [ 5807 + { 5808 + "type": "GitHub Sponsors", 5809 + "url": "https://github.com/sponsors/unifiedjs" 5810 + }, 5811 + { 5812 + "type": "OpenCollective", 5813 + "url": "https://opencollective.com/unified" 5814 + } 5815 + ], 5816 + "dependencies": { 5817 + "decode-named-character-reference": "^1.0.0", 5818 + "micromark-util-character": "^1.0.0", 5819 + "micromark-util-decode-numeric-character-reference": "^1.0.0", 5820 + "micromark-util-symbol": "^1.0.0" 5821 + } 5822 + }, 5823 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-encode": { 5824 + "version": "1.1.0", 5825 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", 5826 + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", 5827 + "funding": [ 5828 + { 5829 + "type": "GitHub Sponsors", 5830 + "url": "https://github.com/sponsors/unifiedjs" 5831 + }, 5832 + { 5833 + "type": "OpenCollective", 5834 + "url": "https://opencollective.com/unified" 5835 + } 5836 + ] 5837 + }, 5838 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-html-tag-name": { 5839 + "version": "1.2.0", 5840 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", 5841 + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", 5842 + "funding": [ 5843 + { 5844 + "type": "GitHub Sponsors", 5845 + "url": "https://github.com/sponsors/unifiedjs" 5846 + }, 5847 + { 5848 + "type": "OpenCollective", 5849 + "url": "https://opencollective.com/unified" 5850 + } 5851 + ] 5852 + }, 5853 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-normalize-identifier": { 5854 + "version": "1.1.0", 5855 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", 5856 + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", 5857 + "funding": [ 5858 + { 5859 + "type": "GitHub Sponsors", 5860 + "url": "https://github.com/sponsors/unifiedjs" 5861 + }, 5862 + { 5863 + "type": "OpenCollective", 5864 + "url": "https://opencollective.com/unified" 5865 + } 5866 + ], 5867 + "dependencies": { 5868 + "micromark-util-symbol": "^1.0.0" 5869 + } 5870 + }, 5871 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-resolve-all": { 5872 + "version": "1.1.0", 5873 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", 5874 + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", 5875 + "funding": [ 5876 + { 5877 + "type": "GitHub Sponsors", 5878 + "url": "https://github.com/sponsors/unifiedjs" 5879 + }, 5880 + { 5881 + "type": "OpenCollective", 5882 + "url": "https://opencollective.com/unified" 5883 + } 5884 + ], 5885 + "dependencies": { 5886 + "micromark-util-types": "^1.0.0" 5887 + } 5888 + }, 5889 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-sanitize-uri": { 5890 + "version": "1.2.0", 5891 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", 5892 + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", 5893 + "funding": [ 5894 + { 5895 + "type": "GitHub Sponsors", 5896 + "url": "https://github.com/sponsors/unifiedjs" 5897 + }, 5898 + { 5899 + "type": "OpenCollective", 5900 + "url": "https://opencollective.com/unified" 5901 + } 5902 + ], 5903 + "dependencies": { 5904 + "micromark-util-character": "^1.0.0", 5905 + "micromark-util-encode": "^1.0.0", 5906 + "micromark-util-symbol": "^1.0.0" 5907 + } 5908 + }, 5909 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-subtokenize": { 5910 + "version": "1.1.0", 5911 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", 5912 + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", 5913 + "funding": [ 5914 + { 5915 + "type": "GitHub Sponsors", 5916 + "url": "https://github.com/sponsors/unifiedjs" 5917 + }, 5918 + { 5919 + "type": "OpenCollective", 5920 + "url": "https://opencollective.com/unified" 5921 + } 5922 + ], 5923 + "dependencies": { 5924 + "micromark-util-chunked": "^1.0.0", 5925 + "micromark-util-symbol": "^1.0.0", 5926 + "micromark-util-types": "^1.0.0", 5927 + "uvu": "^0.5.0" 5928 + } 5929 + }, 5930 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-symbol": { 5931 + "version": "1.1.0", 5932 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 5933 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 5934 + "funding": [ 5935 + { 5936 + "type": "GitHub Sponsors", 5937 + "url": "https://github.com/sponsors/unifiedjs" 5938 + }, 5939 + { 5940 + "type": "OpenCollective", 5941 + "url": "https://opencollective.com/unified" 5942 + } 5943 + ] 5944 + }, 5945 + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-types": { 5946 + "version": "1.1.0", 5947 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 5948 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 5949 + "funding": [ 5950 + { 5951 + "type": "GitHub Sponsors", 5952 + "url": "https://github.com/sponsors/unifiedjs" 5953 + }, 5954 + { 5955 + "type": "OpenCollective", 5956 + "url": "https://opencollective.com/unified" 5957 + } 5958 + ] 5959 + }, 2446 5960 "node_modules/merge2": { 2447 5961 "version": "1.4.1", 2448 5962 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", ··· 2452 5966 "node": ">= 8" 2453 5967 } 2454 5968 }, 5969 + "node_modules/micromark-extension-mdx-expression": { 5970 + "version": "1.0.8", 5971 + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.8.tgz", 5972 + "integrity": "sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==", 5973 + "funding": [ 5974 + { 5975 + "type": "GitHub Sponsors", 5976 + "url": "https://github.com/sponsors/unifiedjs" 5977 + }, 5978 + { 5979 + "type": "OpenCollective", 5980 + "url": "https://opencollective.com/unified" 5981 + } 5982 + ], 5983 + "dependencies": { 5984 + "@types/estree": "^1.0.0", 5985 + "micromark-factory-mdx-expression": "^1.0.0", 5986 + "micromark-factory-space": "^1.0.0", 5987 + "micromark-util-character": "^1.0.0", 5988 + "micromark-util-events-to-acorn": "^1.0.0", 5989 + "micromark-util-symbol": "^1.0.0", 5990 + "micromark-util-types": "^1.0.0", 5991 + "uvu": "^0.5.0" 5992 + } 5993 + }, 5994 + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { 5995 + "version": "1.1.0", 5996 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 5997 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 5998 + "funding": [ 5999 + { 6000 + "type": "GitHub Sponsors", 6001 + "url": "https://github.com/sponsors/unifiedjs" 6002 + }, 6003 + { 6004 + "type": "OpenCollective", 6005 + "url": "https://opencollective.com/unified" 6006 + } 6007 + ], 6008 + "dependencies": { 6009 + "micromark-util-character": "^1.0.0", 6010 + "micromark-util-types": "^1.0.0" 6011 + } 6012 + }, 6013 + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { 6014 + "version": "1.2.0", 6015 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 6016 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 6017 + "funding": [ 6018 + { 6019 + "type": "GitHub Sponsors", 6020 + "url": "https://github.com/sponsors/unifiedjs" 6021 + }, 6022 + { 6023 + "type": "OpenCollective", 6024 + "url": "https://opencollective.com/unified" 6025 + } 6026 + ], 6027 + "dependencies": { 6028 + "micromark-util-symbol": "^1.0.0", 6029 + "micromark-util-types": "^1.0.0" 6030 + } 6031 + }, 6032 + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { 6033 + "version": "1.1.0", 6034 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 6035 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 6036 + "funding": [ 6037 + { 6038 + "type": "GitHub Sponsors", 6039 + "url": "https://github.com/sponsors/unifiedjs" 6040 + }, 6041 + { 6042 + "type": "OpenCollective", 6043 + "url": "https://opencollective.com/unified" 6044 + } 6045 + ] 6046 + }, 6047 + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-types": { 6048 + "version": "1.1.0", 6049 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6050 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6051 + "funding": [ 6052 + { 6053 + "type": "GitHub Sponsors", 6054 + "url": "https://github.com/sponsors/unifiedjs" 6055 + }, 6056 + { 6057 + "type": "OpenCollective", 6058 + "url": "https://opencollective.com/unified" 6059 + } 6060 + ] 6061 + }, 6062 + "node_modules/micromark-extension-mdx-jsx": { 6063 + "version": "1.0.5", 6064 + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.5.tgz", 6065 + "integrity": "sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==", 6066 + "dependencies": { 6067 + "@types/acorn": "^4.0.0", 6068 + "@types/estree": "^1.0.0", 6069 + "estree-util-is-identifier-name": "^2.0.0", 6070 + "micromark-factory-mdx-expression": "^1.0.0", 6071 + "micromark-factory-space": "^1.0.0", 6072 + "micromark-util-character": "^1.0.0", 6073 + "micromark-util-symbol": "^1.0.0", 6074 + "micromark-util-types": "^1.0.0", 6075 + "uvu": "^0.5.0", 6076 + "vfile-message": "^3.0.0" 6077 + }, 6078 + "funding": { 6079 + "type": "opencollective", 6080 + "url": "https://opencollective.com/unified" 6081 + } 6082 + }, 6083 + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { 6084 + "version": "1.1.0", 6085 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 6086 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 6087 + "funding": [ 6088 + { 6089 + "type": "GitHub Sponsors", 6090 + "url": "https://github.com/sponsors/unifiedjs" 6091 + }, 6092 + { 6093 + "type": "OpenCollective", 6094 + "url": "https://opencollective.com/unified" 6095 + } 6096 + ], 6097 + "dependencies": { 6098 + "micromark-util-character": "^1.0.0", 6099 + "micromark-util-types": "^1.0.0" 6100 + } 6101 + }, 6102 + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { 6103 + "version": "1.2.0", 6104 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 6105 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 6106 + "funding": [ 6107 + { 6108 + "type": "GitHub Sponsors", 6109 + "url": "https://github.com/sponsors/unifiedjs" 6110 + }, 6111 + { 6112 + "type": "OpenCollective", 6113 + "url": "https://opencollective.com/unified" 6114 + } 6115 + ], 6116 + "dependencies": { 6117 + "micromark-util-symbol": "^1.0.0", 6118 + "micromark-util-types": "^1.0.0" 6119 + } 6120 + }, 6121 + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { 6122 + "version": "1.1.0", 6123 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 6124 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 6125 + "funding": [ 6126 + { 6127 + "type": "GitHub Sponsors", 6128 + "url": "https://github.com/sponsors/unifiedjs" 6129 + }, 6130 + { 6131 + "type": "OpenCollective", 6132 + "url": "https://opencollective.com/unified" 6133 + } 6134 + ] 6135 + }, 6136 + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-types": { 6137 + "version": "1.1.0", 6138 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6139 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6140 + "funding": [ 6141 + { 6142 + "type": "GitHub Sponsors", 6143 + "url": "https://github.com/sponsors/unifiedjs" 6144 + }, 6145 + { 6146 + "type": "OpenCollective", 6147 + "url": "https://opencollective.com/unified" 6148 + } 6149 + ] 6150 + }, 6151 + "node_modules/micromark-extension-mdx-md": { 6152 + "version": "1.0.1", 6153 + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.1.tgz", 6154 + "integrity": "sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==", 6155 + "dependencies": { 6156 + "micromark-util-types": "^1.0.0" 6157 + }, 6158 + "funding": { 6159 + "type": "opencollective", 6160 + "url": "https://opencollective.com/unified" 6161 + } 6162 + }, 6163 + "node_modules/micromark-extension-mdx-md/node_modules/micromark-util-types": { 6164 + "version": "1.1.0", 6165 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6166 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6167 + "funding": [ 6168 + { 6169 + "type": "GitHub Sponsors", 6170 + "url": "https://github.com/sponsors/unifiedjs" 6171 + }, 6172 + { 6173 + "type": "OpenCollective", 6174 + "url": "https://opencollective.com/unified" 6175 + } 6176 + ] 6177 + }, 6178 + "node_modules/micromark-extension-mdxjs": { 6179 + "version": "1.0.1", 6180 + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.1.tgz", 6181 + "integrity": "sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==", 6182 + "dependencies": { 6183 + "acorn": "^8.0.0", 6184 + "acorn-jsx": "^5.0.0", 6185 + "micromark-extension-mdx-expression": "^1.0.0", 6186 + "micromark-extension-mdx-jsx": "^1.0.0", 6187 + "micromark-extension-mdx-md": "^1.0.0", 6188 + "micromark-extension-mdxjs-esm": "^1.0.0", 6189 + "micromark-util-combine-extensions": "^1.0.0", 6190 + "micromark-util-types": "^1.0.0" 6191 + }, 6192 + "funding": { 6193 + "type": "opencollective", 6194 + "url": "https://opencollective.com/unified" 6195 + } 6196 + }, 6197 + "node_modules/micromark-extension-mdxjs-esm": { 6198 + "version": "1.0.5", 6199 + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.5.tgz", 6200 + "integrity": "sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==", 6201 + "dependencies": { 6202 + "@types/estree": "^1.0.0", 6203 + "micromark-core-commonmark": "^1.0.0", 6204 + "micromark-util-character": "^1.0.0", 6205 + "micromark-util-events-to-acorn": "^1.0.0", 6206 + "micromark-util-symbol": "^1.0.0", 6207 + "micromark-util-types": "^1.0.0", 6208 + "unist-util-position-from-estree": "^1.1.0", 6209 + "uvu": "^0.5.0", 6210 + "vfile-message": "^3.0.0" 6211 + }, 6212 + "funding": { 6213 + "type": "opencollective", 6214 + "url": "https://opencollective.com/unified" 6215 + } 6216 + }, 6217 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-core-commonmark": { 6218 + "version": "1.1.0", 6219 + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", 6220 + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", 6221 + "funding": [ 6222 + { 6223 + "type": "GitHub Sponsors", 6224 + "url": "https://github.com/sponsors/unifiedjs" 6225 + }, 6226 + { 6227 + "type": "OpenCollective", 6228 + "url": "https://opencollective.com/unified" 6229 + } 6230 + ], 6231 + "dependencies": { 6232 + "decode-named-character-reference": "^1.0.0", 6233 + "micromark-factory-destination": "^1.0.0", 6234 + "micromark-factory-label": "^1.0.0", 6235 + "micromark-factory-space": "^1.0.0", 6236 + "micromark-factory-title": "^1.0.0", 6237 + "micromark-factory-whitespace": "^1.0.0", 6238 + "micromark-util-character": "^1.0.0", 6239 + "micromark-util-chunked": "^1.0.0", 6240 + "micromark-util-classify-character": "^1.0.0", 6241 + "micromark-util-html-tag-name": "^1.0.0", 6242 + "micromark-util-normalize-identifier": "^1.0.0", 6243 + "micromark-util-resolve-all": "^1.0.0", 6244 + "micromark-util-subtokenize": "^1.0.0", 6245 + "micromark-util-symbol": "^1.0.0", 6246 + "micromark-util-types": "^1.0.1", 6247 + "uvu": "^0.5.0" 6248 + } 6249 + }, 6250 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-factory-destination": { 6251 + "version": "1.1.0", 6252 + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", 6253 + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", 6254 + "funding": [ 6255 + { 6256 + "type": "GitHub Sponsors", 6257 + "url": "https://github.com/sponsors/unifiedjs" 6258 + }, 6259 + { 6260 + "type": "OpenCollective", 6261 + "url": "https://opencollective.com/unified" 6262 + } 6263 + ], 6264 + "dependencies": { 6265 + "micromark-util-character": "^1.0.0", 6266 + "micromark-util-symbol": "^1.0.0", 6267 + "micromark-util-types": "^1.0.0" 6268 + } 6269 + }, 6270 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-factory-label": { 6271 + "version": "1.1.0", 6272 + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", 6273 + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", 6274 + "funding": [ 6275 + { 6276 + "type": "GitHub Sponsors", 6277 + "url": "https://github.com/sponsors/unifiedjs" 6278 + }, 6279 + { 6280 + "type": "OpenCollective", 6281 + "url": "https://opencollective.com/unified" 6282 + } 6283 + ], 6284 + "dependencies": { 6285 + "micromark-util-character": "^1.0.0", 6286 + "micromark-util-symbol": "^1.0.0", 6287 + "micromark-util-types": "^1.0.0", 6288 + "uvu": "^0.5.0" 6289 + } 6290 + }, 6291 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-factory-space": { 6292 + "version": "1.1.0", 6293 + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", 6294 + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", 6295 + "funding": [ 6296 + { 6297 + "type": "GitHub Sponsors", 6298 + "url": "https://github.com/sponsors/unifiedjs" 6299 + }, 6300 + { 6301 + "type": "OpenCollective", 6302 + "url": "https://opencollective.com/unified" 6303 + } 6304 + ], 6305 + "dependencies": { 6306 + "micromark-util-character": "^1.0.0", 6307 + "micromark-util-types": "^1.0.0" 6308 + } 6309 + }, 6310 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-factory-title": { 6311 + "version": "1.1.0", 6312 + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", 6313 + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", 6314 + "funding": [ 6315 + { 6316 + "type": "GitHub Sponsors", 6317 + "url": "https://github.com/sponsors/unifiedjs" 6318 + }, 6319 + { 6320 + "type": "OpenCollective", 6321 + "url": "https://opencollective.com/unified" 6322 + } 6323 + ], 6324 + "dependencies": { 6325 + "micromark-factory-space": "^1.0.0", 6326 + "micromark-util-character": "^1.0.0", 6327 + "micromark-util-symbol": "^1.0.0", 6328 + "micromark-util-types": "^1.0.0" 6329 + } 6330 + }, 6331 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-factory-whitespace": { 6332 + "version": "1.1.0", 6333 + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", 6334 + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", 6335 + "funding": [ 6336 + { 6337 + "type": "GitHub Sponsors", 6338 + "url": "https://github.com/sponsors/unifiedjs" 6339 + }, 6340 + { 6341 + "type": "OpenCollective", 6342 + "url": "https://opencollective.com/unified" 6343 + } 6344 + ], 6345 + "dependencies": { 6346 + "micromark-factory-space": "^1.0.0", 6347 + "micromark-util-character": "^1.0.0", 6348 + "micromark-util-symbol": "^1.0.0", 6349 + "micromark-util-types": "^1.0.0" 6350 + } 6351 + }, 6352 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { 6353 + "version": "1.2.0", 6354 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 6355 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 6356 + "funding": [ 6357 + { 6358 + "type": "GitHub Sponsors", 6359 + "url": "https://github.com/sponsors/unifiedjs" 6360 + }, 6361 + { 6362 + "type": "OpenCollective", 6363 + "url": "https://opencollective.com/unified" 6364 + } 6365 + ], 6366 + "dependencies": { 6367 + "micromark-util-symbol": "^1.0.0", 6368 + "micromark-util-types": "^1.0.0" 6369 + } 6370 + }, 6371 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-chunked": { 6372 + "version": "1.1.0", 6373 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 6374 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 6375 + "funding": [ 6376 + { 6377 + "type": "GitHub Sponsors", 6378 + "url": "https://github.com/sponsors/unifiedjs" 6379 + }, 6380 + { 6381 + "type": "OpenCollective", 6382 + "url": "https://opencollective.com/unified" 6383 + } 6384 + ], 6385 + "dependencies": { 6386 + "micromark-util-symbol": "^1.0.0" 6387 + } 6388 + }, 6389 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-classify-character": { 6390 + "version": "1.1.0", 6391 + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", 6392 + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", 6393 + "funding": [ 6394 + { 6395 + "type": "GitHub Sponsors", 6396 + "url": "https://github.com/sponsors/unifiedjs" 6397 + }, 6398 + { 6399 + "type": "OpenCollective", 6400 + "url": "https://opencollective.com/unified" 6401 + } 6402 + ], 6403 + "dependencies": { 6404 + "micromark-util-character": "^1.0.0", 6405 + "micromark-util-symbol": "^1.0.0", 6406 + "micromark-util-types": "^1.0.0" 6407 + } 6408 + }, 6409 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-html-tag-name": { 6410 + "version": "1.2.0", 6411 + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", 6412 + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", 6413 + "funding": [ 6414 + { 6415 + "type": "GitHub Sponsors", 6416 + "url": "https://github.com/sponsors/unifiedjs" 6417 + }, 6418 + { 6419 + "type": "OpenCollective", 6420 + "url": "https://opencollective.com/unified" 6421 + } 6422 + ] 6423 + }, 6424 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-normalize-identifier": { 6425 + "version": "1.1.0", 6426 + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", 6427 + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", 6428 + "funding": [ 6429 + { 6430 + "type": "GitHub Sponsors", 6431 + "url": "https://github.com/sponsors/unifiedjs" 6432 + }, 6433 + { 6434 + "type": "OpenCollective", 6435 + "url": "https://opencollective.com/unified" 6436 + } 6437 + ], 6438 + "dependencies": { 6439 + "micromark-util-symbol": "^1.0.0" 6440 + } 6441 + }, 6442 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-resolve-all": { 6443 + "version": "1.1.0", 6444 + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", 6445 + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", 6446 + "funding": [ 6447 + { 6448 + "type": "GitHub Sponsors", 6449 + "url": "https://github.com/sponsors/unifiedjs" 6450 + }, 6451 + { 6452 + "type": "OpenCollective", 6453 + "url": "https://opencollective.com/unified" 6454 + } 6455 + ], 6456 + "dependencies": { 6457 + "micromark-util-types": "^1.0.0" 6458 + } 6459 + }, 6460 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-subtokenize": { 6461 + "version": "1.1.0", 6462 + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", 6463 + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", 6464 + "funding": [ 6465 + { 6466 + "type": "GitHub Sponsors", 6467 + "url": "https://github.com/sponsors/unifiedjs" 6468 + }, 6469 + { 6470 + "type": "OpenCollective", 6471 + "url": "https://opencollective.com/unified" 6472 + } 6473 + ], 6474 + "dependencies": { 6475 + "micromark-util-chunked": "^1.0.0", 6476 + "micromark-util-symbol": "^1.0.0", 6477 + "micromark-util-types": "^1.0.0", 6478 + "uvu": "^0.5.0" 6479 + } 6480 + }, 6481 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { 6482 + "version": "1.1.0", 6483 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 6484 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 6485 + "funding": [ 6486 + { 6487 + "type": "GitHub Sponsors", 6488 + "url": "https://github.com/sponsors/unifiedjs" 6489 + }, 6490 + { 6491 + "type": "OpenCollective", 6492 + "url": "https://opencollective.com/unified" 6493 + } 6494 + ] 6495 + }, 6496 + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-types": { 6497 + "version": "1.1.0", 6498 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6499 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6500 + "funding": [ 6501 + { 6502 + "type": "GitHub Sponsors", 6503 + "url": "https://github.com/sponsors/unifiedjs" 6504 + }, 6505 + { 6506 + "type": "OpenCollective", 6507 + "url": "https://opencollective.com/unified" 6508 + } 6509 + ] 6510 + }, 6511 + "node_modules/micromark-extension-mdxjs/node_modules/micromark-util-chunked": { 6512 + "version": "1.1.0", 6513 + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", 6514 + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", 6515 + "funding": [ 6516 + { 6517 + "type": "GitHub Sponsors", 6518 + "url": "https://github.com/sponsors/unifiedjs" 6519 + }, 6520 + { 6521 + "type": "OpenCollective", 6522 + "url": "https://opencollective.com/unified" 6523 + } 6524 + ], 6525 + "dependencies": { 6526 + "micromark-util-symbol": "^1.0.0" 6527 + } 6528 + }, 6529 + "node_modules/micromark-extension-mdxjs/node_modules/micromark-util-combine-extensions": { 6530 + "version": "1.1.0", 6531 + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", 6532 + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", 6533 + "funding": [ 6534 + { 6535 + "type": "GitHub Sponsors", 6536 + "url": "https://github.com/sponsors/unifiedjs" 6537 + }, 6538 + { 6539 + "type": "OpenCollective", 6540 + "url": "https://opencollective.com/unified" 6541 + } 6542 + ], 6543 + "dependencies": { 6544 + "micromark-util-chunked": "^1.0.0", 6545 + "micromark-util-types": "^1.0.0" 6546 + } 6547 + }, 6548 + "node_modules/micromark-extension-mdxjs/node_modules/micromark-util-symbol": { 6549 + "version": "1.1.0", 6550 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 6551 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 6552 + "funding": [ 6553 + { 6554 + "type": "GitHub Sponsors", 6555 + "url": "https://github.com/sponsors/unifiedjs" 6556 + }, 6557 + { 6558 + "type": "OpenCollective", 6559 + "url": "https://opencollective.com/unified" 6560 + } 6561 + ] 6562 + }, 6563 + "node_modules/micromark-extension-mdxjs/node_modules/micromark-util-types": { 6564 + "version": "1.1.0", 6565 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6566 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6567 + "funding": [ 6568 + { 6569 + "type": "GitHub Sponsors", 6570 + "url": "https://github.com/sponsors/unifiedjs" 6571 + }, 6572 + { 6573 + "type": "OpenCollective", 6574 + "url": "https://opencollective.com/unified" 6575 + } 6576 + ] 6577 + }, 6578 + "node_modules/micromark-factory-mdx-expression": { 6579 + "version": "1.0.9", 6580 + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.9.tgz", 6581 + "integrity": "sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==", 6582 + "funding": [ 6583 + { 6584 + "type": "GitHub Sponsors", 6585 + "url": "https://github.com/sponsors/unifiedjs" 6586 + }, 6587 + { 6588 + "type": "OpenCollective", 6589 + "url": "https://opencollective.com/unified" 6590 + } 6591 + ], 6592 + "dependencies": { 6593 + "@types/estree": "^1.0.0", 6594 + "micromark-util-character": "^1.0.0", 6595 + "micromark-util-events-to-acorn": "^1.0.0", 6596 + "micromark-util-symbol": "^1.0.0", 6597 + "micromark-util-types": "^1.0.0", 6598 + "unist-util-position-from-estree": "^1.0.0", 6599 + "uvu": "^0.5.0", 6600 + "vfile-message": "^3.0.0" 6601 + } 6602 + }, 6603 + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { 6604 + "version": "1.2.0", 6605 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 6606 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 6607 + "funding": [ 6608 + { 6609 + "type": "GitHub Sponsors", 6610 + "url": "https://github.com/sponsors/unifiedjs" 6611 + }, 6612 + { 6613 + "type": "OpenCollective", 6614 + "url": "https://opencollective.com/unified" 6615 + } 6616 + ], 6617 + "dependencies": { 6618 + "micromark-util-symbol": "^1.0.0", 6619 + "micromark-util-types": "^1.0.0" 6620 + } 6621 + }, 6622 + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { 6623 + "version": "1.1.0", 6624 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 6625 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 6626 + "funding": [ 6627 + { 6628 + "type": "GitHub Sponsors", 6629 + "url": "https://github.com/sponsors/unifiedjs" 6630 + }, 6631 + { 6632 + "type": "OpenCollective", 6633 + "url": "https://opencollective.com/unified" 6634 + } 6635 + ] 6636 + }, 6637 + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-types": { 6638 + "version": "1.1.0", 6639 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6640 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6641 + "funding": [ 6642 + { 6643 + "type": "GitHub Sponsors", 6644 + "url": "https://github.com/sponsors/unifiedjs" 6645 + }, 6646 + { 6647 + "type": "OpenCollective", 6648 + "url": "https://opencollective.com/unified" 6649 + } 6650 + ] 6651 + }, 6652 + "node_modules/micromark-util-events-to-acorn": { 6653 + "version": "1.2.3", 6654 + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.3.tgz", 6655 + "integrity": "sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==", 6656 + "funding": [ 6657 + { 6658 + "type": "GitHub Sponsors", 6659 + "url": "https://github.com/sponsors/unifiedjs" 6660 + }, 6661 + { 6662 + "type": "OpenCollective", 6663 + "url": "https://opencollective.com/unified" 6664 + } 6665 + ], 6666 + "dependencies": { 6667 + "@types/acorn": "^4.0.0", 6668 + "@types/estree": "^1.0.0", 6669 + "@types/unist": "^2.0.0", 6670 + "estree-util-visit": "^1.0.0", 6671 + "micromark-util-symbol": "^1.0.0", 6672 + "micromark-util-types": "^1.0.0", 6673 + "uvu": "^0.5.0", 6674 + "vfile-message": "^3.0.0" 6675 + } 6676 + }, 6677 + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { 6678 + "version": "1.1.0", 6679 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 6680 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 6681 + "funding": [ 6682 + { 6683 + "type": "GitHub Sponsors", 6684 + "url": "https://github.com/sponsors/unifiedjs" 6685 + }, 6686 + { 6687 + "type": "OpenCollective", 6688 + "url": "https://opencollective.com/unified" 6689 + } 6690 + ] 6691 + }, 6692 + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-types": { 6693 + "version": "1.1.0", 6694 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 6695 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 6696 + "funding": [ 6697 + { 6698 + "type": "GitHub Sponsors", 6699 + "url": "https://github.com/sponsors/unifiedjs" 6700 + }, 6701 + { 6702 + "type": "OpenCollective", 6703 + "url": "https://opencollective.com/unified" 6704 + } 6705 + ] 6706 + }, 2455 6707 "node_modules/micromatch": { 2456 6708 "version": "4.0.5", 2457 6709 "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", ··· 2486 6738 "url": "https://github.com/sponsors/ljharb" 2487 6739 } 2488 6740 }, 6741 + "node_modules/mri": { 6742 + "version": "1.2.0", 6743 + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 6744 + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 6745 + "engines": { 6746 + "node": ">=4" 6747 + } 6748 + }, 2489 6749 "node_modules/ms": { 2490 6750 "version": "2.1.2", 2491 6751 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 2492 - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 2493 - "dev": true 6752 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 6753 + }, 6754 + "node_modules/mz": { 6755 + "version": "2.7.0", 6756 + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", 6757 + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", 6758 + "dev": true, 6759 + "dependencies": { 6760 + "any-promise": "^1.0.0", 6761 + "object-assign": "^4.0.1", 6762 + "thenify-all": "^1.0.0" 6763 + } 2494 6764 }, 2495 6765 "node_modules/nanoid": { 2496 6766 "version": "3.3.6", ··· 2516 6786 "dev": true 2517 6787 }, 2518 6788 "node_modules/next": { 2519 - "version": "13.5.6", 2520 - "resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz", 2521 - "integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==", 6789 + "version": "14.0.1-canary.2", 6790 + "resolved": "https://registry.npmjs.org/next/-/next-14.0.1-canary.2.tgz", 6791 + "integrity": "sha512-Mrfpzf6MHa3dCHrx+Z4KaEVbJEvfUY9OcaqOSeuw48skXAX9sKAIzMdiYpdQTcuzYdrsOFZwNMqUPTTYPE/z/A==", 2522 6792 "dependencies": { 2523 - "@next/env": "13.5.6", 6793 + "@next/env": "14.0.1-canary.2", 2524 6794 "@swc/helpers": "0.5.2", 2525 6795 "busboy": "1.6.0", 2526 6796 "caniuse-lite": "^1.0.30001406", ··· 2532 6802 "next": "dist/bin/next" 2533 6803 }, 2534 6804 "engines": { 2535 - "node": ">=16.14.0" 6805 + "node": ">=18.17.0" 2536 6806 }, 2537 6807 "optionalDependencies": { 2538 - "@next/swc-darwin-arm64": "13.5.6", 2539 - "@next/swc-darwin-x64": "13.5.6", 2540 - "@next/swc-linux-arm64-gnu": "13.5.6", 2541 - "@next/swc-linux-arm64-musl": "13.5.6", 2542 - "@next/swc-linux-x64-gnu": "13.5.6", 2543 - "@next/swc-linux-x64-musl": "13.5.6", 2544 - "@next/swc-win32-arm64-msvc": "13.5.6", 2545 - "@next/swc-win32-ia32-msvc": "13.5.6", 2546 - "@next/swc-win32-x64-msvc": "13.5.6" 6808 + "@next/swc-darwin-arm64": "14.0.1-canary.2", 6809 + "@next/swc-darwin-x64": "14.0.1-canary.2", 6810 + "@next/swc-linux-arm64-gnu": "14.0.1-canary.2", 6811 + "@next/swc-linux-arm64-musl": "14.0.1-canary.2", 6812 + "@next/swc-linux-x64-gnu": "14.0.1-canary.2", 6813 + "@next/swc-linux-x64-musl": "14.0.1-canary.2", 6814 + "@next/swc-win32-arm64-msvc": "14.0.1-canary.2", 6815 + "@next/swc-win32-ia32-msvc": "14.0.1-canary.2", 6816 + "@next/swc-win32-x64-msvc": "14.0.1-canary.2" 2547 6817 }, 2548 6818 "peerDependencies": { 2549 6819 "@opentelemetry/api": "^1.1.0", ··· 2560 6830 } 2561 6831 } 2562 6832 }, 6833 + "node_modules/next-mdx-remote": { 6834 + "version": "4.4.1", 6835 + "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.4.1.tgz", 6836 + "integrity": "sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==", 6837 + "dependencies": { 6838 + "@mdx-js/mdx": "^2.2.1", 6839 + "@mdx-js/react": "^2.2.1", 6840 + "vfile": "^5.3.0", 6841 + "vfile-matter": "^3.0.1" 6842 + }, 6843 + "engines": { 6844 + "node": ">=14", 6845 + "npm": ">=7" 6846 + }, 6847 + "peerDependencies": { 6848 + "react": ">=16.x <=18.x", 6849 + "react-dom": ">=16.x <=18.x" 6850 + } 6851 + }, 6852 + "node_modules/node-releases": { 6853 + "version": "2.0.13", 6854 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", 6855 + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", 6856 + "dev": true 6857 + }, 6858 + "node_modules/normalize-path": { 6859 + "version": "3.0.0", 6860 + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 6861 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 6862 + "dev": true, 6863 + "engines": { 6864 + "node": ">=0.10.0" 6865 + } 6866 + }, 6867 + "node_modules/normalize-range": { 6868 + "version": "0.1.2", 6869 + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 6870 + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", 6871 + "dev": true, 6872 + "engines": { 6873 + "node": ">=0.10.0" 6874 + } 6875 + }, 2563 6876 "node_modules/object-assign": { 2564 6877 "version": "4.1.1", 2565 6878 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", ··· 2567 6880 "dev": true, 2568 6881 "engines": { 2569 6882 "node": ">=0.10.0" 6883 + } 6884 + }, 6885 + "node_modules/object-hash": { 6886 + "version": "3.0.0", 6887 + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", 6888 + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", 6889 + "dev": true, 6890 + "engines": { 6891 + "node": ">= 6" 2570 6892 } 2571 6893 }, 2572 6894 "node_modules/object-inspect": { ··· 2704 7026 "node": ">= 0.8.0" 2705 7027 } 2706 7028 }, 7029 + "node_modules/overnight": { 7030 + "version": "1.8.0", 7031 + "resolved": "https://registry.npmjs.org/overnight/-/overnight-1.8.0.tgz", 7032 + "integrity": "sha512-8MK831bugEbObv4iVtcP6h3PS0ixi9gZiSRdqKuJKXhpv0URvn+03TZ2tlcXWcqXtCRrxb3InNT7ADFJYXbv9A==", 7033 + "engines": { 7034 + "vscode": "^1.32.0" 7035 + } 7036 + }, 2707 7037 "node_modules/p-limit": { 2708 7038 "version": "3.1.0", 2709 7039 "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", ··· 2746 7076 "node": ">=6" 2747 7077 } 2748 7078 }, 7079 + "node_modules/parse-entities": { 7080 + "version": "4.0.1", 7081 + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", 7082 + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", 7083 + "dependencies": { 7084 + "@types/unist": "^2.0.0", 7085 + "character-entities": "^2.0.0", 7086 + "character-entities-legacy": "^3.0.0", 7087 + "character-reference-invalid": "^2.0.0", 7088 + "decode-named-character-reference": "^1.0.0", 7089 + "is-alphanumerical": "^2.0.0", 7090 + "is-decimal": "^2.0.0", 7091 + "is-hexadecimal": "^2.0.0" 7092 + }, 7093 + "funding": { 7094 + "type": "github", 7095 + "url": "https://github.com/sponsors/wooorm" 7096 + } 7097 + }, 7098 + "node_modules/parse-numeric-range": { 7099 + "version": "1.3.0", 7100 + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", 7101 + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" 7102 + }, 7103 + "node_modules/parse5": { 7104 + "version": "6.0.1", 7105 + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 7106 + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 7107 + }, 2749 7108 "node_modules/path-exists": { 2750 7109 "version": "4.0.0", 2751 7110 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", ··· 2788 7147 "node": ">=8" 2789 7148 } 2790 7149 }, 7150 + "node_modules/periscopic": { 7151 + "version": "3.1.0", 7152 + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", 7153 + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", 7154 + "dependencies": { 7155 + "@types/estree": "^1.0.0", 7156 + "estree-walker": "^3.0.0", 7157 + "is-reference": "^3.0.0" 7158 + } 7159 + }, 2791 7160 "node_modules/picocolors": { 2792 7161 "version": "1.0.0", 2793 7162 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", ··· 2805 7174 "url": "https://github.com/sponsors/jonschlinkert" 2806 7175 } 2807 7176 }, 7177 + "node_modules/pify": { 7178 + "version": "2.3.0", 7179 + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 7180 + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 7181 + "dev": true, 7182 + "engines": { 7183 + "node": ">=0.10.0" 7184 + } 7185 + }, 7186 + "node_modules/pirates": { 7187 + "version": "4.0.6", 7188 + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", 7189 + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", 7190 + "dev": true, 7191 + "engines": { 7192 + "node": ">= 6" 7193 + } 7194 + }, 2808 7195 "node_modules/postcss": { 2809 7196 "version": "8.4.31", 2810 7197 "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", ··· 2832 7219 "node": "^10 || ^12 || >=14" 2833 7220 } 2834 7221 }, 7222 + "node_modules/postcss-import": { 7223 + "version": "15.1.0", 7224 + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", 7225 + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", 7226 + "dev": true, 7227 + "dependencies": { 7228 + "postcss-value-parser": "^4.0.0", 7229 + "read-cache": "^1.0.0", 7230 + "resolve": "^1.1.7" 7231 + }, 7232 + "engines": { 7233 + "node": ">=14.0.0" 7234 + }, 7235 + "peerDependencies": { 7236 + "postcss": "^8.0.0" 7237 + } 7238 + }, 7239 + "node_modules/postcss-js": { 7240 + "version": "4.0.1", 7241 + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", 7242 + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", 7243 + "dev": true, 7244 + "dependencies": { 7245 + "camelcase-css": "^2.0.1" 7246 + }, 7247 + "engines": { 7248 + "node": "^12 || ^14 || >= 16" 7249 + }, 7250 + "funding": { 7251 + "type": "opencollective", 7252 + "url": "https://opencollective.com/postcss/" 7253 + }, 7254 + "peerDependencies": { 7255 + "postcss": "^8.4.21" 7256 + } 7257 + }, 7258 + "node_modules/postcss-load-config": { 7259 + "version": "4.0.1", 7260 + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", 7261 + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", 7262 + "dev": true, 7263 + "dependencies": { 7264 + "lilconfig": "^2.0.5", 7265 + "yaml": "^2.1.1" 7266 + }, 7267 + "engines": { 7268 + "node": ">= 14" 7269 + }, 7270 + "funding": { 7271 + "type": "opencollective", 7272 + "url": "https://opencollective.com/postcss/" 7273 + }, 7274 + "peerDependencies": { 7275 + "postcss": ">=8.0.9", 7276 + "ts-node": ">=9.0.0" 7277 + }, 7278 + "peerDependenciesMeta": { 7279 + "postcss": { 7280 + "optional": true 7281 + }, 7282 + "ts-node": { 7283 + "optional": true 7284 + } 7285 + } 7286 + }, 7287 + "node_modules/postcss-nested": { 7288 + "version": "6.0.1", 7289 + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", 7290 + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", 7291 + "dev": true, 7292 + "dependencies": { 7293 + "postcss-selector-parser": "^6.0.11" 7294 + }, 7295 + "engines": { 7296 + "node": ">=12.0" 7297 + }, 7298 + "funding": { 7299 + "type": "opencollective", 7300 + "url": "https://opencollective.com/postcss/" 7301 + }, 7302 + "peerDependencies": { 7303 + "postcss": "^8.2.14" 7304 + } 7305 + }, 7306 + "node_modules/postcss-selector-parser": { 7307 + "version": "6.0.13", 7308 + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", 7309 + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", 7310 + "dev": true, 7311 + "dependencies": { 7312 + "cssesc": "^3.0.0", 7313 + "util-deprecate": "^1.0.2" 7314 + }, 7315 + "engines": { 7316 + "node": ">=4" 7317 + } 7318 + }, 7319 + "node_modules/postcss-value-parser": { 7320 + "version": "4.2.0", 7321 + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 7322 + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 7323 + "dev": true 7324 + }, 2835 7325 "node_modules/prelude-ls": { 2836 7326 "version": "1.2.1", 2837 7327 "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", ··· 2850 7340 "loose-envify": "^1.4.0", 2851 7341 "object-assign": "^4.1.1", 2852 7342 "react-is": "^16.13.1" 7343 + } 7344 + }, 7345 + "node_modules/property-information": { 7346 + "version": "6.3.0", 7347 + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz", 7348 + "integrity": "sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==", 7349 + "funding": { 7350 + "type": "github", 7351 + "url": "https://github.com/sponsors/wooorm" 2853 7352 } 2854 7353 }, 2855 7354 "node_modules/punycode": { ··· 2910 7409 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 2911 7410 "dev": true 2912 7411 }, 7412 + "node_modules/read-cache": { 7413 + "version": "1.0.0", 7414 + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", 7415 + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 7416 + "dev": true, 7417 + "dependencies": { 7418 + "pify": "^2.3.0" 7419 + } 7420 + }, 7421 + "node_modules/readdirp": { 7422 + "version": "3.6.0", 7423 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 7424 + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 7425 + "dev": true, 7426 + "dependencies": { 7427 + "picomatch": "^2.2.1" 7428 + }, 7429 + "engines": { 7430 + "node": ">=8.10.0" 7431 + } 7432 + }, 2913 7433 "node_modules/reflect.getprototypeof": { 2914 7434 "version": "1.0.4", 2915 7435 "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", ··· 2953 7473 "url": "https://github.com/sponsors/ljharb" 2954 7474 } 2955 7475 }, 7476 + "node_modules/rehype-parse": { 7477 + "version": "8.0.5", 7478 + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.5.tgz", 7479 + "integrity": "sha512-Ds3RglaY/+clEX2U2mHflt7NlMA72KspZ0JLUJgBBLpRddBcEw3H8uYZQliQriku22NZpYMfjDdSgHcjxue24A==", 7480 + "dependencies": { 7481 + "@types/hast": "^2.0.0", 7482 + "hast-util-from-parse5": "^7.0.0", 7483 + "parse5": "^6.0.0", 7484 + "unified": "^10.0.0" 7485 + }, 7486 + "funding": { 7487 + "type": "opencollective", 7488 + "url": "https://opencollective.com/unified" 7489 + } 7490 + }, 7491 + "node_modules/rehype-pretty-code": { 7492 + "version": "0.10.2", 7493 + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.10.2.tgz", 7494 + "integrity": "sha512-yBgk3S4yXtkAWVrkoN1DqDihjsaP0ReuN9Du4Dtkl/wsgwyqGNGuIUGi2etVHAOsi40e2KRHoOulQqnKPuscPA==", 7495 + "dependencies": { 7496 + "hash-obj": "^4.0.0", 7497 + "hast-util-to-string": "^2.0.0", 7498 + "parse-numeric-range": "^1.3.0", 7499 + "rehype-parse": "^8.0.3", 7500 + "unified": "^10.1.2", 7501 + "unist-util-visit": "^4.0.0" 7502 + }, 7503 + "engines": { 7504 + "node": ">=16" 7505 + }, 7506 + "peerDependencies": { 7507 + "shiki": "0.x" 7508 + } 7509 + }, 7510 + "node_modules/remark-mdx": { 7511 + "version": "2.3.0", 7512 + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-2.3.0.tgz", 7513 + "integrity": "sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==", 7514 + "dependencies": { 7515 + "mdast-util-mdx": "^2.0.0", 7516 + "micromark-extension-mdxjs": "^1.0.0" 7517 + }, 7518 + "funding": { 7519 + "type": "opencollective", 7520 + "url": "https://opencollective.com/unified" 7521 + } 7522 + }, 7523 + "node_modules/remark-rehype": { 7524 + "version": "10.1.0", 7525 + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", 7526 + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", 7527 + "dependencies": { 7528 + "@types/hast": "^2.0.0", 7529 + "@types/mdast": "^3.0.0", 7530 + "mdast-util-to-hast": "^12.1.0", 7531 + "unified": "^10.0.0" 7532 + }, 7533 + "funding": { 7534 + "type": "opencollective", 7535 + "url": "https://opencollective.com/unified" 7536 + } 7537 + }, 7538 + "node_modules/remark-rehype/node_modules/@types/mdast": { 7539 + "version": "3.0.14", 7540 + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", 7541 + "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", 7542 + "dependencies": { 7543 + "@types/unist": "^2" 7544 + } 7545 + }, 7546 + "node_modules/remark-rehype/node_modules/mdast-util-to-hast": { 7547 + "version": "12.3.0", 7548 + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", 7549 + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", 7550 + "dependencies": { 7551 + "@types/hast": "^2.0.0", 7552 + "@types/mdast": "^3.0.0", 7553 + "mdast-util-definitions": "^5.0.0", 7554 + "micromark-util-sanitize-uri": "^1.1.0", 7555 + "trim-lines": "^3.0.0", 7556 + "unist-util-generated": "^2.0.0", 7557 + "unist-util-position": "^4.0.0", 7558 + "unist-util-visit": "^4.0.0" 7559 + }, 7560 + "funding": { 7561 + "type": "opencollective", 7562 + "url": "https://opencollective.com/unified" 7563 + } 7564 + }, 7565 + "node_modules/remark-rehype/node_modules/micromark-util-character": { 7566 + "version": "1.2.0", 7567 + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", 7568 + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", 7569 + "funding": [ 7570 + { 7571 + "type": "GitHub Sponsors", 7572 + "url": "https://github.com/sponsors/unifiedjs" 7573 + }, 7574 + { 7575 + "type": "OpenCollective", 7576 + "url": "https://opencollective.com/unified" 7577 + } 7578 + ], 7579 + "dependencies": { 7580 + "micromark-util-symbol": "^1.0.0", 7581 + "micromark-util-types": "^1.0.0" 7582 + } 7583 + }, 7584 + "node_modules/remark-rehype/node_modules/micromark-util-encode": { 7585 + "version": "1.1.0", 7586 + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", 7587 + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", 7588 + "funding": [ 7589 + { 7590 + "type": "GitHub Sponsors", 7591 + "url": "https://github.com/sponsors/unifiedjs" 7592 + }, 7593 + { 7594 + "type": "OpenCollective", 7595 + "url": "https://opencollective.com/unified" 7596 + } 7597 + ] 7598 + }, 7599 + "node_modules/remark-rehype/node_modules/micromark-util-sanitize-uri": { 7600 + "version": "1.2.0", 7601 + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", 7602 + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", 7603 + "funding": [ 7604 + { 7605 + "type": "GitHub Sponsors", 7606 + "url": "https://github.com/sponsors/unifiedjs" 7607 + }, 7608 + { 7609 + "type": "OpenCollective", 7610 + "url": "https://opencollective.com/unified" 7611 + } 7612 + ], 7613 + "dependencies": { 7614 + "micromark-util-character": "^1.0.0", 7615 + "micromark-util-encode": "^1.0.0", 7616 + "micromark-util-symbol": "^1.0.0" 7617 + } 7618 + }, 7619 + "node_modules/remark-rehype/node_modules/micromark-util-symbol": { 7620 + "version": "1.1.0", 7621 + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", 7622 + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", 7623 + "funding": [ 7624 + { 7625 + "type": "GitHub Sponsors", 7626 + "url": "https://github.com/sponsors/unifiedjs" 7627 + }, 7628 + { 7629 + "type": "OpenCollective", 7630 + "url": "https://opencollective.com/unified" 7631 + } 7632 + ] 7633 + }, 7634 + "node_modules/remark-rehype/node_modules/micromark-util-types": { 7635 + "version": "1.1.0", 7636 + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", 7637 + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", 7638 + "funding": [ 7639 + { 7640 + "type": "GitHub Sponsors", 7641 + "url": "https://github.com/sponsors/unifiedjs" 7642 + }, 7643 + { 7644 + "type": "OpenCollective", 7645 + "url": "https://opencollective.com/unified" 7646 + } 7647 + ] 7648 + }, 7649 + "node_modules/remark-rehype/node_modules/unist-util-position": { 7650 + "version": "4.0.4", 7651 + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", 7652 + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", 7653 + "dependencies": { 7654 + "@types/unist": "^2.0.0" 7655 + }, 7656 + "funding": { 7657 + "type": "opencollective", 7658 + "url": "https://opencollective.com/unified" 7659 + } 7660 + }, 2956 7661 "node_modules/resolve": { 2957 7662 "version": "1.22.8", 2958 7663 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", ··· 3036 7741 "queue-microtask": "^1.2.2" 3037 7742 } 3038 7743 }, 7744 + "node_modules/sade": { 7745 + "version": "1.8.1", 7746 + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 7747 + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 7748 + "dependencies": { 7749 + "mri": "^1.1.0" 7750 + }, 7751 + "engines": { 7752 + "node": ">=6" 7753 + } 7754 + }, 3039 7755 "node_modules/safe-array-concat": { 3040 7756 "version": "1.0.1", 3041 7757 "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", ··· 3076 7792 "loose-envify": "^1.1.0" 3077 7793 } 3078 7794 }, 7795 + "node_modules/section-matter": { 7796 + "version": "1.0.0", 7797 + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", 7798 + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", 7799 + "dependencies": { 7800 + "extend-shallow": "^2.0.1", 7801 + "kind-of": "^6.0.0" 7802 + }, 7803 + "engines": { 7804 + "node": ">=4" 7805 + } 7806 + }, 3079 7807 "node_modules/semver": { 3080 7808 "version": "7.5.4", 3081 7809 "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", ··· 3141 7869 "node": ">=8" 3142 7870 } 3143 7871 }, 7872 + "node_modules/shiki": { 7873 + "version": "0.14.5", 7874 + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", 7875 + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", 7876 + "dependencies": { 7877 + "ansi-sequence-parser": "^1.1.0", 7878 + "jsonc-parser": "^3.2.0", 7879 + "vscode-oniguruma": "^1.7.0", 7880 + "vscode-textmate": "^8.0.0" 7881 + } 7882 + }, 3144 7883 "node_modules/side-channel": { 3145 7884 "version": "1.0.4", 3146 7885 "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", ··· 3164 7903 "node": ">=8" 3165 7904 } 3166 7905 }, 7906 + "node_modules/sort-keys": { 7907 + "version": "5.0.0", 7908 + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz", 7909 + "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==", 7910 + "dependencies": { 7911 + "is-plain-obj": "^4.0.0" 7912 + }, 7913 + "engines": { 7914 + "node": ">=12" 7915 + }, 7916 + "funding": { 7917 + "url": "https://github.com/sponsors/sindresorhus" 7918 + } 7919 + }, 7920 + "node_modules/source-map": { 7921 + "version": "0.7.4", 7922 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", 7923 + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", 7924 + "engines": { 7925 + "node": ">= 8" 7926 + } 7927 + }, 3167 7928 "node_modules/source-map-js": { 3168 7929 "version": "1.0.2", 3169 7930 "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", ··· 3172 7933 "node": ">=0.10.0" 3173 7934 } 3174 7935 }, 7936 + "node_modules/space-separated-tokens": { 7937 + "version": "2.0.2", 7938 + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", 7939 + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", 7940 + "funding": { 7941 + "type": "github", 7942 + "url": "https://github.com/sponsors/wooorm" 7943 + } 7944 + }, 7945 + "node_modules/sprintf-js": { 7946 + "version": "1.0.3", 7947 + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 7948 + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" 7949 + }, 3175 7950 "node_modules/streamsearch": { 3176 7951 "version": "1.1.0", 3177 7952 "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", ··· 3245 8020 "url": "https://github.com/sponsors/ljharb" 3246 8021 } 3247 8022 }, 8023 + "node_modules/stringify-entities": { 8024 + "version": "4.0.3", 8025 + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", 8026 + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", 8027 + "dependencies": { 8028 + "character-entities-html4": "^2.0.0", 8029 + "character-entities-legacy": "^3.0.0" 8030 + }, 8031 + "funding": { 8032 + "type": "github", 8033 + "url": "https://github.com/sponsors/wooorm" 8034 + } 8035 + }, 3248 8036 "node_modules/strip-ansi": { 3249 8037 "version": "6.0.1", 3250 8038 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", ··· 3266 8054 "node": ">=4" 3267 8055 } 3268 8056 }, 8057 + "node_modules/strip-bom-string": { 8058 + "version": "1.0.0", 8059 + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", 8060 + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", 8061 + "engines": { 8062 + "node": ">=0.10.0" 8063 + } 8064 + }, 3269 8065 "node_modules/strip-json-comments": { 3270 8066 "version": "3.1.1", 3271 8067 "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", ··· 3276 8072 }, 3277 8073 "funding": { 3278 8074 "url": "https://github.com/sponsors/sindresorhus" 8075 + } 8076 + }, 8077 + "node_modules/style-to-object": { 8078 + "version": "0.4.4", 8079 + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", 8080 + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", 8081 + "dependencies": { 8082 + "inline-style-parser": "0.1.1" 3279 8083 } 3280 8084 }, 3281 8085 "node_modules/styled-jsx": { ··· 3298 8102 "babel-plugin-macros": { 3299 8103 "optional": true 3300 8104 } 8105 + } 8106 + }, 8107 + "node_modules/sucrase": { 8108 + "version": "3.34.0", 8109 + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", 8110 + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", 8111 + "dev": true, 8112 + "dependencies": { 8113 + "@jridgewell/gen-mapping": "^0.3.2", 8114 + "commander": "^4.0.0", 8115 + "glob": "7.1.6", 8116 + "lines-and-columns": "^1.1.6", 8117 + "mz": "^2.7.0", 8118 + "pirates": "^4.0.1", 8119 + "ts-interface-checker": "^0.1.9" 8120 + }, 8121 + "bin": { 8122 + "sucrase": "bin/sucrase", 8123 + "sucrase-node": "bin/sucrase-node" 8124 + }, 8125 + "engines": { 8126 + "node": ">=8" 8127 + } 8128 + }, 8129 + "node_modules/sucrase/node_modules/glob": { 8130 + "version": "7.1.6", 8131 + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 8132 + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 8133 + "dev": true, 8134 + "dependencies": { 8135 + "fs.realpath": "^1.0.0", 8136 + "inflight": "^1.0.4", 8137 + "inherits": "2", 8138 + "minimatch": "^3.0.4", 8139 + "once": "^1.3.0", 8140 + "path-is-absolute": "^1.0.0" 8141 + }, 8142 + "engines": { 8143 + "node": "*" 8144 + }, 8145 + "funding": { 8146 + "url": "https://github.com/sponsors/isaacs" 3301 8147 } 3302 8148 }, 3303 8149 "node_modules/supports-color": { ··· 3324 8170 "url": "https://github.com/sponsors/ljharb" 3325 8171 } 3326 8172 }, 8173 + "node_modules/tailwindcss": { 8174 + "version": "3.3.5", 8175 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", 8176 + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", 8177 + "dev": true, 8178 + "dependencies": { 8179 + "@alloc/quick-lru": "^5.2.0", 8180 + "arg": "^5.0.2", 8181 + "chokidar": "^3.5.3", 8182 + "didyoumean": "^1.2.2", 8183 + "dlv": "^1.1.3", 8184 + "fast-glob": "^3.3.0", 8185 + "glob-parent": "^6.0.2", 8186 + "is-glob": "^4.0.3", 8187 + "jiti": "^1.19.1", 8188 + "lilconfig": "^2.1.0", 8189 + "micromatch": "^4.0.5", 8190 + "normalize-path": "^3.0.0", 8191 + "object-hash": "^3.0.0", 8192 + "picocolors": "^1.0.0", 8193 + "postcss": "^8.4.23", 8194 + "postcss-import": "^15.1.0", 8195 + "postcss-js": "^4.0.1", 8196 + "postcss-load-config": "^4.0.1", 8197 + "postcss-nested": "^6.0.1", 8198 + "postcss-selector-parser": "^6.0.11", 8199 + "resolve": "^1.22.2", 8200 + "sucrase": "^3.32.0" 8201 + }, 8202 + "bin": { 8203 + "tailwind": "lib/cli.js", 8204 + "tailwindcss": "lib/cli.js" 8205 + }, 8206 + "engines": { 8207 + "node": ">=14.0.0" 8208 + } 8209 + }, 3327 8210 "node_modules/tapable": { 3328 8211 "version": "2.2.1", 3329 8212 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", ··· 3339 8222 "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 3340 8223 "dev": true 3341 8224 }, 8225 + "node_modules/thenify": { 8226 + "version": "3.3.1", 8227 + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", 8228 + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", 8229 + "dev": true, 8230 + "dependencies": { 8231 + "any-promise": "^1.0.0" 8232 + } 8233 + }, 8234 + "node_modules/thenify-all": { 8235 + "version": "1.6.0", 8236 + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", 8237 + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", 8238 + "dev": true, 8239 + "dependencies": { 8240 + "thenify": ">= 3.1.0 < 4" 8241 + }, 8242 + "engines": { 8243 + "node": ">=0.8" 8244 + } 8245 + }, 3342 8246 "node_modules/to-regex-range": { 3343 8247 "version": "5.0.1", 3344 8248 "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", ··· 3351 8255 "node": ">=8.0" 3352 8256 } 3353 8257 }, 8258 + "node_modules/trim-lines": { 8259 + "version": "3.0.1", 8260 + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", 8261 + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", 8262 + "funding": { 8263 + "type": "github", 8264 + "url": "https://github.com/sponsors/wooorm" 8265 + } 8266 + }, 8267 + "node_modules/trough": { 8268 + "version": "2.1.0", 8269 + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", 8270 + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", 8271 + "funding": { 8272 + "type": "github", 8273 + "url": "https://github.com/sponsors/wooorm" 8274 + } 8275 + }, 3354 8276 "node_modules/ts-api-utils": { 3355 8277 "version": "1.0.3", 3356 8278 "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", ··· 3362 8284 "peerDependencies": { 3363 8285 "typescript": ">=4.2.0" 3364 8286 } 8287 + }, 8288 + "node_modules/ts-interface-checker": { 8289 + "version": "0.1.13", 8290 + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", 8291 + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", 8292 + "dev": true 3365 8293 }, 3366 8294 "node_modules/tsconfig-paths": { 3367 8295 "version": "3.14.2", ··· 3498 8426 "url": "https://github.com/sponsors/ljharb" 3499 8427 } 3500 8428 }, 8429 + "node_modules/unified": { 8430 + "version": "10.1.2", 8431 + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", 8432 + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", 8433 + "dependencies": { 8434 + "@types/unist": "^2.0.0", 8435 + "bail": "^2.0.0", 8436 + "extend": "^3.0.0", 8437 + "is-buffer": "^2.0.0", 8438 + "is-plain-obj": "^4.0.0", 8439 + "trough": "^2.0.0", 8440 + "vfile": "^5.0.0" 8441 + }, 8442 + "funding": { 8443 + "type": "opencollective", 8444 + "url": "https://opencollective.com/unified" 8445 + } 8446 + }, 8447 + "node_modules/unist-util-generated": { 8448 + "version": "2.0.1", 8449 + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", 8450 + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", 8451 + "funding": { 8452 + "type": "opencollective", 8453 + "url": "https://opencollective.com/unified" 8454 + } 8455 + }, 8456 + "node_modules/unist-util-is": { 8457 + "version": "5.2.1", 8458 + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", 8459 + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", 8460 + "dependencies": { 8461 + "@types/unist": "^2.0.0" 8462 + }, 8463 + "funding": { 8464 + "type": "opencollective", 8465 + "url": "https://opencollective.com/unified" 8466 + } 8467 + }, 8468 + "node_modules/unist-util-position-from-estree": { 8469 + "version": "1.1.2", 8470 + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.2.tgz", 8471 + "integrity": "sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==", 8472 + "dependencies": { 8473 + "@types/unist": "^2.0.0" 8474 + }, 8475 + "funding": { 8476 + "type": "opencollective", 8477 + "url": "https://opencollective.com/unified" 8478 + } 8479 + }, 8480 + "node_modules/unist-util-remove-position": { 8481 + "version": "4.0.2", 8482 + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-4.0.2.tgz", 8483 + "integrity": "sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==", 8484 + "dependencies": { 8485 + "@types/unist": "^2.0.0", 8486 + "unist-util-visit": "^4.0.0" 8487 + }, 8488 + "funding": { 8489 + "type": "opencollective", 8490 + "url": "https://opencollective.com/unified" 8491 + } 8492 + }, 8493 + "node_modules/unist-util-stringify-position": { 8494 + "version": "3.0.3", 8495 + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", 8496 + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", 8497 + "dependencies": { 8498 + "@types/unist": "^2.0.0" 8499 + }, 8500 + "funding": { 8501 + "type": "opencollective", 8502 + "url": "https://opencollective.com/unified" 8503 + } 8504 + }, 8505 + "node_modules/unist-util-visit": { 8506 + "version": "4.1.2", 8507 + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", 8508 + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", 8509 + "dependencies": { 8510 + "@types/unist": "^2.0.0", 8511 + "unist-util-is": "^5.0.0", 8512 + "unist-util-visit-parents": "^5.1.1" 8513 + }, 8514 + "funding": { 8515 + "type": "opencollective", 8516 + "url": "https://opencollective.com/unified" 8517 + } 8518 + }, 8519 + "node_modules/unist-util-visit-parents": { 8520 + "version": "5.1.3", 8521 + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", 8522 + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", 8523 + "dependencies": { 8524 + "@types/unist": "^2.0.0", 8525 + "unist-util-is": "^5.0.0" 8526 + }, 8527 + "funding": { 8528 + "type": "opencollective", 8529 + "url": "https://opencollective.com/unified" 8530 + } 8531 + }, 8532 + "node_modules/update-browserslist-db": { 8533 + "version": "1.0.13", 8534 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", 8535 + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", 8536 + "dev": true, 8537 + "funding": [ 8538 + { 8539 + "type": "opencollective", 8540 + "url": "https://opencollective.com/browserslist" 8541 + }, 8542 + { 8543 + "type": "tidelift", 8544 + "url": "https://tidelift.com/funding/github/npm/browserslist" 8545 + }, 8546 + { 8547 + "type": "github", 8548 + "url": "https://github.com/sponsors/ai" 8549 + } 8550 + ], 8551 + "dependencies": { 8552 + "escalade": "^3.1.1", 8553 + "picocolors": "^1.0.0" 8554 + }, 8555 + "bin": { 8556 + "update-browserslist-db": "cli.js" 8557 + }, 8558 + "peerDependencies": { 8559 + "browserslist": ">= 4.21.0" 8560 + } 8561 + }, 3501 8562 "node_modules/uri-js": { 3502 8563 "version": "4.4.1", 3503 8564 "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", ··· 3507 8568 "punycode": "^2.1.0" 3508 8569 } 3509 8570 }, 8571 + "node_modules/util-deprecate": { 8572 + "version": "1.0.2", 8573 + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 8574 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 8575 + "dev": true 8576 + }, 8577 + "node_modules/uvu": { 8578 + "version": "0.5.6", 8579 + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", 8580 + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", 8581 + "dependencies": { 8582 + "dequal": "^2.0.0", 8583 + "diff": "^5.0.0", 8584 + "kleur": "^4.0.3", 8585 + "sade": "^1.7.3" 8586 + }, 8587 + "bin": { 8588 + "uvu": "bin.js" 8589 + }, 8590 + "engines": { 8591 + "node": ">=8" 8592 + } 8593 + }, 8594 + "node_modules/vfile": { 8595 + "version": "5.3.7", 8596 + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", 8597 + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", 8598 + "dependencies": { 8599 + "@types/unist": "^2.0.0", 8600 + "is-buffer": "^2.0.0", 8601 + "unist-util-stringify-position": "^3.0.0", 8602 + "vfile-message": "^3.0.0" 8603 + }, 8604 + "funding": { 8605 + "type": "opencollective", 8606 + "url": "https://opencollective.com/unified" 8607 + } 8608 + }, 8609 + "node_modules/vfile-location": { 8610 + "version": "4.1.0", 8611 + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", 8612 + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", 8613 + "dependencies": { 8614 + "@types/unist": "^2.0.0", 8615 + "vfile": "^5.0.0" 8616 + }, 8617 + "funding": { 8618 + "type": "opencollective", 8619 + "url": "https://opencollective.com/unified" 8620 + } 8621 + }, 8622 + "node_modules/vfile-matter": { 8623 + "version": "3.0.1", 8624 + "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-3.0.1.tgz", 8625 + "integrity": "sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==", 8626 + "dependencies": { 8627 + "@types/js-yaml": "^4.0.0", 8628 + "is-buffer": "^2.0.0", 8629 + "js-yaml": "^4.0.0" 8630 + }, 8631 + "funding": { 8632 + "type": "opencollective", 8633 + "url": "https://opencollective.com/unified" 8634 + } 8635 + }, 8636 + "node_modules/vfile-message": { 8637 + "version": "3.1.4", 8638 + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", 8639 + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", 8640 + "dependencies": { 8641 + "@types/unist": "^2.0.0", 8642 + "unist-util-stringify-position": "^3.0.0" 8643 + }, 8644 + "funding": { 8645 + "type": "opencollective", 8646 + "url": "https://opencollective.com/unified" 8647 + } 8648 + }, 8649 + "node_modules/vscode-oniguruma": { 8650 + "version": "1.7.0", 8651 + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", 8652 + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" 8653 + }, 8654 + "node_modules/vscode-textmate": { 8655 + "version": "8.0.0", 8656 + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", 8657 + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" 8658 + }, 3510 8659 "node_modules/watchpack": { 3511 8660 "version": "2.4.0", 3512 8661 "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", ··· 3517 8666 }, 3518 8667 "engines": { 3519 8668 "node": ">=10.13.0" 8669 + } 8670 + }, 8671 + "node_modules/web-namespaces": { 8672 + "version": "2.0.1", 8673 + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", 8674 + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", 8675 + "funding": { 8676 + "type": "github", 8677 + "url": "https://github.com/sponsors/wooorm" 3520 8678 } 3521 8679 }, 3522 8680 "node_modules/which": { ··· 3622 8780 "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 3623 8781 "dev": true 3624 8782 }, 8783 + "node_modules/yaml": { 8784 + "version": "2.3.3", 8785 + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", 8786 + "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", 8787 + "dev": true, 8788 + "engines": { 8789 + "node": ">= 14" 8790 + } 8791 + }, 3625 8792 "node_modules/yocto-queue": { 3626 8793 "version": "0.1.0", 3627 8794 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", ··· 3632 8799 }, 3633 8800 "funding": { 3634 8801 "url": "https://github.com/sponsors/sindresorhus" 8802 + } 8803 + }, 8804 + "node_modules/zwitch": { 8805 + "version": "2.0.4", 8806 + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", 8807 + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", 8808 + "funding": { 8809 + "type": "github", 8810 + "url": "https://github.com/sponsors/wooorm" 3635 8811 } 3636 8812 } 3637 8813 }
+11 -2
package.json
··· 9 9 "lint": "next lint" 10 10 }, 11 11 "dependencies": { 12 + "colorjs.io": "^0.4.5", 13 + "gray-matter": "^4.0.3", 14 + "next": "^14.0.1-canary.2", 15 + "next-mdx-remote": "^4.4.1", 16 + "overnight": "^1.8.0", 12 17 "react": "^18", 13 18 "react-dom": "^18", 14 - "next": "13.5.6" 19 + "rehype-pretty-code": "^0.10.2", 20 + "shiki": "^0.14.5" 15 21 }, 16 22 "devDependencies": { 23 + "autoprefixer": "^10.4.16", 17 24 "eslint": "^8", 18 - "eslint-config-next": "13.5.6" 25 + "eslint-config-next": "13.5.6", 26 + "postcss": "^8.4.31", 27 + "tailwindcss": "^3.3.5" 19 28 } 20 29 }
+6
postcss.config.js
··· 1 + module.exports = { 2 + plugins: { 3 + tailwindcss: {}, 4 + autoprefixer: {}, 5 + }, 6 + }
public/a-complete-guide-to-useeffect/counter.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/deja_vu.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/deps-compare-correct.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/deps-compare-wrong.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/exhaustive-deps.gif

This is a binary file and will not be displayed.

+1674
public/a-complete-guide-to-useeffect/index.md
··· 1 + --- 2 + title: A Complete Guide to useEffect 3 + date: '2019-03-09' 4 + spoiler: Effects are a part of your data flow. 5 + cta: 'react' 6 + --- 7 + 8 + You wrote a few components with [Hooks](https://reactjs.org/docs/hooks-intro.html). Maybe even a small app. You’re mostly satisfied. You’re comfortable with the API and picked up a few tricks along the way. You even made some [custom Hooks](https://reactjs.org/docs/hooks-custom.html) to extract repetitive logic (300 lines gone!) and showed it off to your colleagues. “Great job”, they said. 9 + 10 + But sometimes when you `useEffect`, the pieces don’t quite fit together. You have a nagging feeling that you’re missing something. It seems similar to class lifecycles... but is it really? You find yourself asking questions like: 11 + 12 + * 🤔 How do I replicate `componentDidMount` with `useEffect`? 13 + * 🤔 How do I correctly fetch data inside `useEffect`? What is `[]`? 14 + * 🤔 Do I need to specify functions as effect dependencies or not? 15 + * 🤔 Why do I sometimes get an infinite refetching loop? 16 + * 🤔 Why do I sometimes get an old state or prop value inside my effect? 17 + 18 + When I just started using Hooks, I was confused by all of those questions too. Even when writing the initial docs, I didn’t have a firm grasp on some of the subtleties. I’ve since had a few “aha” moments that I want to share with you. **This deep dive will make the answers to these questions look obvious to you.** 19 + 20 + To *see* the answers, we need to take a step back. The goal of this article isn’t to give you a list of bullet point recipes. It’s to help you truly “grok” `useEffect`. There won’t be much to learn. In fact, we’ll spend most of our time *un*learning. 21 + 22 + **It’s only after I stopped looking at the `useEffect` Hook through the prism of the familiar class lifecycle methods that everything came together for me.** 23 + 24 + >“Unlearn what you have learned.” — Yoda 25 + 26 + ![Yoda sniffing the air. Caption: “I smell bacon.”](./yoda.jpg) 27 + 28 + --- 29 + 30 + **This article assumes that you’re somewhat familiar with [`useEffect`](https://reactjs.org/docs/hooks-effect.html) API.** 31 + 32 + **It’s also *really* long. It’s like a mini-book. That’s just my preferred format. But I wrote a TLDR just below if you’re in a rush or don’t really care.** 33 + 34 + **If you’re not comfortable with deep dives, you might want to wait until these explanations appear elsewhere. Just like when React came out in 2013, it will take some time for people to recognize a different mental model and teach it.** 35 + 36 + --- 37 + 38 + ## TLDR 39 + 40 + Here’s a quick TLDR if you don’t want to read the whole thing. If some parts don’t make sense, you can scroll down until you find something related. 41 + 42 + Feel free to skip it if you plan to read the whole post. I’ll link to it at the end. 43 + 44 + 45 + **🤔 Question: How do I replicate `componentDidMount` with `useEffect`?** 46 + 47 + While you can `useEffect(fn, [])`, it’s not an exact equivalent. Unlike `componentDidMount`, it will *capture* props and state. So even inside the callbacks, you’ll see the initial props and state. If you want to see “latest” something, you can write it to a ref. But there’s usually a simpler way to structure the code so that you don’t have to. Keep in mind that the mental model for effects is different from `componentDidMount` and other lifecycles, and trying to find their exact equivalents may confuse you more than help. To get productive, you need to “think in effects”, and their mental model is closer to implementing synchronization than to responding to lifecycle events. 48 + 49 + **🤔 Question: How do I correctly fetch data inside `useEffect`? What is `[]`?** 50 + 51 + [This article](https://www.robinwieruch.de/react-hooks-fetch-data/) is a good primer on data fetching with `useEffect`. Make sure to read it to the end! It’s not as long as this one. `[]` means the effect doesn’t use any value that participates in React data flow, and is for that reason safe to apply once. It is also a common source of bugs when the value actually *is* used. You’ll need to learn a few strategies (primarily `useReducer` and `useCallback`) that can *remove the need* for a dependency instead of incorrectly omitting it. 52 + 53 + **🤔 Question: Do I need to specify functions as effect dependencies or not?** 54 + 55 + The recommendation is to hoist functions that don’t need props or state *outside* of your component, and pull the ones that are used only by an effect *inside* of that effect. If after that your effect still ends up using functions in the render scope (including function from props), wrap them into `useCallback` where they’re defined, and repeat the process. Why does it matter? Functions can “see” values from props and state — so they participate in the data flow. There's a [more detailed answer](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) in our FAQ. 56 + 57 + **🤔 Question: Why do I sometimes get an infinite refetching loop?** 58 + 59 + This can happen if you’re doing data fetching in an effect without the second dependencies argument. Without it, effects run after every render — and setting the state will trigger the effects again. An infinite loop may also happen if you specify a value that *always* changes in the dependency array. You can tell which one by removing them one by one. However, removing a dependency you use (or blindly specifying `[]`) is usually the wrong fix. Instead, fix the problem at its source. For example, functions can cause this problem, and putting them inside effects, hoisting them out, or wrapping them with `useCallback` helps. To avoid recreating objects, `useMemo` can serve a similar purpose. 60 + 61 + **🤔 Why do I sometimes get an old state or prop value inside my effect?** 62 + 63 + Effects always “see” props and state from the render they were defined in. That [helps prevent bugs](/how-are-function-components-different-from-classes/) but in some cases can be annoying. For those cases, you can explicitly maintain some value in a mutable ref (the linked article explains it at the end). If you think you’re seeing some props or state from an old render but don’t expect it, you probably missed some dependencies. Try using the [lint rule](https://github.com/facebook/react/issues/14920) to train yourself to see them. A few days, and it’ll be like a second nature to you. See also [this answer](https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function) in our FAQ. 64 + 65 + --- 66 + 67 + I hope this TLDR was helpful! Otherwise, let’s go. 68 + 69 + --- 70 + 71 + ## Each Render Has Its Own Props and State 72 + 73 + Before we can talk about effects, we need to talk about rendering. 74 + 75 + Here’s a counter. Look at the highlighted line closely: 76 + 77 + ```jsx{6} 78 + function Counter() { 79 + const [count, setCount] = useState(0); 80 + 81 + return ( 82 + <div> 83 + <p>You clicked {count} times</p> 84 + <button onClick={() => setCount(count + 1)}> 85 + Click me 86 + </button> 87 + </div> 88 + ); 89 + } 90 + ``` 91 + 92 + What does it mean? Does `count` somehow “watch” changes to our state and update automatically? That might be a useful first intuition when you learn React but it’s *not* an [accurate mental model](https://overreacted.io/react-as-a-ui-runtime/). 93 + 94 + **In this example, `count` is just a number.** It’s not a magic “data binding”, a “watcher”, a “proxy”, or anything else. It’s a good old number like this one: 95 + 96 + ```jsx 97 + const count = 42; 98 + // ... 99 + <p>You clicked {count} times</p> 100 + // ... 101 + ``` 102 + 103 + The first time our component renders, the `count` variable we get from `useState()` is `0`. When we call `setCount(1)`, React calls our component again. This time, `count` will be `1`. And so on: 104 + 105 + ```jsx{3,11,19} 106 + // During first render 107 + function Counter() { 108 + const count = 0; // Returned by useState() 109 + // ... 110 + <p>You clicked {count} times</p> 111 + // ... 112 + } 113 + 114 + // After a click, our function is called again 115 + function Counter() { 116 + const count = 1; // Returned by useState() 117 + // ... 118 + <p>You clicked {count} times</p> 119 + // ... 120 + } 121 + 122 + // After another click, our function is called again 123 + function Counter() { 124 + const count = 2; // Returned by useState() 125 + // ... 126 + <p>You clicked {count} times</p> 127 + // ... 128 + } 129 + ``` 130 + 131 + **Whenever we update the state, React calls our component. Each render result “sees” its own `counter` state value which is a *constant* inside our function.** 132 + 133 + So this line doesn’t do any special data binding: 134 + 135 + ```jsx 136 + <p>You clicked {count} times</p> 137 + ``` 138 + 139 + **It only embeds a number value into the render output.** That number is provided by React. When we `setCount`, React calls our component again with a different `count` value. Then React updates the DOM to match our latest render output. 140 + 141 + The key takeaway is that the `count` constant inside any particular render doesn’t change over time. It’s our component that’s called again — and each render “sees” its own `count` value that’s isolated between renders. 142 + 143 + *(For an in-depth overview of this process, check out my post [React as a UI Runtime](https://overreacted.io/react-as-a-ui-runtime/).)* 144 + 145 + ## Each Render Has Its Own Event Handlers 146 + 147 + So far so good. What about event handlers? 148 + 149 + Look at this example. It shows an alert with the `count` after three seconds: 150 + 151 + ```jsx{4-8,16-18} 152 + function Counter() { 153 + const [count, setCount] = useState(0); 154 + 155 + function handleAlertClick() { 156 + setTimeout(() => { 157 + alert('You clicked on: ' + count); 158 + }, 3000); 159 + } 160 + 161 + return ( 162 + <div> 163 + <p>You clicked {count} times</p> 164 + <button onClick={() => setCount(count + 1)}> 165 + Click me 166 + </button> 167 + <button onClick={handleAlertClick}> 168 + Show alert 169 + </button> 170 + </div> 171 + ); 172 + } 173 + ``` 174 + 175 + Let’s say I do this sequence of steps: 176 + 177 + * **Increment** the counter to 3 178 + * **Press** “Show alert” 179 + * **Increment** it to 5 before the timeout fires 180 + 181 + ![Counter demo](./counter.gif) 182 + 183 + What do you expect the alert to show? Will it show 5 — which is the counter state at the time of the alert? Or will it show 3 — the state when I clicked? 184 + 185 + ---- 186 + 187 + *spoilers ahead* 188 + 189 + --- 190 + 191 + Go ahead and [try it yourself!](https://codesandbox.io/s/w2wxl3yo0l) 192 + 193 + If the behavior doesn’t quite make sense to you, imagine a more practical example: a chat app with the current recipient ID in the state, and a Send button. [This article](https://overreacted.io/how-are-function-components-different-from-classes/) explores the reasons in depth but the correct answer is 3. 194 + 195 + The alert will “capture” the state at the time I clicked the button. 196 + 197 + *(There are ways to implement the other behavior too but I’ll be focusing on the default case for now. When building a mental model, it’s important that we distinguish the “path of least resistance” from the opt-in escape hatches.)* 198 + 199 + --- 200 + 201 + But how does it work? 202 + 203 + We’ve discussed that the `count` value is constant for every particular call to our function. It’s worth emphasizing this — **our function gets called many times (once per each render), but every one of those times the `count` value inside of it is constant and set to a particular value (state for that render).** 204 + 205 + This is not specific to React — regular functions work in a similar way: 206 + 207 + ```jsx{2} 208 + function sayHi(person) { 209 + const name = person.name; 210 + setTimeout(() => { 211 + alert('Hello, ' + name); 212 + }, 3000); 213 + } 214 + 215 + let someone = {name: 'Dan'}; 216 + sayHi(someone); 217 + 218 + someone = {name: 'Yuzhi'}; 219 + sayHi(someone); 220 + 221 + someone = {name: 'Dominic'}; 222 + sayHi(someone); 223 + ``` 224 + 225 + In [this example](https://codesandbox.io/s/mm6ww11lk8), the outer `someone` variable is reassigned several times. (Just like somewhere in React, the *current* component state can change.) **However, inside `sayHi`, there is a local `name` constant that is associated with a `person` from a particular call.** That constant is local, so it’s isolated between the calls! As a result, when the timeouts fire, each alert “remembers” its own `name`. 226 + 227 + This explains how our event handler captures the `count` at the time of the click. If we apply the same substitution principle, each render “sees” its own `count`: 228 + 229 + ```jsx{3,15,27} 230 + // During first render 231 + function Counter() { 232 + const count = 0; // Returned by useState() 233 + // ... 234 + function handleAlertClick() { 235 + setTimeout(() => { 236 + alert('You clicked on: ' + count); 237 + }, 3000); 238 + } 239 + // ... 240 + } 241 + 242 + // After a click, our function is called again 243 + function Counter() { 244 + const count = 1; // Returned by useState() 245 + // ... 246 + function handleAlertClick() { 247 + setTimeout(() => { 248 + alert('You clicked on: ' + count); 249 + }, 3000); 250 + } 251 + // ... 252 + } 253 + 254 + // After another click, our function is called again 255 + function Counter() { 256 + const count = 2; // Returned by useState() 257 + // ... 258 + function handleAlertClick() { 259 + setTimeout(() => { 260 + alert('You clicked on: ' + count); 261 + }, 3000); 262 + } 263 + // ... 264 + } 265 + ``` 266 + 267 + So effectively, each render returns its own “version” of `handleAlertClick`. Each of those versions “remembers” its own `count`: 268 + 269 + ```jsx{6,10,19,23,32,36} 270 + // During first render 271 + function Counter() { 272 + // ... 273 + function handleAlertClick() { 274 + setTimeout(() => { 275 + alert('You clicked on: ' + 0); 276 + }, 3000); 277 + } 278 + // ... 279 + <button onClick={handleAlertClick} /> // The one with 0 inside 280 + // ... 281 + } 282 + 283 + // After a click, our function is called again 284 + function Counter() { 285 + // ... 286 + function handleAlertClick() { 287 + setTimeout(() => { 288 + alert('You clicked on: ' + 1); 289 + }, 3000); 290 + } 291 + // ... 292 + <button onClick={handleAlertClick} /> // The one with 1 inside 293 + // ... 294 + } 295 + 296 + // After another click, our function is called again 297 + function Counter() { 298 + // ... 299 + function handleAlertClick() { 300 + setTimeout(() => { 301 + alert('You clicked on: ' + 2); 302 + }, 3000); 303 + } 304 + // ... 305 + <button onClick={handleAlertClick} /> // The one with 2 inside 306 + // ... 307 + } 308 + ``` 309 + 310 + This is why [in this demo](https://codesandbox.io/s/w2wxl3yo0l) event handlers “belong” to a particular render, and when you click, it keeps using the `counter` state *from* that render. 311 + 312 + **Inside any particular render, props and state forever stay the same.** But if props and state are isolated between renders, so are any values using them (including the event handlers). They also “belong” to a particular render. So even async functions inside an event handler will “see” the same `count` value. 313 + 314 + *Side note: I inlined concrete `count` values right into `handleAlertClick` functions above. This mental substitution is safe because `count` can’t possibly change within a particular render. It’s declared as a `const` and is a number. It would be safe to think the same way about other values like objects too, but only if we agree to avoid mutating state. Calling `setSomething(newObj)` with a newly created object instead of mutating it is fine because state belonging to previous renders is intact.* 315 + 316 + ## Each Render Has Its Own Effects 317 + 318 + This was supposed to be a post about effects but we still haven’t talked about effects yet! We’ll rectify this now. Turns out, effects aren’t really any different. 319 + 320 + Let’s go back to an example from [the docs](https://reactjs.org/docs/hooks-effect.html): 321 + 322 + ```jsx{4-6} 323 + function Counter() { 324 + const [count, setCount] = useState(0); 325 + 326 + useEffect(() => { 327 + document.title = `You clicked ${count} times`; 328 + }); 329 + 330 + return ( 331 + <div> 332 + <p>You clicked {count} times</p> 333 + <button onClick={() => setCount(count + 1)}> 334 + Click me 335 + </button> 336 + </div> 337 + ); 338 + } 339 + ``` 340 + 341 + **Here’s a question for you: how does the effect read the latest `count` state?** 342 + 343 + Maybe, there’s some kind of “data binding” or “watching” that makes `count` update live inside the effect function? Maybe `count` is a mutable variable that React sets inside our component so that our effect always sees the latest value? 344 + 345 + Nope. 346 + 347 + We already know that `count` is constant within a particular component render. Event handlers “see” the `count` state from the render that they “belong” to because `count` is a variable in their scope. The same is true for effects! 348 + 349 + **It’s not the `count` variable that somehow changes inside an “unchanging” effect. It’s the _effect function itself_ that’s different on every render.** 350 + 351 + Each version “sees” the `count` value from the render that it “belongs” to: 352 + 353 + ```jsx{5-8,17-20,29-32} 354 + // During first render 355 + function Counter() { 356 + // ... 357 + useEffect( 358 + // Effect function from first render 359 + () => { 360 + document.title = `You clicked ${0} times`; 361 + } 362 + ); 363 + // ... 364 + } 365 + 366 + // After a click, our function is called again 367 + function Counter() { 368 + // ... 369 + useEffect( 370 + // Effect function from second render 371 + () => { 372 + document.title = `You clicked ${1} times`; 373 + } 374 + ); 375 + // ... 376 + } 377 + 378 + // After another click, our function is called again 379 + function Counter() { 380 + // ... 381 + useEffect( 382 + // Effect function from third render 383 + () => { 384 + document.title = `You clicked ${2} times`; 385 + } 386 + ); 387 + // .. 388 + } 389 + ``` 390 + 391 + React remembers the effect function you provided, and runs it after flushing changes to the DOM and letting the browser paint the screen. 392 + 393 + So even if we speak of a single conceptual *effect* here (updating the document title), it is represented by a *different function* on every render — and each effect function “sees” props and state from the particular render it “belongs” to. 394 + 395 + **Conceptually, you can imagine effects are a *part of the render result*.** 396 + 397 + Strictly saying, they’re not (in order to [allow Hook composition](https://overreacted.io/why-do-hooks-rely-on-call-order/) without clumsy syntax or runtime overhead). But in the mental model we’re building up, effect functions *belong* to a particular render in the same way that event handlers do. 398 + 399 + --- 400 + 401 + To make sure we have a solid understanding, let’s recap our first render: 402 + 403 + * **React:** Give me the UI when the state is `0`. 404 + * **Your component:** 405 + * Here’s the render result: 406 + `<p>You clicked 0 times</p>`. 407 + * Also remember to run this effect after you’re done: `() => { document.title = 'You clicked 0 times' }`. 408 + * **React:** Sure. Updating the UI. Hey browser, I’m adding some stuff to the DOM. 409 + * **Browser:** Cool, I painted it to the screen. 410 + * **React:** OK, now I’m going to run the effect you gave me. 411 + * Running `() => { document.title = 'You clicked 0 times' }`. 412 + 413 + --- 414 + 415 + Now let’s recap what happens after we click: 416 + 417 + * **Your component:** Hey React, set my state to `1`. 418 + * **React:** Give me the UI for when the state is `1`. 419 + * **Your component:** 420 + * Here’s the render result: 421 + `<p>You clicked 1 times</p>`. 422 + * Also remember to run this effect after you’re done: `() => { document.title = 'You clicked 1 times' }`. 423 + * **React:** Sure. Updating the UI. Hey browser, I’ve changed the DOM. 424 + * **Browser:** Cool, I painted your changes to the screen. 425 + * **React:** OK, now I’ll run the effect that belongs to the render I just did. 426 + * Running `() => { document.title = 'You clicked 1 times' }`. 427 + 428 + --- 429 + 430 + ## Each Render Has Its Own... Everything 431 + 432 + **We know now that effects run after every render, are conceptually a part of the component output, and “see” the props and state from that particular render.** 433 + 434 + Let’s try a thought experiment. Consider this code: 435 + 436 + ```jsx{4-8} 437 + function Counter() { 438 + const [count, setCount] = useState(0); 439 + 440 + useEffect(() => { 441 + setTimeout(() => { 442 + console.log(`You clicked ${count} times`); 443 + }, 3000); 444 + }); 445 + 446 + return ( 447 + <div> 448 + <p>You clicked {count} times</p> 449 + <button onClick={() => setCount(count + 1)}> 450 + Click me 451 + </button> 452 + </div> 453 + ); 454 + } 455 + ``` 456 + 457 + If I click several times with a small delay, what is the log going to look like? 458 + 459 + --- 460 + 461 + *spoilers ahead* 462 + 463 + --- 464 + 465 + You might think this is a gotcha and the end result is unintuitive. It’s not! We’re going to see a sequence of logs — each one belonging to a particular render and thus with its own `count` value. You can [try it yourself](https://codesandbox.io/s/lyx20m1ol): 466 + 467 + 468 + ![Screen recording of 1, 2, 3, 4, 5 logged in order](./timeout_counter.gif) 469 + 470 + You may think: “Of course that’s how it works! How else could it work?” 471 + 472 + Well, that’s not how `this.state` works in classes. It’s easy to make the mistake of thinking that this [class implementation](https://codesandbox.io/s/kkymzwjqz3) is equivalent: 473 + 474 + ```jsx 475 + componentDidUpdate() { 476 + setTimeout(() => { 477 + console.log(`You clicked ${this.state.count} times`); 478 + }, 3000); 479 + } 480 + ``` 481 + 482 + However, `this.state.count` always points at the *latest* count rather than the one belonging to a particular render. So you’ll see `5` logged each time instead: 483 + 484 + ![Screen recording of 5, 5, 5, 5, 5 logged in order](./timeout_counter_class.gif) 485 + 486 + I think it’s ironic that Hooks rely so much on JavaScript closures, and yet it’s the class implementation that suffers from [the canonical wrong-value-in-a-timeout confusion](https://wsvincent.com/javascript-closure-settimeout-for-loop/) that’s often associated with closures. This is because the actual source of the confusion in this example is the mutation (React mutates `this.state` in classes to point to the latest state) and not closures themselves. 487 + 488 + **Closures are great when the values you close over never change. That makes them easy to think about because you’re essentially referring to constants.** And as we discussed, props and state never change within a particular render. By the way, we can fix the class version... by [using a closure](https://codesandbox.io/s/w7vjo07055). 489 + 490 + ## Swimming Against the Tide 491 + 492 + At this point it’s important that we call it out explicitly: **every** function inside the component render (including event handlers, effects, timeouts or API calls inside them) captures the props and state of the render call that defined it. 493 + 494 + So these two examples are equivalent: 495 + 496 + ```jsx{4} 497 + function Example(props) { 498 + useEffect(() => { 499 + setTimeout(() => { 500 + console.log(props.counter); 501 + }, 1000); 502 + }); 503 + // ... 504 + } 505 + ``` 506 + 507 + ```jsx{2,5} 508 + function Example(props) { 509 + const counter = props.counter; 510 + useEffect(() => { 511 + setTimeout(() => { 512 + console.log(counter); 513 + }, 1000); 514 + }); 515 + // ... 516 + } 517 + ``` 518 + 519 + **It doesn’t matter whether you read from props or state “early” inside of your component.** They’re not going to change! Inside the scope of a single render, props and state stay the same. (Destructuring props makes this more obvious.) 520 + 521 + Of course, sometimes you *want* to read the latest rather than captured value inside some callback defined in an effect. The easiest way to do it is by using refs, as described in the last section of [this article](https://overreacted.io/how-are-function-components-different-from-classes/). 522 + 523 + Be aware that when you want to read the *future* props or state from a function in a *past* render, you’re swimming against the tide. It’s not *wrong* (and in some cases necessary) but it might look less “clean” to break out of the paradigm. This is an intentional consequence because it helps highlight which code is fragile and depends on timing. In classes, it’s less obvious when this happens. 524 + 525 + Here’s a [version of our counter example](https://codesandbox.io/s/rm7z22qnlp) that replicates the class behavior: 526 + 527 + ```jsx{3,6-7,9-10} 528 + function Example() { 529 + const [count, setCount] = useState(0); 530 + const latestCount = useRef(count); 531 + 532 + useEffect(() => { 533 + // Set the mutable latest value 534 + latestCount.current = count; 535 + setTimeout(() => { 536 + // Read the mutable latest value 537 + console.log(`You clicked ${latestCount.current} times`); 538 + }, 3000); 539 + }); 540 + // ... 541 + ``` 542 + 543 + ![Screen recording of 5, 5, 5, 5, 5 logged in order](./timeout_counter_refs.gif) 544 + 545 + It might seem quirky to mutate something in React. However, this is exactly how React itself reassigns `this.state` in classes. Unlike with captured props and state, you don’t have any guarantees that reading `latestCount.current` would give you the same value in any particular callback. By definition, you can mutate it any time. This is why it’s not a default, and you have to opt into that. 546 + 547 + ## So What About Cleanup? 548 + 549 + As [the docs explain](https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup), some effects might have a cleanup phase. Essentially, its purpose is to “undo” an effect for cases like subscriptions. 550 + 551 + Consider this code: 552 + 553 + ```jsx 554 + useEffect(() => { 555 + ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange); 556 + return () => { 557 + ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange); 558 + }; 559 + }); 560 + ``` 561 + 562 + Say `props` is `{id: 10}` on the first render, and `{id: 20}` on the second render. You *might* think that something like this happens: 563 + 564 + * React cleans up the effect for `{id: 10}`. 565 + * React renders UI for `{id: 20}`. 566 + * React runs the effect for `{id: 20}`. 567 + 568 + (This is not quite the case.) 569 + 570 + With this mental model, you might think the cleanup “sees” the old props because it runs before we re-render, and then the new effect “sees” the new props because it runs after the re-render. That’s the mental model lifted directly from the class lifecycles, and **it’s not accurate here**. Let’s see why. 571 + 572 + React only runs the effects after [letting the browser paint](https://medium.com/@dan_abramov/this-benchmark-is-indeed-flawed-c3d6b5b6f97f). This makes your app faster as most effects don’t need to block screen updates. Effect cleanup is also delayed. **The previous effect is cleaned up _after_ the re-render with new props:** 573 + 574 + * **React renders UI for `{id: 20}`.** 575 + * The browser paints. We see the UI for `{id: 20}` on the screen. 576 + * **React cleans up the effect for `{id: 10}`.** 577 + * React runs the effect for `{id: 20}`. 578 + 579 + You might be wondering: but how can the cleanup of the previous effect still “see” the old `{id: 10}` props if it runs *after* the props change to `{id: 20}`? 580 + 581 + We’ve been here before... 🤔 582 + 583 + ![Deja vu (cat scene from the Matrix movie)](./deja_vu.gif) 584 + 585 + Quoting the previous section: 586 + 587 + >Every function inside the component render (including event handlers, effects, timeouts or API calls inside them) captures the props and state of the render call that defined it. 588 + 589 + Now the answer is clear! The effect cleanup doesn’t read the “latest” props, whatever that means. It reads props that belong to the render it’s defined in: 590 + 591 + ```jsx{8-11} 592 + // First render, props are {id: 10} 593 + function Example() { 594 + // ... 595 + useEffect( 596 + // Effect from first render 597 + () => { 598 + ChatAPI.subscribeToFriendStatus(10, handleStatusChange); 599 + // Cleanup for effect from first render 600 + return () => { 601 + ChatAPI.unsubscribeFromFriendStatus(10, handleStatusChange); 602 + }; 603 + } 604 + ); 605 + // ... 606 + } 607 + 608 + // Next render, props are {id: 20} 609 + function Example() { 610 + // ... 611 + useEffect( 612 + // Effect from second render 613 + () => { 614 + ChatAPI.subscribeToFriendStatus(20, handleStatusChange); 615 + // Cleanup for effect from second render 616 + return () => { 617 + ChatAPI.unsubscribeFromFriendStatus(20, handleStatusChange); 618 + }; 619 + } 620 + ); 621 + // ... 622 + } 623 + ``` 624 + 625 + Kingdoms will rise and turn into ashes, the Sun will shed its outer layers to be a white dwarf, and the last civilization will end. But nothing will make the props “seen” by the first render effect’s cleanup anything other than `{id: 10}`. 626 + 627 + That’s what allows React to deal with effects right after painting — and make your apps faster by default. The old props are still there if our code needs them. 628 + 629 + ## Synchronization, Not Lifecycle 630 + 631 + One of my favorite things about React is that it unifies describing the initial render result and the updates. This [reduces the entropy](https://overreacted.io/the-bug-o-notation/) of your program. 632 + 633 + Say my component looks like this: 634 + 635 + ```jsx 636 + function Greeting({ name }) { 637 + return ( 638 + <h1 className="Greeting"> 639 + Hello, {name} 640 + </h1> 641 + ); 642 + } 643 + ``` 644 + 645 + It doesn’t matter if I render `<Greeting name="Dan" />` and later `<Greeting name="Yuzhi" />`, or if I just render `<Greeting name="Yuzhi" />`. In the end, we will see “Hello, Yuzhi” in both cases. 646 + 647 + People say: “It’s all about the journey, not the destination”. With React, it’s the opposite. **It’s all about the destination, not the journey.** That’s the difference between `$.addClass` and `$.removeClass` calls in jQuery code (our “journey”) and specifying what the CSS class *should be* in React code (our “destination”). 648 + 649 + **React synchronizes the DOM according to our current props and state.** There is no distinction between a “mount” or an “update” when rendering. 650 + 651 + You should think of effects in a similar way. **`useEffect` lets you _synchronize_ things outside of the React tree according to our props and state.** 652 + 653 + ```jsx{2-4} 654 + function Greeting({ name }) { 655 + useEffect(() => { 656 + document.title = 'Hello, ' + name; 657 + }); 658 + return ( 659 + <h1 className="Greeting"> 660 + Hello, {name} 661 + </h1> 662 + ); 663 + } 664 + ``` 665 + 666 + This is subtly different from the familiar *mount/update/unmount* mental model. It is important really to internalize this. **If you’re trying to write an effect that behaves differently depending on whether the component renders for the first time or not, you’re swimming against the tide!** We’re failing at synchronizing if our result depends on the “journey” rather than the “destination”. 667 + 668 + It shouldn’t matter whether we rendered with props A, B, and C, or if we rendered with C immediately. While there may be some temporary differences (e.g. while we’re fetching data), eventually the end result should be the same. 669 + 670 + Still, of course running all effects on *every* render might not be efficient. (And in some cases, it would lead to infinite loops.) 671 + 672 + So how can we fix this? 673 + 674 + ## Teaching React to Diff Your Effects 675 + 676 + We’ve already learned that lesson with the DOM itself. Instead of touching it on every re-render, React only updates the parts of the DOM that actually change. 677 + 678 + When you’re updating 679 + 680 + ```jsx 681 + <h1 className="Greeting"> 682 + Hello, Dan 683 + </h1> 684 + ``` 685 + 686 + to 687 + 688 + ```jsx 689 + <h1 className="Greeting"> 690 + Hello, Yuzhi 691 + </h1> 692 + ``` 693 + 694 + React sees two objects: 695 + 696 + ```jsx 697 + const oldProps = {className: 'Greeting', children: 'Hello, Dan'}; 698 + const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'}; 699 + ``` 700 + 701 + It goes over each of their props and determine that `children` have changed and need a DOM update, but `className` did not. So it can just do: 702 + 703 + ```jsx 704 + domNode.innerText = 'Hello, Yuzhi'; 705 + // No need to touch domNode.className 706 + ``` 707 + 708 + **Could we do something like this with effects too? It would be nice to avoid re-running them when applying the effect is unnecessary.** 709 + 710 + For example, maybe our component re-renders because of a state change: 711 + 712 + ```jsx{11-13} 713 + function Greeting({ name }) { 714 + const [counter, setCounter] = useState(0); 715 + 716 + useEffect(() => { 717 + document.title = 'Hello, ' + name; 718 + }); 719 + 720 + return ( 721 + <h1 className="Greeting"> 722 + Hello, {name} 723 + <button onClick={() => setCounter(count + 1)}> 724 + Increment 725 + </button> 726 + </h1> 727 + ); 728 + } 729 + ``` 730 + 731 + But our effect doesn’t use the `counter` state. **Our effect synchronizes the `document.title` with the `name` prop, but the `name` prop is the same.** Re-assigning `document.title` on every counter change seems non-ideal. 732 + 733 + OK, so can React just... diff effects? 734 + 735 + 736 + ```jsx 737 + let oldEffect = () => { document.title = 'Hello, Dan'; }; 738 + let newEffect = () => { document.title = 'Hello, Dan'; }; 739 + // Can React see these functions do the same thing? 740 + ``` 741 + 742 + Not really. React can’t guess what the function does without calling it. (The source doesn’t really contain specific values, it just closes over the `name` prop.) 743 + 744 + This is why if you want to avoid re-running effects unnecessarily, you can provide a dependency array (also known as “deps”) argument to `useEffect`: 745 + 746 + ```jsx{3} 747 + useEffect(() => { 748 + document.title = 'Hello, ' + name; 749 + }, [name]); // Our deps 750 + ``` 751 + 752 + **It’s like if we told React: “Hey, I know you can’t see _inside_ this function, but I promise it only uses `name` and nothing else from the render scope.”** 753 + 754 + If each of these values is the same between the current and the previous time this effect ran, there’s nothing to synchronize so React can skip the effect: 755 + 756 + ```jsx 757 + const oldEffect = () => { document.title = 'Hello, Dan'; }; 758 + const oldDeps = ['Dan']; 759 + 760 + const newEffect = () => { document.title = 'Hello, Dan'; }; 761 + const newDeps = ['Dan']; 762 + 763 + // React can't peek inside of functions, but it can compare deps. 764 + // Since all deps are the same, it doesn’t need to run the new effect. 765 + ``` 766 + 767 + If even one of the values in the dependency array is different between renders, we know running the effect can’t be skipped. Synchronize all the things! 768 + 769 + ## Don’t Lie to React About Dependencies 770 + 771 + Lying to React about dependencies has bad consequences. Intuitively, this makes sense, but I’ve seen pretty much everyone who tries `useEffect` with a mental model from classes try to cheat the rules. (And I did that too at first!) 772 + 773 + ```jsx 774 + function SearchResults() { 775 + async function fetchData() { 776 + // ... 777 + } 778 + 779 + useEffect(() => { 780 + fetchData(); 781 + }, []); // Is this okay? Not always -- and there's a better way to write it. 782 + 783 + // ... 784 + } 785 + ``` 786 + 787 + *(The [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) explains what to do instead. We'll come back to this example [below](#moving-functions-inside-effects).)* 788 + 789 + “But I only want to run it on mount!”, you’ll say. For now, remember: if you specify deps, **_all_ values from inside your component that are used by the effect _must_ be there**. Including props, state, functions — anything in your component. 790 + 791 + Sometimes when you do that, it causes a problem. For example, maybe you see an infinite refetching loop, or a socket is recreated too often. **The solution to that problem is _not_ to remove a dependency.** We’ll look at the solutions soon. 792 + 793 + But before we jump to solutions, let’s understand the problem better. 794 + 795 + ## What Happens When Dependencies Lie 796 + 797 + If deps contain every value used by the effect, React knows when to re-run it: 798 + 799 + ```jsx{3} 800 + useEffect(() => { 801 + document.title = 'Hello, ' + name; 802 + }, [name]); 803 + ``` 804 + 805 + ![Diagram of effects replacing one another](./deps-compare-correct.gif) 806 + 807 + *(Dependencies are different, so we re-run the effect.)* 808 + 809 + But if we specified `[]` for this effect, the new effect function wouldn’t run: 810 + 811 + ```jsx{3} 812 + useEffect(() => { 813 + document.title = 'Hello, ' + name; 814 + }, []); // Wrong: name is missing in deps 815 + ``` 816 + 817 + ![Diagram of effects replacing one another](./deps-compare-wrong.gif) 818 + 819 + *(Dependencies are equal, so we skip the effect.)* 820 + 821 + In this case the problem might seem obvious. But the intuition can fool you in other cases where a class solution “jumps out” from your memory. 822 + 823 + For example, let’s say we’re writing a counter that increments every second. With a class, our intuition is: “Set up the interval once and destroy it once”. Here’s an [example](https://codesandbox.io/s/n5mjzjy9kl) of how we can do it. When we mentally translate this code to `useEffect`, we instinctively add `[]` to the deps. “I want it to run once”, right? 824 + 825 + ```jsx{9} 826 + function Counter() { 827 + const [count, setCount] = useState(0); 828 + 829 + useEffect(() => { 830 + const id = setInterval(() => { 831 + setCount(count + 1); 832 + }, 1000); 833 + return () => clearInterval(id); 834 + }, []); 835 + 836 + return <h1>{count}</h1>; 837 + } 838 + ``` 839 + 840 + However, this example [only *increments* once](https://codesandbox.io/s/91n5z8jo7r). *Oops.* 841 + 842 + If your mental model is “dependencies let me specify when I want to re-trigger the effect”, this example might give you an existential crisis. You *want* to trigger it once because it’s an interval — so why is it causing issues? 843 + 844 + However, this makes sense if you know that dependencies are our hint to React about *everything* that the effect uses from the render scope. It uses `count` but we lied that it doesn’t with `[]`. It’s only a matter of time before this bites us! 845 + 846 + In the first render, `count` is `0`. Therefore, `setCount(count + 1)` in the first render’s effect means `setCount(0 + 1)`. **Since we never re-run the effect because of `[]` deps, it will keep calling `setCount(0 + 1)` every second:** 847 + 848 + ```jsx{8,12,21-22} 849 + // First render, state is 0 850 + function Counter() { 851 + // ... 852 + useEffect( 853 + // Effect from first render 854 + () => { 855 + const id = setInterval(() => { 856 + setCount(0 + 1); // Always setCount(1) 857 + }, 1000); 858 + return () => clearInterval(id); 859 + }, 860 + [] // Never re-runs 861 + ); 862 + // ... 863 + } 864 + 865 + // Every next render, state is 1 866 + function Counter() { 867 + // ... 868 + useEffect( 869 + // This effect is always ignored because 870 + // we lied to React about empty deps. 871 + () => { 872 + const id = setInterval(() => { 873 + setCount(1 + 1); 874 + }, 1000); 875 + return () => clearInterval(id); 876 + }, 877 + [] 878 + ); 879 + // ... 880 + } 881 + ``` 882 + 883 + We lied to React by saying our effect doesn’t depend on a value from inside our component, when in fact it does! 884 + 885 + Our effect uses `count` — a value inside the component (but outside the effect): 886 + 887 + ```jsx{1,5} 888 + const count = // ... 889 + 890 + useEffect(() => { 891 + const id = setInterval(() => { 892 + setCount(count + 1); 893 + }, 1000); 894 + return () => clearInterval(id); 895 + }, []); 896 + ``` 897 + 898 + Therefore, specifying `[]` as a dependency will create a bug. React will compare the dependencies, and skip updating this effect: 899 + 900 + ![Diagram of stale interval closure](./interval-wrong.gif) 901 + 902 + *(Dependencies are equal, so we skip the effect.)* 903 + 904 + Issues like this are difficult to think about. Therefore, I encourage you to adopt it as a hard rule to always be honest about the effect dependencies, and specify them all. (We provide a [lint rule](https://github.com/facebook/react/issues/14920) if you want to enforce this on your team.) 905 + 906 + ## Two Ways to Be Honest About Dependencies 907 + 908 + There are two strategies to be honest about dependencies. You should generally start with the first one, and then apply the second one if needed. 909 + 910 + **The first strategy is to fix the dependency array to include _all_ the values inside the component that are used inside the effect.** Let’s include `count` as a dep: 911 + 912 + ```jsx{3,6} 913 + useEffect(() => { 914 + const id = setInterval(() => { 915 + setCount(count + 1); 916 + }, 1000); 917 + return () => clearInterval(id); 918 + }, [count]); 919 + ``` 920 + 921 + This makes the dependency array correct. It may not be *ideal* but that’s the first issue we needed to fix. Now a change to `count` will re-run the effect, with each next interval referencing `count` from its render in `setCount(count + 1)`: 922 + 923 + ```jsx{8,12,24,28} 924 + // First render, state is 0 925 + function Counter() { 926 + // ... 927 + useEffect( 928 + // Effect from first render 929 + () => { 930 + const id = setInterval(() => { 931 + setCount(0 + 1); // setCount(count + 1) 932 + }, 1000); 933 + return () => clearInterval(id); 934 + }, 935 + [0] // [count] 936 + ); 937 + // ... 938 + } 939 + 940 + // Second render, state is 1 941 + function Counter() { 942 + // ... 943 + useEffect( 944 + // Effect from second render 945 + () => { 946 + const id = setInterval(() => { 947 + setCount(1 + 1); // setCount(count + 1) 948 + }, 1000); 949 + return () => clearInterval(id); 950 + }, 951 + [1] // [count] 952 + ); 953 + // ... 954 + } 955 + ``` 956 + 957 + That would [fix the problem](https://codesandbox.io/s/0x0mnlyq8l) but our interval would be cleared and set again whenever the `count` changes. That may be undesirable: 958 + 959 + ![Diagram of interval that re-subscribes](./interval-rightish.gif) 960 + 961 + *(Dependencies are different, so we re-run the effect.)* 962 + 963 + --- 964 + 965 + **The second strategy is to change our effect code so that it wouldn’t *need* a value that changes more often than we want.** We don’t want to lie about the dependencies — we just want to change our effect to have *fewer* of them. 966 + 967 + Let’s look at a few common techniques for removing dependencies. 968 + 969 + --- 970 + 971 + ## Making Effects Self-Sufficient 972 + 973 + We want to get rid of the `count` dependency in our effect. 974 + 975 + ```jsx{3,6} 976 + useEffect(() => { 977 + const id = setInterval(() => { 978 + setCount(count + 1); 979 + }, 1000); 980 + return () => clearInterval(id); 981 + }, [count]); 982 + ``` 983 + 984 + To do this, we need to ask ourselves: **what are we using `count` for?** It seems like we only use it for the `setCount` call. In that case, we don’t actually need `count` in the scope at all. When we want to update state based on the previous state, we can use the [functional updater form](https://reactjs.org/docs/hooks-reference.html#functional-updates) of `setState`: 985 + 986 + ```jsx{3} 987 + useEffect(() => { 988 + const id = setInterval(() => { 989 + setCount(c => c + 1); 990 + }, 1000); 991 + return () => clearInterval(id); 992 + }, []); 993 + ``` 994 + 995 + I like to think of these cases as “false dependencies”. Yes, `count` was a necessary dependency because we wrote `setCount(count + 1)` inside the effect. However, we only truly needed `count` to transform it into `count + 1` and “send it back” to React. But React *already knows* the current `count`. **All we needed to tell React is to increment the state — whatever it is right now.** 996 + 997 + That’s exactly what `setCount(c => c + 1)` does. You can think of it as “sending an instruction” to React about how the state should change. This “updater form” also helps in other cases, like when you [batch multiple updates](/react-as-a-ui-runtime/#batching). 998 + 999 + **Note that we actually _did the work_ to remove the dependency. We didn’t cheat. Our effect doesn’t read the `counter` value from the render scope anymore:** 1000 + 1001 + ![Diagram of interval that works](./interval-right.gif) 1002 + 1003 + *(Dependencies are equal, so we skip the effect.)* 1004 + 1005 + You can try it [here](https://codesandbox.io/s/q3181xz1pj). 1006 + 1007 + Even though this effect only runs once, the interval callback that belongs to the first render is perfectly capable of sending the `c => c + 1` update instruction every time the interval fires. It doesn’t need to know the current `counter` state anymore. React already knows it. 1008 + 1009 + ## Functional Updates and Google Docs 1010 + 1011 + Remember how we talked about synchronization being the mental model for effects? An interesting aspect of synchronization is that you often want to keep the “messages” between the systems untangled from their state. For example, editing a document in Google Docs doesn’t actually send the *whole* page to the server. That would be very inefficient. Instead, it sends a representation of what the user tried to do. 1012 + 1013 + While our use case is different, a similar philosophy applies to effects. **It helps to send only the minimal necessary information from inside the effects into a component.** The updater form like `setCount(c => c + 1)` conveys strictly less information than `setCount(count + 1)` because it isn’t “tainted” by the current count. It only expresses the action (“incrementing”). Thinking in React involves [finding the minimal state](https://reactjs.org/docs/thinking-in-react.html#step-3-identify-the-minimal-but-complete-representation-of-ui-state). This is the same principle, but for updates. 1014 + 1015 + Encoding the *intent* (rather than the result) is similar to how Google Docs [solves](https://medium.com/@srijancse/how-real-time-collaborative-editing-work-operational-transformation-ac4902d75682) collaborative editing. While this is stretching the analogy, functional updates serve a similar role in React. They ensure updates from multiple sources (event handlers, effect subscriptions, etc) can be correctly applied in a batch and in a predictable way. 1016 + 1017 + **However, even `setCount(c => c + 1)` isn’t that great.** It looks a bit weird and it’s very limited in what it can do. For example, if we had two state variables whose values depend on each other, or if we needed to calculate the next state based on a prop, it wouldn’t help us. Luckily, `setCount(c => c + 1)` has a more powerful sister pattern. Its name is `useReducer`. 1018 + 1019 + ## Decoupling Updates from Actions 1020 + 1021 + Let’s modify the previous example to have two state variables: `count` and `step`. Our interval will increment the count by the value of the `step` input: 1022 + 1023 + ```jsx{7,10} 1024 + function Counter() { 1025 + const [count, setCount] = useState(0); 1026 + const [step, setStep] = useState(1); 1027 + 1028 + useEffect(() => { 1029 + const id = setInterval(() => { 1030 + setCount(c => c + step); 1031 + }, 1000); 1032 + return () => clearInterval(id); 1033 + }, [step]); 1034 + 1035 + return ( 1036 + <> 1037 + <h1>{count}</h1> 1038 + <input value={step} onChange={e => setStep(Number(e.target.value))} /> 1039 + </> 1040 + ); 1041 + } 1042 + ``` 1043 + 1044 + (Here’s a [demo](https://codesandbox.io/s/zxn70rnkx).) 1045 + 1046 + Note that **we’re not cheating**. Since I started using `step` inside the effect, I added it to the dependencies. And that’s why the code runs correctly. 1047 + 1048 + The current behavior in this example is that changing the `step` restarts the interval — because it’s one of the dependencies. And in many cases, that is exactly what you want! There’s nothing wrong with tearing down an effect and setting it up anew, and we shouldn’t avoid that unless we have a good reason. 1049 + 1050 + However, let’s say we want the interval clock to not reset on changes to the `step`. How do we remove the `step` dependency from our effect? 1051 + 1052 + **When setting a state variable depends on the current value of another state variable, you might want to try replacing them both with `useReducer`.** 1053 + 1054 + When you find yourself writing `setSomething(something => ...)`, it’s a good time to consider using a reducer instead. A reducer lets you **decouple expressing the “actions” that happened in your component from how the state updates in response to them**. 1055 + 1056 + Let’s trade the `step` dependency for a `dispatch` dependency in our effect: 1057 + 1058 + ```jsx{1,6,9} 1059 + const [state, dispatch] = useReducer(reducer, initialState); 1060 + const { count, step } = state; 1061 + 1062 + useEffect(() => { 1063 + const id = setInterval(() => { 1064 + dispatch({ type: 'tick' }); // Instead of setCount(c => c + step); 1065 + }, 1000); 1066 + return () => clearInterval(id); 1067 + }, [dispatch]); 1068 + ``` 1069 + 1070 + (See the [demo](https://codesandbox.io/s/xzr480k0np).) 1071 + 1072 + You might ask me: “How is this any better?” The answer is that **React guarantees the `dispatch` function to be constant throughout the component lifetime. So the example above doesn’t ever need to resubscribe the interval.** 1073 + 1074 + We solved our problem! 1075 + 1076 + *(You may omit `dispatch`, `setState`, and `useRef` container values from the deps because React guarantees them to be static. But it also doesn’t hurt to specify them.)* 1077 + 1078 + Instead of reading the state *inside* an effect, it dispatches an *action* that encodes the information about *what happened*. This allows our effect to stay decoupled from the `step` state. Our effect doesn’t care *how* we update the state, it just tells us about *what happened*. And the reducer centralizes the update logic: 1079 + 1080 + ```jsx{8,9} 1081 + const initialState = { 1082 + count: 0, 1083 + step: 1, 1084 + }; 1085 + 1086 + function reducer(state, action) { 1087 + const { count, step } = state; 1088 + if (action.type === 'tick') { 1089 + return { count: count + step, step }; 1090 + } else if (action.type === 'step') { 1091 + return { count, step: action.step }; 1092 + } else { 1093 + throw new Error(); 1094 + } 1095 + } 1096 + ``` 1097 + 1098 + (Here’s a [demo](https://codesandbox.io/s/xzr480k0np) if you missed it earlier). 1099 + 1100 + ## Why useReducer Is the Cheat Mode of Hooks 1101 + 1102 + We’ve seen how to remove dependencies when an effect needs to set state based on previous state, or on another state variable. **But what if we need _props_ to calculate the next state?** For example, maybe our API is `<Counter step={1} />`. Surely, in this case we can’t avoid specifying `props.step` as a dependency? 1103 + 1104 + In fact, we can! We can put *the reducer itself* inside our component to read props: 1105 + 1106 + ```jsx{1,6} 1107 + function Counter({ step }) { 1108 + const [count, dispatch] = useReducer(reducer, 0); 1109 + 1110 + function reducer(state, action) { 1111 + if (action.type === 'tick') { 1112 + return state + step; 1113 + } else { 1114 + throw new Error(); 1115 + } 1116 + } 1117 + 1118 + useEffect(() => { 1119 + const id = setInterval(() => { 1120 + dispatch({ type: 'tick' }); 1121 + }, 1000); 1122 + return () => clearInterval(id); 1123 + }, [dispatch]); 1124 + 1125 + return <h1>{count}</h1>; 1126 + } 1127 + ``` 1128 + 1129 + This pattern disables a few optimizations so try not to use it everywhere, but you can totally access props from a reducer if you need to. (Here’s a [demo](https://codesandbox.io/s/7ypm405o8q).) 1130 + 1131 + **Even in that case, `dispatch` identity is still guaranteed to be stable between re-renders.** So you may omit it from the effect deps if you want. It’s not going to cause the effect to re-run. 1132 + 1133 + You may be wondering: how can this possibly work? How can the reducer “know” props when called from inside an effect that belongs to another render? The answer is that when you `dispatch`, React just remembers the action — but it will *call* your reducer during the next render. At that point the fresh props will be in scope, and you won’t be inside an effect. 1134 + 1135 + **This is why I like to think of `useReducer` as the “cheat mode” of Hooks. It lets me decouple the update logic from describing what happened. This, in turn, helps me remove unnecessary dependencies from my effects and avoid re-running them more often than necessary.** 1136 + 1137 + ## Moving Functions Inside Effects 1138 + 1139 + A common mistake is to think functions shouldn’t be dependencies. For example, this seems like it could work: 1140 + 1141 + ```jsx{13} 1142 + function SearchResults() { 1143 + const [data, setData] = useState({ hits: [] }); 1144 + 1145 + async function fetchData() { 1146 + const result = await axios( 1147 + 'https://hn.algolia.com/api/v1/search?query=react', 1148 + ); 1149 + setData(result.data); 1150 + } 1151 + 1152 + useEffect(() => { 1153 + fetchData(); 1154 + }, []); // Is this okay? 1155 + 1156 + // ... 1157 + ``` 1158 + 1159 + *([This example](https://codesandbox.io/s/8j4ykjyv0) is adapted from a great article by Robin Wieruch — [check it out](https://www.robinwieruch.de/react-hooks-fetch-data/)!)* 1160 + 1161 + And to be clear, this code *does* work. **But the problem with simply omitting local functions is that it gets pretty hard to tell whether we’re handling all cases as the component grows!** 1162 + 1163 + Imagine our code was split like this and each function was five times larger: 1164 + 1165 + ```jsx 1166 + function SearchResults() { 1167 + // Imagine this function is long 1168 + function getFetchUrl() { 1169 + return 'https://hn.algolia.com/api/v1/search?query=react'; 1170 + } 1171 + 1172 + // Imagine this function is also long 1173 + async function fetchData() { 1174 + const result = await axios(getFetchUrl()); 1175 + setData(result.data); 1176 + } 1177 + 1178 + useEffect(() => { 1179 + fetchData(); 1180 + }, []); 1181 + 1182 + // ... 1183 + } 1184 + ``` 1185 + 1186 + 1187 + Now let’s say we later use some state or prop in one of these functions: 1188 + 1189 + ```jsx{6} 1190 + function SearchResults() { 1191 + const [query, setQuery] = useState('react'); 1192 + 1193 + // Imagine this function is also long 1194 + function getFetchUrl() { 1195 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1196 + } 1197 + 1198 + // Imagine this function is also long 1199 + async function fetchData() { 1200 + const result = await axios(getFetchUrl()); 1201 + setData(result.data); 1202 + } 1203 + 1204 + useEffect(() => { 1205 + fetchData(); 1206 + }, []); 1207 + 1208 + // ... 1209 + } 1210 + ``` 1211 + 1212 + If we forget to update the deps of any effects that call these functions (possibly, through other functions!), our effects will fail to synchronize changes from our props and state. This doesn’t sound great. 1213 + 1214 + Luckily, there is an easy solution to this problem. **If you only use some functions *inside* an effect, move them directly *into* that effect:** 1215 + 1216 + ```jsx{4-12} 1217 + function SearchResults() { 1218 + // ... 1219 + useEffect(() => { 1220 + // We moved these functions inside! 1221 + function getFetchUrl() { 1222 + return 'https://hn.algolia.com/api/v1/search?query=react'; 1223 + } 1224 + 1225 + async function fetchData() { 1226 + const result = await axios(getFetchUrl()); 1227 + setData(result.data); 1228 + } 1229 + 1230 + fetchData(); 1231 + }, []); // ✅ Deps are OK 1232 + // ... 1233 + } 1234 + ``` 1235 + 1236 + ([Here’s a demo](https://codesandbox.io/s/04kp3jwwql).) 1237 + 1238 + So what is the benefit? We no longer have to think about the “transitive dependencies”. Our dependencies array isn’t lying anymore: **we truly _aren’t_ using anything from the outer scope of the component in our effect**. 1239 + 1240 + If we later edit `getFetchUrl` to use the `query` state, we’re much more likely to notice that we’re editing it *inside* an effect — and therefore, we need to add `query` to the effect dependencies: 1241 + 1242 + ```jsx{6,15} 1243 + function SearchResults() { 1244 + const [query, setQuery] = useState('react'); 1245 + 1246 + useEffect(() => { 1247 + function getFetchUrl() { 1248 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1249 + } 1250 + 1251 + async function fetchData() { 1252 + const result = await axios(getFetchUrl()); 1253 + setData(result.data); 1254 + } 1255 + 1256 + fetchData(); 1257 + }, [query]); // ✅ Deps are OK 1258 + 1259 + // ... 1260 + } 1261 + ``` 1262 + 1263 + (Here’s a [demo](https://codesandbox.io/s/pwm32zx7z7).) 1264 + 1265 + By adding this dependency, we’re not just “appeasing React”. It *makes sense* to refetch the data when the query changes. **The design of `useEffect` forces you to notice the change in our data flow and choose how our effects should synchronize it — instead of ignoring it until our product users hit a bug.** 1266 + 1267 + Thanks to the `exhaustive-deps` lint rule from the `eslint-plugin-react-hooks` plugin, you can [analyze the effects as you type in your editor](https://github.com/facebook/react/issues/14920) and receive suggestions about which dependencies are missing. In other words, a machine can tell you which data flow changes aren’t handled correctly by a component. 1268 + 1269 + ![Lint rule gif](./exhaustive-deps.gif) 1270 + 1271 + Pretty sweet. 1272 + 1273 + ## But I Can’t Put This Function Inside an Effect 1274 + 1275 + Sometimes you might not want to move a function *inside* an effect. For example, several effects in the same component may call the same function, and you don’t want to copy and paste its logic. Or maybe it’s a prop. 1276 + 1277 + Should you skip a function like this in the effect dependencies? I think not. Again, **effects shouldn’t lie about their dependencies.** There are usually better solutions. A common misconception is that “a function would never change”. But as we learned throughout this article, this couldn’t be further from truth. Indeed, a function defined inside a component changes on every render! 1278 + 1279 + **That by itself presents a problem.** Say two effects call `getFetchUrl`: 1280 + 1281 + ```jsx 1282 + function SearchResults() { 1283 + function getFetchUrl(query) { 1284 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1285 + } 1286 + 1287 + useEffect(() => { 1288 + const url = getFetchUrl('react'); 1289 + // ... Fetch data and do something ... 1290 + }, []); // 🔴 Missing dep: getFetchUrl 1291 + 1292 + useEffect(() => { 1293 + const url = getFetchUrl('redux'); 1294 + // ... Fetch data and do something ... 1295 + }, []); // 🔴 Missing dep: getFetchUrl 1296 + 1297 + // ... 1298 + } 1299 + ``` 1300 + 1301 + In that case you might not want to move `getFetchUrl` inside either of the effects since you wouldn’t be able to share the logic. 1302 + 1303 + On the other hand, if you’re “honest” about the effect dependencies, you may run into a problem. Since both our effects depend on `getFetchUrl` **(which is different on every render)**, our dependency arrays are useless: 1304 + 1305 + ```jsx{2-5} 1306 + function SearchResults() { 1307 + // 🔴 Re-triggers all effects on every render 1308 + function getFetchUrl(query) { 1309 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1310 + } 1311 + 1312 + useEffect(() => { 1313 + const url = getFetchUrl('react'); 1314 + // ... Fetch data and do something ... 1315 + }, [getFetchUrl]); // 🚧 Deps are correct but they change too often 1316 + 1317 + useEffect(() => { 1318 + const url = getFetchUrl('redux'); 1319 + // ... Fetch data and do something ... 1320 + }, [getFetchUrl]); // 🚧 Deps are correct but they change too often 1321 + 1322 + // ... 1323 + } 1324 + ``` 1325 + 1326 + A tempting solution to this is to just skip the `getFetchUrl` function in the deps list. However, I don’t think it’s a good solution. This makes it difficult to notice when we *are* adding a change to the data flow that *needs* to be handled by an effect. This leads to bugs like the “never updating interval” we saw earlier. 1327 + 1328 + Instead, there are two other solutions that are simpler. 1329 + 1330 + **First of all, if a function doesn’t use anything from the component scope, you can hoist it outside the component and then freely use it inside your effects:** 1331 + 1332 + ```jsx{1-4} 1333 + // ✅ Not affected by the data flow 1334 + function getFetchUrl(query) { 1335 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1336 + } 1337 + 1338 + function SearchResults() { 1339 + useEffect(() => { 1340 + const url = getFetchUrl('react'); 1341 + // ... Fetch data and do something ... 1342 + }, []); // ✅ Deps are OK 1343 + 1344 + useEffect(() => { 1345 + const url = getFetchUrl('redux'); 1346 + // ... Fetch data and do something ... 1347 + }, []); // ✅ Deps are OK 1348 + 1349 + // ... 1350 + } 1351 + ``` 1352 + 1353 + There’s no need to specify it in deps because it’s not in the render scope and can’t be affected by the data flow. It can’t accidentally depend on props or state. 1354 + 1355 + Alternatively, you can wrap it into the [`useCallback` Hook](https://reactjs.org/docs/hooks-reference.html#usecallback): 1356 + 1357 + 1358 + ```jsx{2-5} 1359 + function SearchResults() { 1360 + // ✅ Preserves identity when its own deps are the same 1361 + const getFetchUrl = useCallback((query) => { 1362 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1363 + }, []); // ✅ Callback deps are OK 1364 + 1365 + useEffect(() => { 1366 + const url = getFetchUrl('react'); 1367 + // ... Fetch data and do something ... 1368 + }, [getFetchUrl]); // ✅ Effect deps are OK 1369 + 1370 + useEffect(() => { 1371 + const url = getFetchUrl('redux'); 1372 + // ... Fetch data and do something ... 1373 + }, [getFetchUrl]); // ✅ Effect deps are OK 1374 + 1375 + // ... 1376 + } 1377 + ``` 1378 + 1379 + `useCallback` is essentially like adding another layer of dependency checks. It’s solving the problem on the other end — **rather than avoid a function dependency, we make the function itself only change when necessary**. 1380 + 1381 + Let's see why this approach is useful. Previously, our example showed two search results (for `'react'` and `'redux'` search queries). But let's say we want to add an input so that you can search for an arbitrary `query`. So instead of taking `query` as an argument, `getFetchUrl` will now read it from local state. 1382 + 1383 + We'll immediately see that it's missing a `query` dependency: 1384 + 1385 + ```jsx{5} 1386 + function SearchResults() { 1387 + const [query, setQuery] = useState('react'); 1388 + const getFetchUrl = useCallback(() => { // No query argument 1389 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1390 + }, []); // 🔴 Missing dep: query 1391 + // ... 1392 + } 1393 + ``` 1394 + 1395 + If I fix my `useCallback` deps to include `query`, any effect with `getFetchUrl` in deps will re-run whenever the `query` changes: 1396 + 1397 + ```jsx{4-7} 1398 + function SearchResults() { 1399 + const [query, setQuery] = useState('react'); 1400 + 1401 + // ✅ Preserves identity until query changes 1402 + const getFetchUrl = useCallback(() => { 1403 + return 'https://hn.algolia.com/api/v1/search?query=' + query; 1404 + }, [query]); // ✅ Callback deps are OK 1405 + 1406 + useEffect(() => { 1407 + const url = getFetchUrl(); 1408 + // ... Fetch data and do something ... 1409 + }, [getFetchUrl]); // ✅ Effect deps are OK 1410 + 1411 + // ... 1412 + } 1413 + ``` 1414 + 1415 + Thanks to `useCallback`, if `query` is the same, `getFetchUrl` also stays the same, and our effect doesn't re-run. But if `query` changes, `getFetchUrl` will also change, and we will re-fetch the data. It's a lot like when you change some cell in an Excel spreadsheet, and the other cells using it recalculate automatically. 1416 + 1417 + This is just a consequence of embracing the data flow and the synchronization mindset. **The same solution works for function props passed from parents:** 1418 + 1419 + ```jsx{4-8} 1420 + function Parent() { 1421 + const [query, setQuery] = useState('react'); 1422 + 1423 + // ✅ Preserves identity until query changes 1424 + const fetchData = useCallback(() => { 1425 + const url = 'https://hn.algolia.com/api/v1/search?query=' + query; 1426 + // ... Fetch data and return it ... 1427 + }, [query]); // ✅ Callback deps are OK 1428 + 1429 + return <Child fetchData={fetchData} /> 1430 + } 1431 + 1432 + function Child({ fetchData }) { 1433 + let [data, setData] = useState(null); 1434 + 1435 + useEffect(() => { 1436 + fetchData().then(setData); 1437 + }, [fetchData]); // ✅ Effect deps are OK 1438 + 1439 + // ... 1440 + } 1441 + ``` 1442 + 1443 + Since `fetchData` only changes inside `Parent` when its `query` state changes, our `Child` won’t refetch the data until it’s actually necessary for the app. 1444 + 1445 + ## Are Functions Part of the Data Flow? 1446 + 1447 + Interestingly, this pattern is broken with classes in a way that really shows the difference between the effect and lifecycle paradigms. Consider this translation: 1448 + 1449 + ```jsx{5-8,18-20} 1450 + class Parent extends Component { 1451 + state = { 1452 + query: 'react' 1453 + }; 1454 + fetchData = () => { 1455 + const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query; 1456 + // ... Fetch data and do something ... 1457 + }; 1458 + render() { 1459 + return <Child fetchData={this.fetchData} />; 1460 + } 1461 + } 1462 + 1463 + class Child extends Component { 1464 + state = { 1465 + data: null 1466 + }; 1467 + componentDidMount() { 1468 + this.props.fetchData(); 1469 + } 1470 + render() { 1471 + // ... 1472 + } 1473 + } 1474 + ``` 1475 + 1476 + You might be thinking: “Come on Dan, we all know that `useEffect` is like `componentDidMount` and `componentDidUpdate` combined, you can’t keep beating that drum!” **Yet this doesn’t work even with `componentDidUpdate`:** 1477 + 1478 + ```jsx{8-13} 1479 + class Child extends Component { 1480 + state = { 1481 + data: null 1482 + }; 1483 + componentDidMount() { 1484 + this.props.fetchData(); 1485 + } 1486 + componentDidUpdate(prevProps) { 1487 + // 🔴 This condition will never be true 1488 + if (this.props.fetchData !== prevProps.fetchData) { 1489 + this.props.fetchData(); 1490 + } 1491 + } 1492 + render() { 1493 + // ... 1494 + } 1495 + } 1496 + ``` 1497 + 1498 + Of course, `fetchData` is a class method! (Or, rather, a class property — but that doesn’t change anything.) It’s not going to be different because of a state change. So `this.props.fetchData` will stay equal to `prevProps.fetchData` and we’ll never refetch. Let’s just remove this condition then? 1499 + 1500 + ```jsx 1501 + componentDidUpdate(prevProps) { 1502 + this.props.fetchData(); 1503 + } 1504 + ``` 1505 + 1506 + Oh wait, this fetches on *every* re-render. (Adding an animation above in the tree is a fun way to discover it.) Maybe let’s bind it to a particular query? 1507 + 1508 + ```jsx 1509 + render() { 1510 + return <Child fetchData={this.fetchData.bind(this, this.state.query)} />; 1511 + } 1512 + ``` 1513 + 1514 + But then `this.props.fetchData !== prevProps.fetchData` is *always* `true`, even if the `query` didn’t change! So we’ll *always* refetch. 1515 + 1516 + The only real solution to this conundrum with classes is to bite the bullet and pass the `query` itself into the `Child` component. The `Child` doesn’t actually end up *using* the `query`, but it can trigger a refetch when it changes: 1517 + 1518 + ```jsx{10,22-24} 1519 + class Parent extends Component { 1520 + state = { 1521 + query: 'react' 1522 + }; 1523 + fetchData = () => { 1524 + const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query; 1525 + // ... Fetch data and do something ... 1526 + }; 1527 + render() { 1528 + return <Child fetchData={this.fetchData} query={this.state.query} />; 1529 + } 1530 + } 1531 + 1532 + class Child extends Component { 1533 + state = { 1534 + data: null 1535 + }; 1536 + componentDidMount() { 1537 + this.props.fetchData(); 1538 + } 1539 + componentDidUpdate(prevProps) { 1540 + if (this.props.query !== prevProps.query) { 1541 + this.props.fetchData(); 1542 + } 1543 + } 1544 + render() { 1545 + // ... 1546 + } 1547 + } 1548 + ``` 1549 + 1550 + Over the years of working with classes with React, I’ve gotten so used to passing unnecessary props down and breaking encapsulation of parent components that I only realized a week ago why we had to do it. 1551 + 1552 + **With classes, function props by themselves aren’t truly a part of the data flow.** Methods close over the mutable `this` variable so we can’t rely on their identity to mean anything. Therefore, even when we only want a function, we have to pass a bunch of other data around in order to be able to “diff” it. We can’t know whether `this.props.fetchData` passed from the parent depends on some state or not, and whether that state has just changed. 1553 + 1554 + **With `useCallback`, functions can fully participate in the data flow.** We can say that if the function inputs changed, the function itself has changed, but if not, it stayed the same. Thanks to the granularity provided by `useCallback`, changes to props like `props.fetchData` can propagate down automatically. 1555 + 1556 + Similarly, [`useMemo`](https://reactjs.org/docs/hooks-reference.html#usememo) lets us do the same for complex objects: 1557 + 1558 + ```jsx 1559 + function ColorPicker() { 1560 + // Doesn't break Child's shallow equality prop check 1561 + // unless the color actually changes. 1562 + const [color, setColor] = useState('pink'); 1563 + const style = useMemo(() => ({ color }), [color]); 1564 + return <Child style={style} />; 1565 + } 1566 + ``` 1567 + 1568 + **I want to emphasize that putting `useCallback` everywhere is pretty clunky.** It’s a nice escape hatch and it’s useful when a function is both passed down *and* called from inside an effect in some children. Or if you’re trying to prevent breaking memoization of a child component. But Hooks lend themselves better to [avoiding passing callbacks down](https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down) altogether. 1569 + 1570 + In the above examples, I’d much prefer if `fetchData` was either inside my effect (which itself could be extracted to a custom Hook) or a top-level import. I want to keep the effects simple, and callbacks in them don’t help that. (“What if some `props.onComplete` callback changes while the request was in flight?”) You can [simulate the class behavior](#swimming-against-the-tide) but that doesn’t solve race conditions. 1571 + 1572 + ## Speaking of Race Conditions 1573 + 1574 + A classic data fetching example with classes might look like this: 1575 + 1576 + ```jsx 1577 + class Article extends Component { 1578 + state = { 1579 + article: null 1580 + }; 1581 + componentDidMount() { 1582 + this.fetchData(this.props.id); 1583 + } 1584 + async fetchData(id) { 1585 + const article = await API.fetchArticle(id); 1586 + this.setState({ article }); 1587 + } 1588 + // ... 1589 + } 1590 + ``` 1591 + 1592 + As you probably know, this code is buggy. It doesn’t handle updates. So the second classic example you could find online is something like this: 1593 + 1594 + ```jsx{8-12} 1595 + class Article extends Component { 1596 + state = { 1597 + article: null 1598 + }; 1599 + componentDidMount() { 1600 + this.fetchData(this.props.id); 1601 + } 1602 + componentDidUpdate(prevProps) { 1603 + if (prevProps.id !== this.props.id) { 1604 + this.fetchData(this.props.id); 1605 + } 1606 + } 1607 + async fetchData(id) { 1608 + const article = await API.fetchArticle(id); 1609 + this.setState({ article }); 1610 + } 1611 + // ... 1612 + } 1613 + ``` 1614 + 1615 + This is definitely better! But it’s still buggy. The reason it’s buggy is that the request may come out of order. So if I’m fetching `{id: 10}`, switch to `{id: 20}`, but the `{id: 20}` request comes first, the request that started earlier but finished later would incorrectly overwrite my state. 1616 + 1617 + This is called a race condition, and it’s typical in code that mixes `async` / `await` (which assumes something waits for the result) with top-down data flow (props or state can change while we’re in the middle of an async function). 1618 + 1619 + Effects don’t magically solve this problem, although they’ll warn you if you try to pass an `async` function to the effect directly. (We’ll need to improve that warning to better explain the problems you might run into.) 1620 + 1621 + If the async approach you use supports cancellation, that’s great! You can cancel the async request right in your cleanup function. 1622 + 1623 + Alternatively, the easiest stopgap approach is to track it with a boolean: 1624 + 1625 + ```jsx{5,9,16-18} 1626 + function Article({ id }) { 1627 + const [article, setArticle] = useState(null); 1628 + 1629 + useEffect(() => { 1630 + let didCancel = false; 1631 + 1632 + async function fetchData() { 1633 + const article = await API.fetchArticle(id); 1634 + if (!didCancel) { 1635 + setArticle(article); 1636 + } 1637 + } 1638 + 1639 + fetchData(); 1640 + 1641 + return () => { 1642 + didCancel = true; 1643 + }; 1644 + }, [id]); 1645 + 1646 + // ... 1647 + } 1648 + ``` 1649 + 1650 + [This article](https://www.robinwieruch.de/react-hooks-fetch-data/) goes into more detail about how you can handle errors and loading states, as well as extract that logic into a custom Hook. I recommend you to check it out if you’re interested to learn more about data fetching with Hooks. 1651 + 1652 + ## Raising the Bar 1653 + 1654 + With the class lifecycle mindset, side effects behave differently from the render output. Rendering the UI is driven by props and state, and is guaranteed to be consistent with them, but side effects are not. This is a common source of bugs. 1655 + 1656 + With the mindset of `useEffect`, things are synchronized by default. Side effects become a part of the React data flow. For every `useEffect` call, once you get it right, your component handles edge cases much better. 1657 + 1658 + However, the upfront cost of getting it right is higher. This can be annoying. Writing synchronization code that handles edge cases well is inherently more difficult than firing one-off side effects that aren’t consistent with rendering. 1659 + 1660 + This could be worrying if `useEffect` was meant to be *the* tool you use most of the time. However, it’s a low-level building block. It’s an early time for Hooks so everybody uses low-level ones all the time, especially in tutorials. But in practice, it’s likely the community will start moving to higher-level Hooks as good APIs gain momentum. 1661 + 1662 + I’m seeing different apps create their own Hooks like `useFetch` that encapsulates some of their app’s auth logic or `useTheme` which uses theme context. Once you have a toolbox of those, you don’t reach for `useEffect` *that* often. But the resilience it brings benefits every Hook built on top of it. 1663 + 1664 + So far, `useEffect` is most commonly used for data fetching. But data fetching isn’t exactly a synchronization problem. This is especially obvious because our deps are often `[]`. What are we even synchronizing? 1665 + 1666 + In the longer term, [Suspense for Data Fetching](https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#react-16x-mid-2019-the-one-with-suspense-for-data-fetching) will allow third-party libraries to have a first-class way to tell React to suspend rendering until something async (anything: code, data, images) is ready. 1667 + 1668 + As Suspense gradually covers more data fetching use cases, I anticipate that `useEffect` will fade into background as a power user tool for cases when you actually want to synchronize props and state to some side effect. Unlike data fetching, it handles this case naturally because it was designed for it. But until then, custom Hooks like [shown here](https://www.robinwieruch.de/react-hooks-fetch-data/) are a good way to reuse data fetching logic. 1669 + 1670 + ## In Closing 1671 + 1672 + Now that you know pretty much everything I know about using effects, check out the [TLDR](#tldr) in the beginning. Does it make sense? Did I miss something? (I haven’t run out of paper yet!) 1673 + 1674 + I’d love to hear from you on Twitter! Thanks for reading.
public/a-complete-guide-to-useeffect/interval-right.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/interval-rightish.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/interval-wrong.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/timeout_counter.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/timeout_counter_class.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/timeout_counter_refs.gif

This is a binary file and will not be displayed.

public/a-complete-guide-to-useeffect/yoda.jpg

This is a binary file and will not be displayed.

public/algebraic-effects-for-the-rest-of-us/effects.jpg

This is a binary file and will not be displayed.

+425
public/algebraic-effects-for-the-rest-of-us/index.md
··· 1 + --- 2 + title: Algebraic Effects for the Rest of Us 3 + date: '2019-07-21' 4 + spoiler: They’re not burritos. 5 + cta: 'react' 6 + --- 7 + 8 + Have you heard about *algebraic effects*? 9 + 10 + My first attempts to figure out what they are or why I should care about them were unsuccessful. I found a [few](https://www.eff-lang.org/handlers-tutorial.pdf) [pdfs](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/algeff-tr-2016-v2.pdf) but they only confused me more. (There’s something about academic pdfs that makes me sleepy.) 11 + 12 + But my colleague Sebastian [kept](https://mobile.twitter.com/sebmarkbage/status/763792452289343490) [referring](https://mobile.twitter.com/sebmarkbage/status/776883429400915968) [to](https://mobile.twitter.com/sebmarkbage/status/776840575207116800) [them](https://mobile.twitter.com/sebmarkbage/status/969279885276454912) as a mental model for some things we do inside of React. (Sebastian works on the React team and came up with quite a few ideas, including Hooks and Suspense.) At some point, it became a running joke on the React team, with many of our conversations ending with: 13 + 14 + !["Algebraic Effects" caption on the "Ancient Aliens" guy meme](./effects.jpg) 15 + 16 + It turned out that algebraic effects are a cool concept and not as scary as I thought from those pdfs. **If you’re just using React, you don’t need to know anything about them — but if you’re feeling curious, like I was, read on.** 17 + 18 + *(Disclaimer: I’m not a programming language researcher, and might have messed something up in my explanation. I am not an authority on this topic so let me know!)* 19 + 20 + ### Not Production Ready Yet 21 + 22 + *Algebraic Effects* are a research programming language feature. This means that **unlike `if`, functions, or even `async / await`, you probably can’t really use them in production yet.** They are only supported by a [few](https://www.eff-lang.org/) [languages](https://www.microsoft.com/en-us/research/project/koka/) that were created specifically to explore that idea. There is progress on productionizing them in OCaml which is... still [ongoing](https://github.com/ocaml-multicore/ocaml-multicore/wiki). In other words, [Can’t Touch This](https://www.youtube.com/watch?v=otCpCn0l4Wo). 23 + 24 + >Edit: a few people mentioned that LISP languages [do offer something similar](#learn-more), so you can use it in production if you write LISP. 25 + 26 + ### So Why Should I Care? 27 + 28 + Imagine that you’re writing code with `goto`, and somebody shows you `if` and `for` statements. Or maybe you’re deep in the callback hell, and somebody shows you `async / await`. Pretty cool, huh? 29 + 30 + If you’re the kind of person who likes to learn about programming ideas several years before they hit the mainstream, it might be a good time to get curious about algebraic effects. Don’t feel like you *have to* though. It is a bit like thinking about `async / await` in 1999. 31 + 32 + ### Okay, What Are Algebraic Effects? 33 + 34 + The name might be a bit intimidating but the idea is simple. If you’re familiar with `try / catch` blocks, you’ll figure out algebraic effects very fast. 35 + 36 + Let’s recap `try / catch` first. Say you have a function that throws. Maybe there’s a bunch of functions between it and the `catch` block: 37 + 38 + ```jsx{4,19} 39 + function getName(user) { 40 + let name = user.name; 41 + if (name === null) { 42 + throw new Error('A girl has no name'); 43 + } 44 + return name; 45 + } 46 + 47 + function makeFriends(user1, user2) { 48 + user1.friendNames.push(getName(user2)); 49 + user2.friendNames.push(getName(user1)); 50 + } 51 + 52 + const arya = { name: null, friendNames: [] }; 53 + const gendry = { name: 'Gendry', friendNames: [] }; 54 + try { 55 + makeFriends(arya, gendry); 56 + } catch (err) { 57 + console.log("Oops, that didn't work out: ", err); 58 + } 59 + ``` 60 + 61 + We `throw` inside `getName`, but it “bubbles” up right through `makeFriends` to the closest `catch` block. This is an important property of `try / catch`. **Things in the middle don’t need to concern themselves with error handling.** 62 + 63 + Unlike error codes in languages like C, with `try / catch`, you don’t have to manually pass errors through every intermediate layer in the fear of losing them. They get propagated automatically. 64 + 65 + ### What Does This Have to Do With Algebraic Effects? 66 + 67 + In the above example, once we hit an error, we can’t continue. When we end up in the `catch` block, there’s no way we can continue executing the original code. 68 + 69 + We’re done. It’s too late. The best we can do is to recover from a failure and maybe somehow retry what we were doing, but we can’t magically “go back” to where we were, and do something different. **But with algebraic effects, *we can*.** 70 + 71 + This is an example written in a hypothetical JavaScript dialect (let’s call it ES2025 just for kicks) that lets us *recover* from a missing `user.name`: 72 + 73 + ```jsx{4,19-21} 74 + function getName(user) { 75 + let name = user.name; 76 + if (name === null) { 77 + name = perform 'ask_name'; 78 + } 79 + return name; 80 + } 81 + 82 + function makeFriends(user1, user2) { 83 + user1.friendNames.push(getName(user2)); 84 + user2.friendNames.push(getName(user1)); 85 + } 86 + 87 + const arya = { name: null, friendNames: [] }; 88 + const gendry = { name: 'Gendry', friendNames: [] }; 89 + try { 90 + makeFriends(arya, gendry); 91 + } handle (effect) { 92 + if (effect === 'ask_name') { 93 + resume with 'Arya Stark'; 94 + } 95 + } 96 + ``` 97 + 98 + *(I apologize to all readers from 2025 who search the web for “ES2025” and find this article. If algebraic effects are a part of JavaScript by then, I’d be happy to update it!)* 99 + 100 + Instead of `throw`, we use a hypothetical `perform` keyword. Similarly, instead of `try / catch`, we use a hypothetical `try / handle`. **The exact syntax doesn’t matter here — I just came up with something to illustrate the idea.** 101 + 102 + So what’s happening? Let’s take a closer look. 103 + 104 + Instead of throwing an error, we *perform an effect*. Just like we can `throw` any value, we can pass any value to `perform`. In this example, I’m passing a string, but it could be an object, or any other data type: 105 + 106 + ```jsx{4} 107 + function getName(user) { 108 + let name = user.name; 109 + if (name === null) { 110 + name = perform 'ask_name'; 111 + } 112 + return name; 113 + } 114 + ``` 115 + 116 + When we `throw` an error, the engine looks for the closest `try / catch` error handler up the call stack. Similarly, when we `perform` an effect, the engine would search for the closest `try / handle` *effect handler* up the call stack: 117 + 118 + ```jsx{3} 119 + try { 120 + makeFriends(arya, gendry); 121 + } handle (effect) { 122 + if (effect === 'ask_name') { 123 + resume with 'Arya Stark'; 124 + } 125 + } 126 + ``` 127 + 128 + This effect lets us decide how to handle the case where a name is missing. The novel part here (compared to exceptions) is the hypothetical `resume with`: 129 + 130 + ```jsx{5} 131 + try { 132 + makeFriends(arya, gendry); 133 + } handle (effect) { 134 + if (effect === 'ask_name') { 135 + resume with 'Arya Stark'; 136 + } 137 + } 138 + ``` 139 + 140 + This is the part you can’t do with `try / catch`. It lets us **jump back to where we performed the effect, and pass something back to it from the handler**. 🤯 141 + 142 + ```jsx{4,6,16,18} 143 + function getName(user) { 144 + let name = user.name; 145 + if (name === null) { 146 + // 1. We perform an effect here 147 + name = perform 'ask_name'; 148 + // 4. ...and end up back here (name is now 'Arya Stark') 149 + } 150 + return name; 151 + } 152 + 153 + // ... 154 + 155 + try { 156 + makeFriends(arya, gendry); 157 + } handle (effect) { 158 + // 2. We jump to the handler (like try/catch) 159 + if (effect === 'ask_name') { 160 + // 3. However, we can resume with a value (unlike try/catch!) 161 + resume with 'Arya Stark'; 162 + } 163 + } 164 + ``` 165 + 166 + This takes a bit of time to get comfortable with, but it’s really not much different conceptually from a “resumable `try / catch`”. 167 + 168 + Note, however, that **algebraic effects are much more flexible than `try / catch`, and recoverable errors are just one of many possible use cases.** I started with it only because I found it easiest to wrap my mind around it. 169 + 170 + ### A Function Has No Color 171 + 172 + Algebraic effects have interesting implications for asynchronous code. 173 + 174 + In languages with an `async / await`, [functions usually have a “color”](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). For example, in JavaScript we can’t just make `getName` asynchronous without also “infecting” `makeFriends` and its callers with being `async`. This can be a real pain if *a piece of code sometimes needs to be sync, and sometimes needs to be async*. 175 + 176 + ```jsx 177 + // If we want to make this async... 178 + async getName(user) { 179 + // ... 180 + } 181 + 182 + // Then this has to be async too... 183 + async function makeFriends(user1, user2) { 184 + user1.friendNames.push(await getName(user2)); 185 + user2.friendNames.push(await getName(user1)); 186 + } 187 + 188 + // And so on... 189 + ``` 190 + 191 + JavaScript generators are [similar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*): if you’re working with generators, things in the middle also have to be aware of generators. 192 + 193 + So how is that relevant? 194 + 195 + For a moment, let’s forget about `async / await` and get back to our example: 196 + 197 + ```jsx{4,19-21} 198 + function getName(user) { 199 + let name = user.name; 200 + if (name === null) { 201 + name = perform 'ask_name'; 202 + } 203 + return name; 204 + } 205 + 206 + function makeFriends(user1, user2) { 207 + user1.friendNames.push(getName(user2)); 208 + user2.friendNames.push(getName(user1)); 209 + } 210 + 211 + const arya = { name: null, friendNames: [] }; 212 + const gendry = { name: 'Gendry', friendNames: [] }; 213 + try { 214 + makeFriends(arya, gendry); 215 + } handle (effect) { 216 + if (effect === 'ask_name') { 217 + resume with 'Arya Stark'; 218 + } 219 + } 220 + ``` 221 + 222 + What if our effect handler didn’t know the “fallback name” synchronously? What if we wanted to fetch it from a database? 223 + 224 + It turns out, we can call `resume with` asynchronously from our effect handler without making any changes to `getName` or `makeFriends`: 225 + 226 + ```jsx{19-23} 227 + function getName(user) { 228 + let name = user.name; 229 + if (name === null) { 230 + name = perform 'ask_name'; 231 + } 232 + return name; 233 + } 234 + 235 + function makeFriends(user1, user2) { 236 + user1.friendNames.push(getName(user2)); 237 + user2.friendNames.push(getName(user1)); 238 + } 239 + 240 + const arya = { name: null, friendNames: [] }; 241 + const gendry = { name: 'Gendry', friendNames: [] }; 242 + try { 243 + makeFriends(arya, gendry); 244 + } handle (effect) { 245 + if (effect === 'ask_name') { 246 + setTimeout(() => { 247 + resume with 'Arya Stark'; 248 + }, 1000); 249 + } 250 + } 251 + ``` 252 + 253 + In this example, we don’t call `resume with` until a second later. You can think of `resume with` as a callback which you may only call once. (You can also impress your friends by calling it a “one-shot delimited continuation.”) 254 + 255 + Now the mechanics of algebraic effects should be a bit clearer. When we `throw` an error, the JavaScript engine “unwinds the stack”, destroying local variables in the process. However, when we `perform` an effect, our hypothetical engine would *create a callback* with the rest of our function, and `resume with` calls it. 256 + 257 + **Again, a reminder: the concrete syntax and specific keywords are made up for this article. They’re not the point, the point is in the mechanics.** 258 + 259 + ### A Note on Purity 260 + 261 + It’s worth noting that algebraic effects came out of functional programming research. Some of the problems they solve are unique to pure functional programming. For example, in languages that *don’t* allow arbitrary side effects (like Haskell), you have to use concepts like Monads to wire effects through your program. If you ever read a Monad tutorial, you know they’re a bit tricky to think about. Algebraic effects help do something similar with less ceremony. 262 + 263 + This is why so much discussion about algebraic effects is incomprehensible to me. (I [don’t know](/things-i-dont-know-as-of-2018/) Haskell and friends.) However, I do think that even in an impure language like JavaScript, **algebraic effects can be a very powerful instrument to separate the *what* from the *how* in the code.** 264 + 265 + They let you write code that focuses on *what* you’re doing: 266 + 267 + ```jsx{2,3,5,7,12} 268 + function enumerateFiles(dir) { 269 + const contents = perform OpenDirectory(dir); 270 + perform Log('Enumerating files in ', dir); 271 + for (let file of contents.files) { 272 + perform HandleFile(file); 273 + } 274 + perform Log('Enumerating subdirectories in ', dir); 275 + for (let directory of contents.dir) { 276 + // We can use recursion or call other functions with effects 277 + enumerateFiles(directory); 278 + } 279 + perform Log('Done'); 280 + } 281 + ``` 282 + 283 + And later wrap it with something that specifies *how*: 284 + 285 + ```jsx{6-7,9-11,13-14} 286 + let files = []; 287 + try { 288 + enumerateFiles('C:\\'); 289 + } handle (effect) { 290 + if (effect instanceof Log) { 291 + myLoggingLibrary.log(effect.message); 292 + resume; 293 + } else if (effect instanceof OpenDirectory) { 294 + myFileSystemImpl.openDir(effect.dirName, (contents) => { 295 + resume with contents; 296 + }); 297 + } else if (effect instanceof HandleFile) { 298 + files.push(effect.fileName); 299 + resume; 300 + } 301 + } 302 + // The `files` array now has all the files 303 + ``` 304 + 305 + Which means that those pieces can even become librarified: 306 + 307 + ```jsx 308 + import { withMyLoggingLibrary } from 'my-log'; 309 + import { withMyFileSystem } from 'my-fs'; 310 + 311 + function ourProgram() { 312 + enumerateFiles('C:\\'); 313 + } 314 + 315 + withMyLoggingLibrary(() => { 316 + withMyFileSystem(() => { 317 + ourProgram(); 318 + }); 319 + }); 320 + ``` 321 + 322 + Unlike `async / await` or Generators, **algebraic effects don’t require complicating functions “in the middle”**. Our `enumerateFiles` call could be deep within `ourProgram`, but as long as there’s an effect handler *somewhere above* for each of the effects it may perform, our code would still work. 323 + 324 + Effect handlers let us decouple the program logic from its concrete effect implementations without too much ceremony or boilerplate code. For example, we could completely override the behavior in tests to use a fake filesystem and to snapshot logs instead of outputting them to the console: 325 + 326 + ```jsx{19-23} 327 + import { withFakeFileSystem } from 'fake-fs'; 328 + 329 + function withLogSnapshot(fn) { 330 + let logs = []; 331 + try { 332 + fn(); 333 + } handle (effect) { 334 + if (effect instanceof Log) { 335 + logs.push(effect.message); 336 + resume; 337 + } 338 + } 339 + // Snapshot emitted logs. 340 + expect(logs).toMatchSnapshot(); 341 + } 342 + 343 + test('my program', () => { 344 + const fakeFiles = [/* ... */]; 345 + withFakeFileSystem(fakeFiles, () => { 346 + withLogSnapshot(() => { 347 + ourProgram(); 348 + }); 349 + }); 350 + }); 351 + ``` 352 + 353 + Because there is no [“function color”](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) (code in the middle doesn’t need to be aware of effects) and effect handlers are *composable* (you can nest them), you can create very expressive abstractions with them. 354 + 355 + ### A Note on Types 356 + 357 + Because algebraic effects are coming from statically typed languages, much of the debate about them centers on the ways they can be expressed in types. This is no doubt important but can also make it challenging to grasp the concept. That’s why this article doesn’t talk about types at all. However, I should note that usually the fact that a function can perform an effect would be encoded into its type signature. So you shouldn’t end up in a situation where random effects are happening and you can’t trace where they’re coming from. 358 + 359 + You might argue that algebraic effects technically do [“give color”](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) to functions in statically typed languages because effects are a part of the type signature. That’s true. However, fixing a type annotation for an intermediate function to include a new effect is not by itself a semantic change — unlike adding `async` or turning a function into a generator. Inference can also help avoid cascading changes. An important difference is you can “bottle up” an effect by providing a noop or a mock implementation (for example, a sync call for an async effect), which lets you prevent it from reaching the outer code if necessary — or turn it into a different effect. 360 + 361 + ### Should We Add Algebraic Effects to JavaScript? 362 + 363 + Honestly, I don’t know. They are very powerful, and you can make an argument that they might be *too* powerful for a language like JavaScript. 364 + 365 + I think they could be a great fit for a language where mutation is uncommon, and where the standard library fully embraced effects. If you primarily do `perform Timeout(1000)`, `perform Fetch('http://google.com')`, and `perform ReadFile('file.txt')`, and your language has pattern matching and static typing for effects, it might be a very nice programming environment. 366 + 367 + Maybe that language could even compile to JavaScript! 368 + 369 + ### How Is All of This Relevant to React? 370 + 371 + Not that much. You can even say it’s a stretch. 372 + 373 + If you watched [my talk about Time Slicing and Suspense](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html), the second part involves components reading data from a cache: 374 + 375 + ```jsx 376 + function MovieDetails({ id }) { 377 + // What if it's still being fetched? 378 + const movie = movieCache.read(id); 379 + } 380 + ``` 381 + 382 + *(The talk uses a slightly different API but that’s not the point.)* 383 + 384 + This builds on a React feature called “Suspense”, which is in active development for the data fetching use case. The interesting part, of course, is that the data might not yet be in the `movieCache` — in which case we need to do *something* because we can’t proceed below. Technically, in that case the `read()` call throws a Promise (yes, *throws* a Promise — let that sink in). This “suspends” the execution. React catches that Promise, and remembers to retry rendering the component tree after the thrown Promise resolves. 385 + 386 + This isn’t an algebraic effect per se, even though this trick was [inspired](https://mobile.twitter.com/sebmarkbage/status/941214259505119232) by them. But it achieves the same goal: some code below in the call stack yields to something above in the call stack (React, in this case) without all the intermediate functions necessarily knowing about it or being “poisoned” by `async` or generators. Of course, we can’t really *resume* execution in JavaScript later, but from React’s point of view, re-rendering a component tree when the Promise resolves is pretty much the same thing. You can cheat when your programming model [assumes idempotence](/react-as-a-ui-runtime/#purity)! 387 + 388 + [Hooks](https://reactjs.org/docs/hooks-intro.html) are another example that might remind you of algebraic effects. One of the first questions that people ask is: how can a `useState` call possibly know which component it refers to? 389 + 390 + ```jsx 391 + function LikeButton() { 392 + // How does useState know which component it's in? 393 + const [isLiked, setIsLiked] = useState(false); 394 + } 395 + ``` 396 + 397 + I already explained the answer [near the end of this article](/how-does-setstate-know-what-to-do/): there is a “current dispatcher” mutable state on the React object which points to the implementation you’re using right now (such as the one in `react-dom`). There is similarly a “current component” property that points to our `LikeButton`’s internal data structure. That’s how `useState` knows what to do. 398 + 399 + Before people get used to it, they often think it’s a bit “dirty” for an obvious reason. It doesn’t “feel right” to rely on shared mutable state. *(Side note: how do you think `try / catch` is implemented in a JavaScript engine?)* 400 + 401 + However, conceptually you can think of `useState()` as of being a `perform State()` effect which is handled by React when executing your component. That would “explain” why React (the thing calling your component) can provide state to it (it’s above in the call stack, so it can provide the effect handler). Indeed, [implementing state](https://github.com/ocamllabs/ocaml-effects-tutorial/#2-effectful-computations-in-a-pure-setting) is one of the most common examples in the algebraic effect tutorials I’ve encountered. 402 + 403 + Again, of course, that’s not how React *actually* works because we don’t have algebraic effects in JavaScript. Instead, there is a hidden field where we keep the current component, as well as a field that points to the current “dispatcher” with the `useState` implementation. As a performance optimization, there are even separate `useState` implementations [for mounts and updates](https://github.com/facebook/react/blob/2c4d61e1022ae383dd11fe237f6df8451e6f0310/packages/react-reconciler/src/ReactFiberHooks.js#L1260-L1290). But if you squint at this code very hard, you might see them as essentially effect handlers. 404 + 405 + To sum up, in JavaScript, throwing can serve as a crude approximation for IO effects (as long as it’s safe to re-execute the code later, and as long as it’s not CPU-bound), and having a mutable “dispatcher” field that’s restored in `try / finally` can serve as a crude approximation for synchronous effect handlers. 406 + 407 + You can also get a much higher fidelity effect implementation [with generators](https://dev.to/yelouafi/algebraic-effects-in-javascript-part-4---implementing-algebraic-effects-and-handlers-2703) but that means you’ll have to give up on the “transparent” nature of JavaScript functions and you’ll have to make everything a generator. Which is... yeah. 408 + 409 + ### Learn More 410 + 411 + Personally, I was surprised by how much algebraic effects made sense to me. I always struggled understanding abstract concepts like Monads, but Algebraic Effects just “clicked”. I hope this article will help them “click” for you too. 412 + 413 + I don’t know if they’re ever going to reach mainstream adoption. I think I’ll be disappointed if they don’t catch on in any mainstream language by 2025. Remind me to check back in five years! 414 + 415 + I’m sure there’s so much more you can do with them — but it’s really difficult to get a sense of their power without actually writing code this way. If this post made you curious, here’s a few more resources you might want to check out: 416 + 417 + * https://github.com/ocamllabs/ocaml-effects-tutorial 418 + 419 + * https://www.janestreet.com/tech-talks/effective-programming/ 420 + 421 + * https://www.youtube.com/watch?v=hrBq8R_kxI0 422 + 423 + Many people also pointed out that if you omit the typing aspects (as I did in this article), you can find much earlier prior art for this in the [condition system](https://en.wikibooks.org/wiki/Common_Lisp/Advanced_topics/Condition_System) in Common Lisp. You might also enjoy reading James Long’s [post on continuations](https://jlongster.com/Whats-in-a-Continuation) that explains how the `call/cc` primitive can also serve as a foundation for building resumable exceptions in userland. 424 + 425 + If you find other useful resources on algebraic effects for people with JavaScript background, please let me know on Twitter!
+178
public/before-you-memo/index.md
··· 1 + --- 2 + title: 'Before You memo()' 3 + date: '2021-02-23' 4 + spoiler: "Rendering optimizations that come naturally." 5 + --- 6 + 7 + There are many articles written about React performance optimizations. In general, if some state update is slow, you need to: 8 + 9 + 1. Verify you're running a production build. (Development builds are intentionally slower, in extreme cases even by an order of magnitude.) 10 + 2. Verify that you didn't put the state higher in the tree than necessary. (For example, putting input state in a centralized store might not be the best idea.) 11 + 3. Run React DevTools Profiler to see what gets re-rendered, and wrap the most expensive subtrees with `memo()`. (And add `useMemo()` where needed.) 12 + 13 + This last step is annoying, especially for components in between, and ideally a compiler would do it for you. In the future, it might. 14 + 15 + **In this post, I want to share two different techniques.** They're surprisingly basic, which is why people rarely realize they improve rendering performance. 16 + 17 + **These techniques are complementary to what you already know!** They don't replace `memo` or `useMemo`, but they're often good to try first. 18 + 19 + ## An (Artificially) Slow Component 20 + 21 + Here's a component with a severe rendering performance problem: 22 + 23 + ```jsx 24 + import { useState } from 'react'; 25 + 26 + export default function App() { 27 + let [color, setColor] = useState('red'); 28 + return ( 29 + <div> 30 + <input value={color} onChange={(e) => setColor(e.target.value)} /> 31 + <p style={{ color }}>Hello, world!</p> 32 + <ExpensiveTree /> 33 + </div> 34 + ); 35 + } 36 + 37 + function ExpensiveTree() { 38 + let now = performance.now(); 39 + while (performance.now() - now < 100) { 40 + // Artificial delay -- do nothing for 100ms 41 + } 42 + return <p>I am a very slow component tree.</p>; 43 + } 44 + ``` 45 + 46 + *([Try it here](https://codesandbox.io/s/frosty-glade-m33km?file=/src/App.js:23-513))* 47 + 48 + The problem is that whenever `color` changes inside `App`, we will re-render `<ExpensiveTree />` which we've artificially delayed to be very slow. 49 + 50 + I could [put `memo()` on it](https://codesandbox.io/s/amazing-shtern-61tu4?file=/src/App.js) and call it a day, but there are many existing articles about it so I won't spend time on it. I want to show two different solutions. 51 + 52 + ## Solution 1: Move State Down 53 + 54 + If you look at the rendering code closer, you'll notice only a part of the returned tree actually cares about the current `color`: 55 + 56 + ```jsx{2,5-6} 57 + export default function App() { 58 + let [color, setColor] = useState('red'); 59 + return ( 60 + <div> 61 + <input value={color} onChange={(e) => setColor(e.target.value)} /> 62 + <p style={{ color }}>Hello, world!</p> 63 + <ExpensiveTree /> 64 + </div> 65 + ); 66 + } 67 + ``` 68 + 69 + So let's extract that part into a `Form` component and move state _down_ into it: 70 + 71 + ```jsx{4,11,14,15} 72 + export default function App() { 73 + return ( 74 + <> 75 + <Form /> 76 + <ExpensiveTree /> 77 + </> 78 + ); 79 + } 80 + 81 + function Form() { 82 + let [color, setColor] = useState('red'); 83 + return ( 84 + <> 85 + <input value={color} onChange={(e) => setColor(e.target.value)} /> 86 + <p style={{ color }}>Hello, world!</p> 87 + </> 88 + ); 89 + } 90 + ``` 91 + 92 + *([Try it here](https://codesandbox.io/s/billowing-wood-1tq2u?file=/src/App.js:64-380))* 93 + 94 + Now if the `color` changes, only the `Form` re-renders. Problem solved. 95 + 96 + ## Solution 2: Lift Content Up 97 + 98 + The above solution doesn't work if the piece of state is used somewhere *above* the expensive tree. For example, let's say we put the `color` on the *parent* `<div>`: 99 + 100 + ```jsx{2,4} 101 + export default function App() { 102 + let [color, setColor] = useState('red'); 103 + return ( 104 + <div style={{ color }}> 105 + <input value={color} onChange={(e) => setColor(e.target.value)} /> 106 + <p>Hello, world!</p> 107 + <ExpensiveTree /> 108 + </div> 109 + ); 110 + } 111 + ``` 112 + 113 + *([Try it here](https://codesandbox.io/s/bold-dust-0jbg7?file=/src/App.js:58-313))* 114 + 115 + Now it seems like we can't just "extract" the parts that don't use `color` into another component, since that would include the parent `<div>`, which would then include `<ExpensiveTree />`. Can't avoid `memo` this time, right? 116 + 117 + Or can we? 118 + 119 + Play with this sandbox and see if you can figure it out. 120 + 121 + ... 122 + 123 + ... 124 + 125 + ... 126 + 127 + The answer is remarkably plain: 128 + 129 + ```jsx{4,5,10,15} 130 + export default function App() { 131 + return ( 132 + <ColorPicker> 133 + <p>Hello, world!</p> 134 + <ExpensiveTree /> 135 + </ColorPicker> 136 + ); 137 + } 138 + 139 + function ColorPicker({ children }) { 140 + let [color, setColor] = useState("red"); 141 + return ( 142 + <div style={{ color }}> 143 + <input value={color} onChange={(e) => setColor(e.target.value)} /> 144 + {children} 145 + </div> 146 + ); 147 + } 148 + ``` 149 + 150 + *([Try it here](https://codesandbox.io/s/wonderful-banach-tyfr1?file=/src/App.js:58-423))* 151 + 152 + We split the `App` component in two. The parts that depend on the `color`, together with the `color` state variable itself, have moved into `ColorPicker`. 153 + 154 + The parts that don't care about the `color` stayed in the `App` component and are passed to `ColorPicker` as JSX content, also known as the `children` prop. 155 + 156 + When the `color` changes, `ColorPicker` re-renders. But it still has the same `children` prop it got from the `App` last time, so React doesn't visit that subtree. 157 + 158 + And as a result, `<ExpensiveTree />` doesn't re-render. 159 + 160 + ## What's the moral? 161 + 162 + Before you apply optimizations like `memo` or `useMemo`, it might make sense to look if you can split the parts that change from the parts that don't change. 163 + 164 + The interesting part about these approaches is that **they don't really have anything to do with performance, per se**. Using the `children` prop to split up components usually makes the data flow of your application easier to follow and reduces the number of props plumbed down through the tree. Improved performance in cases like this is a cherry on top, not the end goal. 165 + 166 + Curiously, this pattern also unlocks _more_ performance benefits in the future. 167 + 168 + For example, when [Server Components](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html) are stable and ready for adoption, our `ColorPicker` component could receive its `children` [from the server](https://youtu.be/TQQPAU21ZUw?t=1314). Either the whole `<ExpensiveTree />` component or its parts could run on the server, and even a top-level React state update would "skip over" those parts on the client. 169 + 170 + That's something even `memo` couldn't do! But again, both approaches are complementary. Don't neglect moving state down (and lifting content up!) 171 + 172 + Then, where it's not enough, use the Profiler and sprinkle those memo’s. 173 + 174 + ## Didn't I read about this before? 175 + 176 + [Yes, probably.](https://kentcdodds.com/blog/optimize-react-re-renders) 177 + 178 + This is not a new idea. It's a natural consequence of React composition model. It's simple enough that it's underappreciated, and deserves a bit more love.
+29
public/coping-with-feedback/index.md
··· 1 + --- 2 + title: 'Coping with Feedback' 3 + date: '2019-03-02' 4 + spoiler: Sometimes I can’t fall asleep. 5 + --- 6 + 7 + The last few weeks have been very hectic. We’ve finally released a stable version of [React Hooks](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html) and the first [React website translations](https://reactjs.org/blog/2019/02/23/is-react-translated-yet.html). Both of these projects meant a lot to me, and shipping them felt great. 8 + 9 + However, I also feel more pressure now to stay on top of the scrutiny that comes when you mark something as stable. Hooks are very new, we are still figuring out “best practices” for some of the less common patterns — both internally at Facebook and in GitHub discussions. But the longer it takes to document and enforce them (e.g. with warnings), the more risk there is that someone will create a course or write an article using a broken pattern without realizing that. It’s hard not to feel at least partially responsible when that happens. 10 + 11 + Most of this is self-imposed. No one technically expects me to care. I do though. 12 + 13 + Maintaining a balance between *providing* guidance and *learning* from people using an API and their experiences can take some toll. Especially when you don’t know all the answers yet. Over the past weeks, I had a few nights when I couldn’t remember if I slept at all due to nonstopping internal monologue. 14 + 15 + I’ve noticed that I have a particular set of triggers that can cause this. So I’m more careful to avoid them. In case it’s helpful to anyone else, here’s a set of rules I’m trying to follow which seem to help empirically: 16 + 17 + * **Not drinking more than two cups of coffee.** Coffee is my friend. I tried giving up caffeine for a few months and I had headaches spanning for more than the tolerance period. Coffee actually *does* help me (maybe some blood pressure thing?) but in moderation. Drinking more than two cups leaves me too pumped. 18 + 19 + * **Not arguing with strangers after 9pm.** I love debating things on Twitter, and I’m no stranger to occasional disagreements. Some of them can turn pretty draining emotionally — especially when people assume bad intent. Even if I try to maintain a friendly tone, this kind of thing hurts and then I get too agitated to fall asleep. So I’ve been trying to avoid reading any feedback in the evenings. 20 + 21 + * **Not skipping meals or eating after 8pm.** This one is weird. (Don’t forget, I’m just describing what works for me!) Generally I’ve been pretty flexible with meals. But lately I’ve noticed that whenever I wake up at 5am and can’t fall asleep, my stomach feels heavy. I don’t know whether it’s the *reason* or the *consequence* of a buzzing mind, but there is a correlation. Take care of your gut. 22 + 23 + * **Not publishing articles right before going to bed.** Whenever I share anything, I get a bit excited and nervous about the feedback. (Will anyone notice it? Is it too controversial? How will it rank on aggregators? Do people like reading it?) I found it useful to leave a buffer of a few hours for the initial feedback so that curiosity and nerves don’t wake me up a few hours later. 24 + 25 + * **Not lying there trying to fall asleep.** A few times, I would wake up at 5am and then lie in bed until 10am in a vain attempt to relax. This doesn’t work for me. However, I’ve noticed that if I simply do whatever I feel like (write some code, read some online junk, eat a banana), eventually I get sleepy. Splitting the night in two halves feels a bit awkward but it’s still better than not sleeping at all. 26 + 27 + The deeper problem is that I’m restless when I know there is a problem but I don’t have a fix ready yet. Such as writing documentation, correcting a bug or sharing an idea. This drive can be helpful but at some point it’s not worth it. 28 + 29 + The techniques above help me reduce the anxiety to the point where that drive is productive to me. But I should watch out. It’s important to be surrounded by people who can tell when you’re going off the rails. I feel thankful to have them.
+23
public/fix-like-no-ones-watching/index.md
··· 1 + --- 2 + title: 'Fix Like No One’s Watching' 3 + date: '2019-02-15' 4 + spoiler: The other kind of technical debt. 5 + --- 6 + 7 + Some technical debt is in plain sight. 8 + 9 + An inadequate data structure might lead to convoluted code. When the requirements keep changing, the code might contain traces of previous approaches. Sometimes the code is written in a hurry or is just sloppy. 10 + 11 + This kind of technical debt is easy to discuss because it’s highly visible. It manifests as bugs, performance problems, and difficulties adding features. 12 + 13 + There is another, more insidious kind of technical debt. 14 + 15 + Maybe the test suite is a *little bit* slow. Not slow to a crawl — but just enough that you don’t bother looking at a bug and add it to the backlog instead. Maybe you don’t trust the deployment script so you skip that extra release. Perhaps the abstraction layers make it too hard to locate a performance regression so you leave a TODO in the code. Sometimes the unit tests are too rigid so you postpone trying an intriguing new idea until after you’ve shipped the planned features. 16 + 17 + None of these things are dealbreakers. If anything, they might seem like distractions. It feels vain to complain about them. After all, if they *really* mattered, you would have done those things despite the friction, wouldn’t you? 18 + 19 + And so these things never get done. They don’t seem important enough by themselves. **The friction killed them.** Some of these explorations could be of no consequence. Some of them could redefine your project. 20 + 21 + You never know. This is why you must actively reduce friction. Like your project’s fate depends on it. Because it does. 22 + 23 + Fix like no one’s watching.
+181
public/goodbye-clean-code/index.md
··· 1 + --- 2 + title: 'Goodbye, Clean Code' 3 + date: '2020-01-11' 4 + spoiler: Let clean code guide you. Then let it go. 5 + --- 6 + 7 + It was a late evening. 8 + 9 + My colleague has just checked in the code that they've been writing all week. We were working on a graphics editor canvas, and they implemented the ability to resize shapes like rectangles and ovals by dragging small handles at their edges. 10 + 11 + The code worked. 12 + 13 + But it was repetitive. Each shape (such as a rectangle or an oval) had a different set of handles, and dragging each handle in different directions affected the shape's position and size in a different way. If the user held Shift, we'd also need to preserve proportions while resizing. There was a bunch of math. 14 + 15 + The code looked something like this: 16 + 17 + ```jsx 18 + let Rectangle = { 19 + resizeTopLeft(position, size, preserveAspect, dx, dy) { 20 + // 10 repetitive lines of math 21 + }, 22 + resizeTopRight(position, size, preserveAspect, dx, dy) { 23 + // 10 repetitive lines of math 24 + }, 25 + resizeBottomLeft(position, size, preserveAspect, dx, dy) { 26 + // 10 repetitive lines of math 27 + }, 28 + resizeBottomRight(position, size, preserveAspect, dx, dy) { 29 + // 10 repetitive lines of math 30 + }, 31 + }; 32 + 33 + let Oval = { 34 + resizeLeft(position, size, preserveAspect, dx, dy) { 35 + // 10 repetitive lines of math 36 + }, 37 + resizeRight(position, size, preserveAspect, dx, dy) { 38 + // 10 repetitive lines of math 39 + }, 40 + resizeTop(position, size, preserveAspect, dx, dy) { 41 + // 10 repetitive lines of math 42 + }, 43 + resizeBottom(position, size, preserveAspect, dx, dy) { 44 + // 10 repetitive lines of math 45 + }, 46 + }; 47 + 48 + let Header = { 49 + resizeLeft(position, size, preserveAspect, dx, dy) { 50 + // 10 repetitive lines of math 51 + }, 52 + resizeRight(position, size, preserveAspect, dx, dy) { 53 + // 10 repetitive lines of math 54 + }, 55 + } 56 + 57 + let TextBlock = { 58 + resizeTopLeft(position, size, preserveAspect, dx, dy) { 59 + // 10 repetitive lines of math 60 + }, 61 + resizeTopRight(position, size, preserveAspect, dx, dy) { 62 + // 10 repetitive lines of math 63 + }, 64 + resizeBottomLeft(position, size, preserveAspect, dx, dy) { 65 + // 10 repetitive lines of math 66 + }, 67 + resizeBottomRight(position, size, preserveAspect, dx, dy) { 68 + // 10 repetitive lines of math 69 + }, 70 + }; 71 + ``` 72 + 73 + That repetitive math was really bothering me. 74 + 75 + It wasn’t *clean*. 76 + 77 + Most of the repetition was between similar directions. For example, `Oval.resizeLeft()` had similarities with `Header.resizeLeft()`. This was because they both dealt with dragging the handle on the left side. 78 + 79 + The other similarity was between the methods for the same shape. For example, `Oval.resizeLeft()` had similarities with the other `Oval` methods. This was because they all dealt with ovals. There was also some duplication between `Rectangle`, `Header`, and `TextBlock` because text blocks *were* rectangles. 80 + 81 + I had an idea. 82 + 83 + We could *remove all duplication* by grouping the code like this instead: 84 + 85 + ```jsx 86 + let Directions = { 87 + top(...) { 88 + // 5 unique lines of math 89 + }, 90 + left(...) { 91 + // 5 unique lines of math 92 + }, 93 + bottom(...) { 94 + // 5 unique lines of math 95 + }, 96 + right(...) { 97 + // 5 unique lines of math 98 + }, 99 + }; 100 + 101 + let Shapes = { 102 + Oval(...) { 103 + // 5 unique lines of math 104 + }, 105 + Rectangle(...) { 106 + // 5 unique lines of math 107 + }, 108 + } 109 + ``` 110 + 111 + and then composing their behaviors: 112 + 113 + ```jsx 114 + let {top, bottom, left, right} = Directions; 115 + 116 + function createHandle(directions) { 117 + // 20 lines of code 118 + } 119 + 120 + let fourCorners = [ 121 + createHandle([top, left]), 122 + createHandle([top, right]), 123 + createHandle([bottom, left]), 124 + createHandle([bottom, right]), 125 + ]; 126 + let fourSides = [ 127 + createHandle([top]), 128 + createHandle([left]), 129 + createHandle([right]), 130 + createHandle([bottom]), 131 + ]; 132 + let twoSides = [ 133 + createHandle([left]), 134 + createHandle([right]), 135 + ]; 136 + 137 + function createBox(shape, handles) { 138 + // 20 lines of code 139 + } 140 + 141 + let Rectangle = createBox(Shapes.Rectangle, fourCorners); 142 + let Oval = createBox(Shapes.Oval, fourSides); 143 + let Header = createBox(Shapes.Rectangle, twoSides); 144 + let TextBox = createBox(Shapes.Rectangle, fourCorners); 145 + ``` 146 + 147 + The code is half the total size, and the duplication is gone completely! So *clean*. If we want to change the behavior for a particular direction or a shape, we could do it in a single place instead of updating methods all over the place. 148 + 149 + It was already late at night (I got carried away). I checked in my refactoring to master and went to bed, proud of how I untangled my colleague's messy code. 150 + 151 + ## The Next Morning 152 + 153 + ... did not go as expected. 154 + 155 + My boss invited me for a one-on-one chat where they politely asked me to revert my change. I was aghast. The old code was a mess, and mine was *clean*! 156 + 157 + I begrudgingly complied, but it took me years to see they were right. 158 + 159 + ## It’s a Phase 160 + 161 + Obsessing with "clean code" and removing duplication is a phase many of us go through. When we don't feel confident in our code, it is tempting to attach our sense of self-worth and professional pride to something that can be measured. A set of strict lint rules, a naming schema, a file structure, a lack of duplication. 162 + 163 + You can't automate removing duplication, but it *does* get easier with practice. You can usually tell whether there's less or more of it after every change. As a result, removing duplication feels like improving some objective metric about the code. Worse, it messes with people's sense of identity: *"I'm the kind of person who writes clean code"*. It's as powerful as any sort of self-deception. 164 + 165 + Once we learn how to create [abstractions](https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction), it is tempting to get high on that ability, and pull abstractions out of thin air whenever we see repetitive code. After a few years of coding, we see repetition *everywhere* -- and abstracting is our new superpower. If someone tells us that abstraction is a *virtue*, we'll eat it. And we'll start judging other people for not worshipping "cleanliness". 166 + 167 + I see now that my "refactoring" was a disaster in two ways: 168 + 169 + * Firstly, I didn't talk to the person who wrote it. I rewrote the code and checked it in without their input. Even if it *was* an improvement (which I don't believe anymore), this is a terrible way to go about it. A healthy engineering team is constantly *building trust*. Rewriting your teammate's code without a discussion is a huge blow to your ability to effectively collaborate on a codebase together. 170 + 171 + * Secondly, nothing is free. My code traded the ability to change requirements for reduced duplication, and it was not a good trade. For example, we later needed many special cases and behaviors for different handles on different shapes. My abstraction would have to become several times more convoluted to afford that, whereas with the original "messy" version such changes stayed easy as cake. 172 + 173 + Am I saying that you should write "dirty" code? No. I suggest to think deeply about what you mean when you say "clean" or "dirty". Do you get a feeling of revolt? Righteousness? Beauty? Elegance? How sure are you that you can name the concrete engineering outcomes corresponding to those qualities? How exactly do they affect the way the code is written and [modified](/optimized-for-change/)? 174 + 175 + I sure didn't think deeply about any of those things. I thought a lot about how the code *looked* -- but not about how it *evolved* with a team of squishy humans. 176 + 177 + Coding is a journey. Think how far you came from your first line of code to where you are now. I reckon it was a joy to see for the first time how extracting a function or refactoring a class can make convoluted code simple. If you find pride in your craft, it is tempting to pursue cleanliness in code. Do it for a while. 178 + 179 + But don't stop there. Don't be a clean code zealot. Clean code is not a goal. It's an attempt to make some sense out of the immense complexity of systems we're dealing with. It's a defense mechanism when you're not yet sure how a change would affect the codebase but you need guidance in a sea of unknowns. 180 + 181 + Let clean code guide you. **Then let it go.**
public/how-are-function-components-different-from-classes/bug.gif

This is a binary file and will not be displayed.

public/how-are-function-components-different-from-classes/fix.gif

This is a binary file and will not be displayed.

+402
public/how-are-function-components-different-from-classes/index.md
··· 1 + --- 2 + title: How Are Function Components Different from Classes? 3 + date: '2019-03-03' 4 + spoiler: They’re a whole different Pokémon. 5 + cta: 'react' 6 + --- 7 + 8 + How do React function components differ from React classes? 9 + 10 + For a while, the canonical answer has been that classes provide access to more features (like state). With [Hooks](https://reactjs.org/docs/hooks-intro.html), that’s not true anymore. 11 + 12 + Maybe you’ve heard one of them is better for performance. Which one? Many of such benchmarks are [flawed](https://medium.com/@dan_abramov/this-benchmark-is-indeed-flawed-c3d6b5b6f97f?source=your_stories_page---------------------------) so I’d be careful [drawing conclusions](https://github.com/ryardley/hooks-perf-issues/pull/2) from them. Performance primarily depends on what the code is doing rather than whether you chose a function or a class. In our observation, the performance differences are negligible, though optimization strategies are a bit [different](https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render). 13 + 14 + In either case we [don’t recommend](https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both) rewriting your existing components unless you have other reasons and don’t mind being an early adopter. Hooks are still new (like React was in 2014), and some “best practices” haven’t yet found their way into the tutorials. 15 + 16 + So where does that leave us? Are there any fundamental differences between React functions and classes at all? Of course, there are — in the mental model. **In this post, I will look at the biggest difference between them.** It existed ever since function components were [introduced](https://reactjs.org/blog/2015/09/10/react-v0.14-rc1.html#stateless-function-components) in 2015 but it’s often overlooked: 17 + 18 + >**Function components capture the rendered values.** 19 + 20 + Let’s unpack what this means. 21 + 22 + --- 23 + 24 + **Note: this post isn’t a value judgement of either classes or functions. I’m only describing the difference between these two programming models in React. For questions about adopting functions more widely, refer to the [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#adoption-strategy).** 25 + 26 + --- 27 + 28 + Consider this component: 29 + 30 + ```jsx 31 + function ProfilePage(props) { 32 + const showMessage = () => { 33 + alert('Followed ' + props.user); 34 + }; 35 + 36 + const handleClick = () => { 37 + setTimeout(showMessage, 3000); 38 + }; 39 + 40 + return ( 41 + <button onClick={handleClick}>Follow</button> 42 + ); 43 + } 44 + ``` 45 + 46 + It shows a button that simulates a network request with `setTimeout` and then shows a confirmation alert. For example, if `props.user` is `'Dan'`, it will show `'Followed Dan'` after three seconds. Simple enough. 47 + 48 + *(Note it doesn’t matter whether I use arrows or function declarations in the above example. `function handleClick()` would work exactly the same way.)* 49 + 50 + How do we write it as a class? A naïve translation might look like this: 51 + 52 + ```jsx 53 + class ProfilePage extends React.Component { 54 + showMessage = () => { 55 + alert('Followed ' + this.props.user); 56 + }; 57 + 58 + handleClick = () => { 59 + setTimeout(this.showMessage, 3000); 60 + }; 61 + 62 + render() { 63 + return <button onClick={this.handleClick}>Follow</button>; 64 + } 65 + } 66 + ``` 67 + 68 + It is common to think these two snippets of code are equivalent. People often freely refactor between these patterns without noticing their implications: 69 + 70 + ![Spot the difference between two versions](./wtf.gif) 71 + 72 + **However, these two snippets of code are subtly different.** Take a good look at them. Do you see the difference yet? Personally, it took me a while to see this. 73 + 74 + **There are spoilers ahead so here’s a [live demo](https://codesandbox.io/s/pjqnl16lm7) if you wanna figure it out on your own.** The rest of this article explains the difference and why it matters. 75 + 76 + --- 77 + 78 + Before we continue, I’d like to emphasize that the difference I’m describing has nothing to do with React Hooks per se. Examples above don’t even use Hooks! 79 + 80 + It’s all about the difference between functions and classes in React. If you plan to use functions more often in a React app, you might want to understand it. 81 + 82 + --- 83 + 84 + **We’ll illustrate the difference with a bug that is common in React applications.** 85 + 86 + Open this **[example sandbox](https://codesandbox.io/s/pjqnl16lm7)** with a current profile selector and the two `ProfilePage` implementations from above — each rendering a Follow button. 87 + 88 + Try this sequence of actions with both buttons: 89 + 90 + 1. **Click** one of the Follow buttons. 91 + 2. **Change** the selected profile before 3 seconds pass. 92 + 3. **Read** the alert text. 93 + 94 + You will notice a peculiar difference: 95 + 96 + * With the above `ProfilePage` **function**, clicking Follow on Dan’s profile and then navigating to Sophie’s would still alert `'Followed Dan'`. 97 + 98 + * With the above `ProfilePage` **class**, it would alert `'Followed Sophie'`: 99 + 100 + ![Demonstration of the steps](./bug.gif) 101 + 102 + --- 103 + 104 + 105 + In this example, the first behavior is the correct one. **If I follow a person and then navigate to another person’s profile, my component shouldn’t get confused about who I followed.** This class implementation is clearly buggy. 106 + 107 + *(You should totally [follow Sophie](https://mobile.twitter.com/sophiebits) though.)* 108 + 109 + --- 110 + 111 + So why does our class example behave this way? 112 + 113 + Let’s look closely at the `showMessage` method in our class: 114 + 115 + ```jsx{3} 116 + class ProfilePage extends React.Component { 117 + showMessage = () => { 118 + alert('Followed ' + this.props.user); 119 + }; 120 + ``` 121 + 122 + This class method reads from `this.props.user`. Props are immutable in React so they can never change. **However, `this` *is*, and has always been, mutable.** 123 + 124 + Indeed, that’s the whole purpose of `this` in a class. React itself mutates it over time so that you can read the fresh version in the `render` and lifecycle methods. 125 + 126 + So if our component re-renders while the request is in flight, `this.props` will change. The `showMessage` method reads the `user` from the “too new” `props`. 127 + 128 + This exposes an interesting observation about the nature of user interfaces. If we say that a UI is conceptually a function of current application state, **the event handlers are a part of the render result — just like the visual output**. Our event handlers “belong” to a particular render with particular props and state. 129 + 130 + However, scheduling a timeout whose callback reads `this.props` breaks that association. Our `showMessage` callback is not “tied” to any particular render, and so it “loses” the correct props. Reading from `this` severed that connection. 131 + 132 + --- 133 + 134 + **Let’s say function components didn’t exist.** How would we solve this problem? 135 + 136 + We’d want to somehow “repair” the connection between the `render` with the correct props and the `showMessage` callback that reads them. Somewhere along the way the `props` get lost. 137 + 138 + One way to do it would be to read `this.props` early during the event, and then explicitly pass them through into the timeout completion handler: 139 + 140 + ```jsx{2,7} 141 + class ProfilePage extends React.Component { 142 + showMessage = (user) => { 143 + alert('Followed ' + user); 144 + }; 145 + 146 + handleClick = () => { 147 + const {user} = this.props; 148 + setTimeout(() => this.showMessage(user), 3000); 149 + }; 150 + 151 + render() { 152 + return <button onClick={this.handleClick}>Follow</button>; 153 + } 154 + } 155 + ``` 156 + 157 + This [works](https://codesandbox.io/s/3q737pw8lq). However, this approach makes the code significantly more verbose and error-prone with time. What if we needed more than a single prop? What if we also needed to access the state? **If `showMessage` calls another method, and that method reads `this.props.something` or `this.state.something`, we’ll have the exact same problem again.** So we would have to pass `this.props` and `this.state` as arguments through every method called from `showMessage`. 158 + 159 + Doing so defeats the ergonomics normally afforded by a class. It is also difficult to remember or enforce, which is why people often settle for bugs instead. 160 + 161 + Similarly, inlining the `alert` code inside `handleClick` doesn’t answer the bigger problem. We want to structure the code in a way that allows splitting it into more methods *but* also reading the props and state that correspond to the render related to that call. **This problem isn’t even unique to React — you can reproduce it in any UI library that puts data into a mutable object like `this`.** 162 + 163 + Perhaps, we could *bind* the methods in the constructor? 164 + 165 + ```jsx{4-5} 166 + class ProfilePage extends React.Component { 167 + constructor(props) { 168 + super(props); 169 + this.showMessage = this.showMessage.bind(this); 170 + this.handleClick = this.handleClick.bind(this); 171 + } 172 + 173 + showMessage() { 174 + alert('Followed ' + this.props.user); 175 + } 176 + 177 + handleClick() { 178 + setTimeout(this.showMessage, 3000); 179 + } 180 + 181 + render() { 182 + return <button onClick={this.handleClick}>Follow</button>; 183 + } 184 + } 185 + ``` 186 + 187 + No, this doesn’t fix anything. Remember, the problem is us reading from `this.props` too late — not with the syntax we’re using! **However, the problem would go away if we fully relied on JavaScript closures.** 188 + 189 + Closures are often avoided because it’s [hard](https://wsvincent.com/javascript-closure-settimeout-for-loop/) to think about a value that can be mutated over time. But in React, props and state are immutable! (Or at least, it’s a strong recommendation.) That removes a major footgun of closures. 190 + 191 + This means that if you close over props or state from a particular render, you can always count on them staying exactly the same: 192 + 193 + ```jsx{3,4,9} 194 + class ProfilePage extends React.Component { 195 + render() { 196 + // Capture the props! 197 + const props = this.props; 198 + 199 + // Note: we are *inside render*. 200 + // These aren't class methods. 201 + const showMessage = () => { 202 + alert('Followed ' + props.user); 203 + }; 204 + 205 + const handleClick = () => { 206 + setTimeout(showMessage, 3000); 207 + }; 208 + 209 + return <button onClick={handleClick}>Follow</button>; 210 + } 211 + } 212 + ``` 213 + 214 + 215 + **You’ve “captured” props at the time of render:** 216 + 217 + ![Capturing Pokemon](./pokemon.gif) 218 + 219 + This way any code inside it (including `showMessage`) is guaranteed to see the props for this particular render. React doesn’t “move our cheese” anymore. 220 + 221 + **We could then add as many helper functions inside as we want, and they would all use the captured props and state.** Closures to the rescue! 222 + 223 + --- 224 + 225 + The [example above](https://codesandbox.io/s/oqxy9m7om5) is correct but it looks odd. What’s the point of having a class if you define functions inside `render` instead of using class methods? 226 + 227 + Indeed, we can simplify the code by removing the class “shell” around it: 228 + 229 + ```jsx 230 + function ProfilePage(props) { 231 + const showMessage = () => { 232 + alert('Followed ' + props.user); 233 + }; 234 + 235 + const handleClick = () => { 236 + setTimeout(showMessage, 3000); 237 + }; 238 + 239 + return ( 240 + <button onClick={handleClick}>Follow</button> 241 + ); 242 + } 243 + ``` 244 + 245 + Just like above, the `props` are still being captured — React passes them as an argument. **Unlike `this`, the `props` object itself is never mutated by React.** 246 + 247 + It’s a bit more obvious if you destructure `props` in the function definition: 248 + 249 + ```jsx{1,3} 250 + function ProfilePage({ user }) { 251 + const showMessage = () => { 252 + alert('Followed ' + user); 253 + }; 254 + 255 + const handleClick = () => { 256 + setTimeout(showMessage, 3000); 257 + }; 258 + 259 + return ( 260 + <button onClick={handleClick}>Follow</button> 261 + ); 262 + } 263 + ``` 264 + 265 + When the parent component renders `ProfilePage` with different props, React will call the `ProfilePage` function again. But the event handler we already clicked “belonged” to the previous render with its own `user` value and the `showMessage` callback that reads it. They’re all left intact. 266 + 267 + This is why, in the function version of [this demo](https://codesandbox.io/s/pjqnl16lm7), clicking Follow on Sophie’s profile and then changing selection to Sunil would alert `'Followed Sophie'`: 268 + 269 + ![Demo of correct behavior](./fix.gif) 270 + 271 + This behavior is correct. *(Although you might want to [follow Sunil](https://mobile.twitter.com/threepointone) too!)* 272 + 273 + --- 274 + 275 + Now we understand the big difference between functions and classes in React: 276 + 277 + >**Function components capture the rendered values.** 278 + 279 + **With Hooks, the same principle applies to state as well.** Consider this example: 280 + 281 + ```jsx 282 + function MessageThread() { 283 + const [message, setMessage] = useState(''); 284 + 285 + const showMessage = () => { 286 + alert('You said: ' + message); 287 + }; 288 + 289 + const handleSendClick = () => { 290 + setTimeout(showMessage, 3000); 291 + }; 292 + 293 + const handleMessageChange = (e) => { 294 + setMessage(e.target.value); 295 + }; 296 + 297 + return ( 298 + <> 299 + <input value={message} onChange={handleMessageChange} /> 300 + <button onClick={handleSendClick}>Send</button> 301 + </> 302 + ); 303 + } 304 + ``` 305 + 306 + (Here’s a [live demo](https://codesandbox.io/s/93m5mz9w24).) 307 + 308 + While this isn’t a very good message app UI, it illustrates the same point: if I send a particular message, the component shouldn’t get confused about which message actually got sent. This function component’s `message` captures the state that “belongs” to the render which returned the click handler called by the browser. So the `message` is set to what was in the input when I clicked “Send”. 309 + 310 + --- 311 + 312 + So we know functions in React capture props and state by default. **But what if we *want* to read the latest props or state that don’t belong to this particular render?** What if we want to [“read them from the future”](https://dev.to/scastiel/react-hooks-get-the-current-state-back-to-the-future-3op2)? 313 + 314 + In classes, you’d do it by reading `this.props` or `this.state` because `this` itself is mutable. React mutates it. In function components, you can also have a mutable value that is shared by all component renders. It’s called a “ref”: 315 + 316 + ```jsx 317 + function MyComponent() { 318 + const ref = useRef(null); 319 + // You can read or write `ref.current`. 320 + // ... 321 + } 322 + ``` 323 + 324 + However, you’ll have to manage it yourself. 325 + 326 + A ref [plays the same role](https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables) as an instance field. It’s the escape hatch into the mutable imperative world. You may be familiar with “DOM refs” but the concept is much more general. It’s just a box into which you can put something. 327 + 328 + Even visually, `this.something` looks like a mirror of `something.current`. They represent the same concept. 329 + 330 + By default, React doesn’t create refs for latest props or state in function components. In many cases you don’t need them, and it would be wasted work to assign them. However, you can track the value manually if you’d like: 331 + 332 + ```jsx{3,6,15} 333 + function MessageThread() { 334 + const [message, setMessage] = useState(''); 335 + const latestMessage = useRef(''); 336 + 337 + const showMessage = () => { 338 + alert('You said: ' + latestMessage.current); 339 + }; 340 + 341 + const handleSendClick = () => { 342 + setTimeout(showMessage, 3000); 343 + }; 344 + 345 + const handleMessageChange = (e) => { 346 + setMessage(e.target.value); 347 + latestMessage.current = e.target.value; 348 + }; 349 + ``` 350 + 351 + If we read `message` in `showMessage`, we’ll see the message at the time we pressed the Send button. But when we read `latestMessage.current`, we get the latest value — even if we kept typing after the Send button was pressed. 352 + 353 + You can compare the [two](https://codesandbox.io/s/93m5mz9w24) [demos](https://codesandbox.io/s/ox200vw8k9) to see the difference yourself. A ref is a way to “opt out” of the rendering consistency, and can be handy in some cases. 354 + 355 + Generally, you should avoid reading or setting refs *during* rendering because they’re mutable. We want to keep the rendering predictable. **However, if we want to get the latest value of a particular prop or state, it can be annoying to update the ref manually.** We could automate it by using an effect: 356 + 357 + ```jsx{4-8,11} 358 + function MessageThread() { 359 + const [message, setMessage] = useState(''); 360 + 361 + // Keep track of the latest value. 362 + const latestMessage = useRef(''); 363 + useEffect(() => { 364 + latestMessage.current = message; 365 + }); 366 + 367 + const showMessage = () => { 368 + alert('You said: ' + latestMessage.current); 369 + }; 370 + ``` 371 + 372 + (Here’s a [demo](https://codesandbox.io/s/yqmnz7xy8x).) 373 + 374 + We do the assignment *inside* an effect so that the ref value only changes after the DOM has been updated. This ensures our mutation doesn’t break features like [Time Slicing and Suspense](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) which rely on interruptible rendering. 375 + 376 + Using a ref like this isn’t necessary very often. **Capturing props or state is usually a better default.** However, it can be handy when dealing with [imperative APIs](/making-setinterval-declarative-with-react-hooks/) like intervals and subscriptions. Remember that you can track *any* value like this — a prop, a state variable, the whole props object, or even a function. 377 + 378 + This pattern can also be handy for optimizations — such as when `useCallback` identity changes too often. However, [using a reducer](https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down) is often a [better solution](https://github.com/ryardley/hooks-perf-issues/pull/3). (A topic for a future blog post!) 379 + 380 + --- 381 + 382 + In this post, we’ve looked at common broken pattern in classes, and how closures help us fix it. However, you might have noticed that when you try to optimize Hooks by specifying a dependency array, you can run into bugs with stale closures. Does it mean that closures are the problem? I don’t think so. 383 + 384 + As we’ve seen above, closures actually help us *fix* the subtle problems that are hard to notice. Similarly, they make it much easier to write code that works correctly in the [Concurrent Mode](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html). This is possible because the logic inside the component closes over the correct props and state with which it was rendered. 385 + 386 + In all cases I’ve seen so far, **the “stale closures” problems happen due to a mistaken assumption that “functions don’t change” or that “props are always the same”**. This is not the case, as I hope this post has helped to clarify. 387 + 388 + Functions close over their props and state — and so their identity is just as important. This is not a bug, but a feature of function components. Functions shouldn’t be excluded from the “dependencies array” for `useEffect` or `useCallback`, for example. (The right fix is usually either `useReducer` or the `useRef` solution above — we will soon document how to choose between them.) 389 + 390 + When we write the majority of our React code with functions, we need to adjust our intuition about [optimizing code](https://github.com/ryardley/hooks-perf-issues/pull/3) and [what values can change over time](https://github.com/facebook/react/issues/14920). 391 + 392 + As [Fredrik put it](https://mobile.twitter.com/EphemeralCircle/status/1099095063223812096): 393 + 394 + >The best mental rule I’ve found so far with hooks is ”code as if any value can change at any time”. 395 + 396 + Functions are no exception to this rule. It will take some time for this to be common knowledge in React learning materials. It requires some adjustment from the class mindset. But I hope this article helps you see it with fresh eyes. 397 + 398 + React functions always capture their values — and now we know why. 399 + 400 + ![Smiling Pikachu](./pikachu.gif) 401 + 402 + They’re a whole different Pokémon.
public/how-are-function-components-different-from-classes/pikachu.gif

This is a binary file and will not be displayed.

public/how-are-function-components-different-from-classes/pokemon.gif

This is a binary file and will not be displayed.

public/how-are-function-components-different-from-classes/wtf.gif

This is a binary file and will not be displayed.

+554
public/how-does-react-tell-a-class-from-a-function/index.md
··· 1 + --- 2 + title: How Does React Tell a Class from a Function? 3 + date: '2018-12-02' 4 + spoiler: We talk about classes, new, instanceof, prototype chains, and API design. 5 + cta: 'react' 6 + --- 7 + 8 + Consider this `Greeting` component which is defined as a function: 9 + 10 + ```jsx 11 + function Greeting() { 12 + return <p>Hello</p>; 13 + } 14 + ``` 15 + 16 + React also supports defining it as a class: 17 + 18 + ```jsx 19 + class Greeting extends React.Component { 20 + render() { 21 + return <p>Hello</p>; 22 + } 23 + } 24 + ``` 25 + 26 + (Until [recently](https://reactjs.org/docs/hooks-intro.html), that was the only way to use features like state.) 27 + 28 + When you want to render a `<Greeting />`, you don’t care how it’s defined: 29 + 30 + ```jsx 31 + // Class or function — whatever. 32 + <Greeting /> 33 + ``` 34 + 35 + But *React itself* cares about the difference! 36 + 37 + If `Greeting` is a function, React needs to call it: 38 + 39 + ```jsx 40 + // Your code 41 + function Greeting() { 42 + return <p>Hello</p>; 43 + } 44 + 45 + // Inside React 46 + const result = Greeting(props); // <p>Hello</p> 47 + ``` 48 + 49 + But if `Greeting` is a class, React needs to instantiate it with the `new` operator and *then* call the `render` method on the just created instance: 50 + 51 + ```jsx 52 + // Your code 53 + class Greeting extends React.Component { 54 + render() { 55 + return <p>Hello</p>; 56 + } 57 + } 58 + 59 + // Inside React 60 + const instance = new Greeting(props); // Greeting {} 61 + const result = instance.render(); // <p>Hello</p> 62 + ``` 63 + 64 + In both cases React’s goal is to get the rendered node (in this example, `<p>Hello</p>`). But the exact steps depend on how `Greeting` is defined. 65 + 66 + **So how does React know if something is a class or a function?** 67 + 68 + Just like in my [previous post](/why-do-we-write-super-props/), **you don’t *need* to know this to be productive in React.** I didn’t know this for years. Please don’t turn this into an interview question. In fact, this post is more about JavaScript than it is about React. 69 + 70 + This blog is for a curious reader who wants to know *why* React works in a certain way. Are you that person? Then let’s dig in together. 71 + 72 + **This is a long journey. Buckle up. This post doesn’t have much information about React itself, but we’ll go through some aspects of `new`, `this`, `class`, arrow functions, `prototype`, `__proto__`, `instanceof`, and how those things work together in JavaScript. Luckily, you don’t need to think about those as much when you *use* React. If you’re implementing React though...** 73 + 74 + (If you really just want to know the answer, scroll to the very end.) 75 + 76 + ---- 77 + 78 + First, we need to understand why it’s important to treat functions and classes differently. Note how we use the `new` operator when calling a class: 79 + 80 + ```jsx{5} 81 + // If Greeting is a function 82 + const result = Greeting(props); // <p>Hello</p> 83 + 84 + // If Greeting is a class 85 + const instance = new Greeting(props); // Greeting {} 86 + const result = instance.render(); // <p>Hello</p> 87 + ``` 88 + 89 + Let’s get a rough sense of what the `new` operator does in JavaScript. 90 + 91 + --- 92 + 93 + In the old days, JavaScript did not have classes. However, you could express a similar pattern to classes using plain functions. **Concretely, you can use *any* function in a role similar to a class constructor by adding `new` before its call:** 94 + 95 + ```jsx 96 + // Just a function 97 + function Person(name) { 98 + this.name = name; 99 + } 100 + 101 + var fred = new Person('Fred'); // ✅ Person {name: 'Fred'} 102 + var george = Person('George'); // 🔴 Won’t work 103 + ``` 104 + 105 + You can still write code like this today! Try it in DevTools. 106 + 107 + If you called `Person('Fred')` **without** `new`, `this` inside it would point to something global and useless (for example, `window` or `undefined`). So our code would crash or do something silly like setting `window.name`. 108 + 109 + By adding `new` before the call, we say: “Hey JavaScript, I know `Person` is just a function but let’s pretend it’s something like a class constructor. **Create an `{}` object and point `this` inside the `Person` function to that object so I can assign stuff like `this.name`. Then give that object back to me.**” 110 + 111 + That’s what the `new` operator does. 112 + 113 + ```jsx 114 + var fred = new Person('Fred'); // Same object as `this` inside `Person` 115 + ``` 116 + 117 + The `new` operator also makes anything we put on `Person.prototype` available on the `fred` object: 118 + 119 + ```jsx{4-6,9} 120 + function Person(name) { 121 + this.name = name; 122 + } 123 + Person.prototype.sayHi = function() { 124 + alert('Hi, I am ' + this.name); 125 + } 126 + 127 + var fred = new Person('Fred'); 128 + fred.sayHi(); 129 + ``` 130 + 131 + This is how people emulated classes before JavaScript added them directly. 132 + 133 + --- 134 + 135 + So `new` has been around in JavaScript for a while. However, classes are more recent. They let us rewrite the code above to match our intent more closely: 136 + 137 + ```jsx 138 + class Person { 139 + constructor(name) { 140 + this.name = name; 141 + } 142 + sayHi() { 143 + alert('Hi, I am ' + this.name); 144 + } 145 + } 146 + 147 + let fred = new Person('Fred'); 148 + fred.sayHi(); 149 + ``` 150 + 151 + *Capturing developer’s intent* is important in language and API design. 152 + 153 + If you write a function, JavaScript can’t guess if it’s meant to be called like `alert()` or if it serves as a constructor like `new Person()`. Forgetting to specify `new` for a function like `Person` would lead to confusing behavior. 154 + 155 + **Class syntax lets us say: “This isn’t just a function — it’s a class and it has a constructor”.** If you forget `new` when calling it, JavaScript will raise an error: 156 + 157 + ```jsx 158 + let fred = new Person('Fred'); 159 + // ✅ If Person is a function: works fine 160 + // ✅ If Person is a class: works fine too 161 + 162 + let george = Person('George'); // We forgot `new` 163 + // 😳 If Person is a constructor-like function: confusing behavior 164 + // 🔴 If Person is a class: fails immediately 165 + ``` 166 + 167 + This helps us catch mistakes early instead of waiting for some obscure bug like `this.name` being treated as `window.name` instead of `george.name`. 168 + 169 + However, it means that React needs to put `new` before calling any class. It can’t just call it as a regular function, as JavaScript would treat it as an error! 170 + 171 + ```jsx 172 + class Counter extends React.Component { 173 + render() { 174 + return <p>Hello</p>; 175 + } 176 + } 177 + 178 + // 🔴 React can't just do this: 179 + const instance = Counter(props); 180 + ``` 181 + 182 + This spells trouble. 183 + 184 + --- 185 + 186 + Before we see how React solves this, it’s important to remember most people using React use compilers like Babel to compile away modern features like classes for older browsers. So we need to consider compilers in our design. 187 + 188 + In early versions of Babel, classes could be called without `new`. However, this was fixed — by generating some extra code: 189 + 190 + ```jsx 191 + function Person(name) { 192 + // A bit simplified from Babel output: 193 + if (!(this instanceof Person)) { 194 + throw new TypeError("Cannot call a class as a function"); 195 + } 196 + // Our code: 197 + this.name = name; 198 + } 199 + 200 + new Person('Fred'); // ✅ Okay 201 + Person('George'); // 🔴 Cannot call a class as a function 202 + ``` 203 + 204 + You might have seen code like this in your bundle. That’s what all those `_classCallCheck` functions do. (You can reduce the bundle size by opting into the “loose mode” with no checks but this might complicate your eventual transition to real native classes.) 205 + 206 + --- 207 + 208 + By now, you should roughly understand the difference between calling something with `new` or without `new`: 209 + 210 + | | `new Person()` | `Person()` | 211 + |---|---|---| 212 + | `class` | ✅ `this` is a `Person` instance | 🔴 `TypeError` 213 + | `function` | ✅ `this` is a `Person` instance | 😳 `this` is `window` or `undefined` | 214 + 215 + This is why it’s important for React to call your component correctly. **If your component is defined as a class, React needs to use `new` when calling it.** 216 + 217 + So can React just check if something is a class or not? 218 + 219 + Not so easy! Even if we could [tell a class from a function in JavaScript](https://stackoverflow.com/questions/29093396/how-do-you-check-the-difference-between-an-ecmascript-6-class-and-function), this still wouldn’t work for classes processed by tools like Babel. To the browser, they’re just plain functions. Tough luck for React. 220 + 221 + --- 222 + 223 + Okay, so maybe React could just use `new` on every call? Unfortunately, that doesn’t always work either. 224 + 225 + With regular functions, calling them with `new` would give them an object instance as `this`. It’s desirable for functions written as constructor (like our `Person` above), but it would be confusing for function components: 226 + 227 + ```jsx 228 + function Greeting() { 229 + // We wouldn’t expect `this` to be any kind of instance here 230 + return <p>Hello</p>; 231 + } 232 + ``` 233 + 234 + That could be tolerable though. There are two *other* reasons that kill this idea. 235 + 236 + --- 237 + 238 + The first reason why always using `new` wouldn’t work is that for native arrow functions (not the ones compiled by Babel), calling with `new` throws an error: 239 + 240 + ```jsx 241 + const Greeting = () => <p>Hello</p>; 242 + new Greeting(); // 🔴 Greeting is not a constructor 243 + ``` 244 + 245 + This behavior is intentional and follows from the design of arrow functions. One of the main perks of arrow functions is that they *don’t* have their own `this` value — instead, `this` is resolved from the closest regular function: 246 + 247 + ```jsx{2,6,7} 248 + class Friends extends React.Component { 249 + render() { 250 + const friends = this.props.friends; 251 + return friends.map(friend => 252 + <Friend 253 + // `this` is resolved from the `render` method 254 + size={this.props.size} 255 + name={friend.name} 256 + key={friend.id} 257 + /> 258 + ); 259 + } 260 + } 261 + ``` 262 + 263 + Okay, so **arrow functions don’t have their own `this`.** But that means they would be entirely useless as constructors! 264 + 265 + ```jsx 266 + const Person = (name) => { 267 + // 🔴 This wouldn’t make sense! 268 + this.name = name; 269 + } 270 + ``` 271 + 272 + Therefore, **JavaScript disallows calling an arrow function with `new`.** If you do it, you probably made a mistake anyway, and it’s best to tell you early. This is similar to how JavaScript doesn’t let you call a class *without* `new`. 273 + 274 + This is nice but it also foils our plan. React can’t just call `new` on everything because it would break arrow functions! We could try detecting arrow functions specifically by their lack of `prototype`, and not `new` just them: 275 + 276 + ```jsx 277 + (() => {}).prototype // undefined 278 + (function() {}).prototype // {constructor: f} 279 + ``` 280 + 281 + But this [wouldn’t work](https://github.com/facebook/react/issues/4599#issuecomment-136562930) for functions compiled with Babel. This might not be a big deal, but there is another reason that makes this approach a dead end. 282 + 283 + --- 284 + 285 + Another reason we can’t always use `new` is that it would preclude React from supporting components that return strings or other primitive types. 286 + 287 + ```jsx 288 + function Greeting() { 289 + return 'Hello'; 290 + } 291 + 292 + Greeting(); // ✅ 'Hello' 293 + new Greeting(); // 😳 Greeting {} 294 + ``` 295 + 296 + This, again, has to do with the quirks of the [`new` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) design. As we saw earlier, `new` tells the JavaScript engine to create an object, make that object `this` inside the function, and later give us that object as a result of `new`. 297 + 298 + However, JavaScript also allows a function called with `new` to *override* the return value of `new` by returning some other object. Presumably, this was considered useful for patterns like pooling where we want to reuse instances: 299 + 300 + ```jsx{1-2,7-8,17-18} 301 + // Created lazily 302 + var zeroVector = null; 303 + 304 + function Vector(x, y) { 305 + if (x === 0 && y === 0) { 306 + if (zeroVector !== null) { 307 + // Reuse the same instance 308 + return zeroVector; 309 + } 310 + zeroVector = this; 311 + } 312 + this.x = x; 313 + this.y = y; 314 + } 315 + 316 + var a = new Vector(1, 1); 317 + var b = new Vector(0, 0); 318 + var c = new Vector(0, 0); // 😲 b === c 319 + ``` 320 + 321 + However, `new` also *completely ignores* a function’s return value if it’s *not* an object. If you return a string or a number, it’s like there was no `return` at all. 322 + 323 + ```jsx 324 + function Answer() { 325 + return 42; 326 + } 327 + 328 + Answer(); // ✅ 42 329 + new Answer(); // 😳 Answer {} 330 + ``` 331 + 332 + There is just no way to read a primitive return value (like a number or a string) from a function when calling it with `new`. So if React always used `new`, it would be unable to add support components that return strings! 333 + 334 + That’s unacceptable so we need to compromise. 335 + 336 + --- 337 + 338 + What did we learn so far? React needs to call classes (including Babel output) *with* `new` but it needs to call regular functions or arrow functions (including Babel output) *without* `new`. And there is no reliable way to distinguish them. 339 + 340 + **If we can’t solve a general problem, can we solve a more specific one?** 341 + 342 + When you define a component as a class, you’ll likely want to extend `React.Component` for built-in methods like `this.setState()`. **Rather than try to detect all classes, can we detect only `React.Component` descendants?** 343 + 344 + Spoiler: this is exactly what React does. 345 + 346 + --- 347 + 348 + Perhaps, the idiomatic way to check if `Greeting` is a React component class is by testing if `Greeting.prototype instanceof React.Component`: 349 + 350 + ```jsx 351 + class A {} 352 + class B extends A {} 353 + 354 + console.log(B.prototype instanceof A); // true 355 + ``` 356 + 357 + I know what you’re thinking. What just happened here?! To answer this, we need to understand JavaScript prototypes. 358 + 359 + You might be familiar with the “prototype chain”. Every object in JavaScript might have a “prototype”. When we write `fred.sayHi()` but `fred` object has no `sayHi` property, we look for `sayHi` property on `fred`’s prototype. If we don’t find it there, we look at the next prototype in the chain — `fred`’s prototype’s prototype. And so on. 360 + 361 + **Confusingly, the `prototype` property of a class or a function _does not_ point to the prototype of that value.** I’m not kidding. 362 + 363 + ```jsx 364 + function Person() {} 365 + 366 + console.log(Person.prototype); // 🤪 Not Person's prototype 367 + console.log(Person.__proto__); // 😳 Person's prototype 368 + ``` 369 + 370 + So the “prototype chain” is more like `__proto__.__proto__.__proto__` than `prototype.prototype.prototype`. This took me years to get. 371 + 372 + What’s the `prototype` property on a function or a class, then? **It’s the `__proto__` given to all objects `new`ed with that class or a function!** 373 + 374 + ```jsx{8} 375 + function Person(name) { 376 + this.name = name; 377 + } 378 + Person.prototype.sayHi = function() { 379 + alert('Hi, I am ' + this.name); 380 + } 381 + 382 + var fred = new Person('Fred'); // Sets `fred.__proto__` to `Person.prototype` 383 + ``` 384 + 385 + And that `__proto__` chain is how JavaScript looks up properties: 386 + 387 + ```jsx 388 + fred.sayHi(); 389 + // 1. Does fred have a sayHi property? No. 390 + // 2. Does fred.__proto__ have a sayHi property? Yes. Call it! 391 + 392 + fred.toString(); 393 + // 1. Does fred have a toString property? No. 394 + // 2. Does fred.__proto__ have a toString property? No. 395 + // 3. Does fred.__proto__.__proto__ have a toString property? Yes. Call it! 396 + ``` 397 + 398 + In practice, you should almost never need to touch `__proto__` from the code directly unless you’re debugging something related to the prototype chain. If you want to make stuff available on `fred.__proto__`, you’re supposed to put it on `Person.prototype`. At least that’s how it was originally designed. 399 + 400 + The `__proto__` property wasn’t even supposed to be exposed by browsers at first because the prototype chain was considered an internal concept. But some browsers added `__proto__` and eventually it was begrudgingly standardized (but deprecated in favor of `Object.getPrototypeOf()`). 401 + 402 + **And yet I still find it very confusing that a property called `prototype` does not give you a value’s prototype** (for example, `fred.prototype` is undefined because `fred` is not a function). Personally, I think this is the biggest reason even experienced developers tend to misunderstand JavaScript prototypes. 403 + 404 + --- 405 + 406 + This is a long post, eh? I’d say we’re 80% there. Hang on. 407 + 408 + We know that when we say `obj.foo`, JavaScript actually looks for `foo` in `obj`, `obj.__proto__`, `obj.__proto__.__proto__`, and so on. 409 + 410 + With classes, you’re not exposed directly to this mechanism, but `extends` also works on top of the good old prototype chain. That’s how our React class instance gets access to methods like `setState`: 411 + 412 + ```jsx{1,9,13} 413 + class Greeting extends React.Component { 414 + render() { 415 + return <p>Hello</p>; 416 + } 417 + } 418 + 419 + let c = new Greeting(); 420 + console.log(c.__proto__); // Greeting.prototype 421 + console.log(c.__proto__.__proto__); // React.Component.prototype 422 + console.log(c.__proto__.__proto__.__proto__); // Object.prototype 423 + 424 + c.render(); // Found on c.__proto__ (Greeting.prototype) 425 + c.setState(); // Found on c.__proto__.__proto__ (React.Component.prototype) 426 + c.toString(); // Found on c.__proto__.__proto__.__proto__ (Object.prototype) 427 + ``` 428 + 429 + In other words, **when you use classes, an instance’s `__proto__` chain “mirrors” the class hierarchy:** 430 + 431 + ```jsx 432 + // `extends` chain 433 + Greeting 434 + → React.Component 435 + → Object (implicitly) 436 + 437 + // `__proto__` chain 438 + new Greeting() 439 + → Greeting.prototype 440 + → React.Component.prototype 441 + → Object.prototype 442 + ``` 443 + 444 + 2 Chainz. 445 + 446 + --- 447 + 448 + Since the `__proto__` chain mirrors the class hierarchy, we can check whether a `Greeting` extends `React.Component` by starting with `Greeting.prototype`, and then following down its `__proto__` chain: 449 + 450 + ```jsx{3,4} 451 + // `__proto__` chain 452 + new Greeting() 453 + → Greeting.prototype // 🕵️ We start here 454 + → React.Component.prototype // ✅ Found it! 455 + → Object.prototype 456 + ``` 457 + 458 + Conveniently, `x instanceof Y` does exactly this kind of search. It follows the `x.__proto__` chain looking for `Y.prototype` there. 459 + 460 + Normally, it’s used to determine whether something is an instance of a class: 461 + 462 + ```jsx 463 + let greeting = new Greeting(); 464 + 465 + console.log(greeting instanceof Greeting); // true 466 + // greeting (🕵️‍ We start here) 467 + // .__proto__ → Greeting.prototype (✅ Found it!) 468 + // .__proto__ → React.Component.prototype 469 + // .__proto__ → Object.prototype 470 + 471 + console.log(greeting instanceof React.Component); // true 472 + // greeting (🕵️‍ We start here) 473 + // .__proto__ → Greeting.prototype 474 + // .__proto__ → React.Component.prototype (✅ Found it!) 475 + // .__proto__ → Object.prototype 476 + 477 + console.log(greeting instanceof Object); // true 478 + // greeting (🕵️‍ We start here) 479 + // .__proto__ → Greeting.prototype 480 + // .__proto__ → React.Component.prototype 481 + // .__proto__ → Object.prototype (✅ Found it!) 482 + 483 + console.log(greeting instanceof Banana); // false 484 + // greeting (🕵️‍ We start here) 485 + // .__proto__ → Greeting.prototype 486 + // .__proto__ → React.Component.prototype 487 + // .__proto__ → Object.prototype (🙅‍ Did not find it!) 488 + ``` 489 + 490 + But it would work just as fine to determine if a class extends another class: 491 + 492 + ```jsx 493 + console.log(Greeting.prototype instanceof React.Component); 494 + // greeting 495 + // .__proto__ → Greeting.prototype (🕵️‍ We start here) 496 + // .__proto__ → React.Component.prototype (✅ Found it!) 497 + // .__proto__ → Object.prototype 498 + ``` 499 + 500 + And that check is how we could determine if something is a React component class or a regular function. 501 + 502 + --- 503 + 504 + That’s not what React does though. 😳 505 + 506 + One caveat to the `instanceof` solution is that it doesn’t work when there are multiple copies of React on the page, and the component we’re checking inherits from *another* React copy’s `React.Component`. Mixing multiple copies of React in a single project is bad for several reasons but historically we’ve tried to avoid issues when possible. (With Hooks, we [might need to](https://github.com/facebook/react/issues/13991) force deduplication though.) 507 + 508 + One other possible heuristic could be to check for presence of a `render` method on the prototype. However, at the time it [wasn’t clear](https://github.com/facebook/react/issues/4599#issuecomment-129714112) how the component API would evolve. Every check has a cost so we wouldn’t want to add more than one. This would also not work if `render` was defined as an instance method, such as with the class property syntax. 509 + 510 + So instead, React [added](https://github.com/facebook/react/pull/4663) a special flag to the base component. React checks for the presence of that flag, and that’s how it knows whether something is a React component class or not. 511 + 512 + Originally the flag was on the base `React.Component` class itself: 513 + 514 + ```jsx 515 + // Inside React 516 + class Component {} 517 + Component.isReactClass = {}; 518 + 519 + // We can check it like this 520 + class Greeting extends Component {} 521 + console.log(Greeting.isReactClass); // ✅ Yes 522 + ``` 523 + 524 + However, some class implementations we wanted to target [did not](https://github.com/scala-js/scala-js/issues/1900) copy static properties (or set the non-standard `__proto__`), so the flag was getting lost. 525 + 526 + This is why React [moved](https://github.com/facebook/react/pull/5021) this flag to `React.Component.prototype`: 527 + 528 + ```jsx 529 + // Inside React 530 + class Component {} 531 + Component.prototype.isReactComponent = {}; 532 + 533 + // We can check it like this 534 + class Greeting extends Component {} 535 + console.log(Greeting.prototype.isReactComponent); // ✅ Yes 536 + ``` 537 + 538 + **And this is literally all there is to it.** 539 + 540 + You might be wondering why it’s an object and not just a boolean. It doesn’t matter much in practice but early versions of Jest (before Jest was Good™️) had automocking turned on by default. The generated mocks omitted primitive properties, [breaking the check](https://github.com/facebook/react/pull/4663#issuecomment-136533373). Thanks, Jest. 541 + 542 + The `isReactComponent` check is [used in React](https://github.com/facebook/react/blob/769b1f270e1251d9dbdce0fcbd9e92e502d059b8/packages/react-reconciler/src/ReactFiber.js#L297-L300) to this day. 543 + 544 + If you don’t extend `React.Component`, React won’t find `isReactComponent` on the prototype, and won’t treat component as a class. Now you know why [the most upvoted answer](https://stackoverflow.com/a/42680526/458193) for `Cannot call a class as a function` error is to add `extends React.Component`. Finally, a [warning was added](https://github.com/facebook/react/pull/11168) that warns when `prototype.render` exists but `prototype.isReactComponent` doesn’t. 545 + 546 + --- 547 + 548 + You might say this story is a bit of a bait-and-switch. **The actual solution is really simple, but I went on a huge tangent to explain *why* React ended up with this solution, and what the alternatives were.** 549 + 550 + In my experience, that’s often the case with library APIs. For an API to be simple to use, you often need to consider the language semantics (possibly, for several languages, including future directions), runtime performance, ergonomics with and without compile-time steps, the state of the ecosystem and packaging solutions, early warnings, and many other things. The end result might not always be the most elegant, but it must be practical. 551 + 552 + **If the final API is successful, _its users_ never have to think about this process.** Instead they can focus on creating apps. 553 + 554 + But if you’re also curious... it’s nice to know how it works.
+198
public/how-does-setstate-know-what-to-do/index.md
··· 1 + --- 2 + title: How Does setState Know What to Do? 3 + date: '2018-12-09' 4 + spoiler: Dependency injection is nice if you don’t have to think about it. 5 + cta: 'react' 6 + --- 7 + 8 + When you call `setState` in a component, what do you think happens? 9 + 10 + ```jsx{11} 11 + import React from 'react'; 12 + import ReactDOM from 'react-dom'; 13 + 14 + class Button extends React.Component { 15 + constructor(props) { 16 + super(props); 17 + this.state = { clicked: false }; 18 + this.handleClick = this.handleClick.bind(this); 19 + } 20 + handleClick() { 21 + this.setState({ clicked: true }); 22 + } 23 + render() { 24 + if (this.state.clicked) { 25 + return <h1>Thanks</h1>; 26 + } 27 + return ( 28 + <button onClick={this.handleClick}> 29 + Click me! 30 + </button> 31 + ); 32 + } 33 + } 34 + 35 + ReactDOM.render(<Button />, document.getElementById('container')); 36 + ``` 37 + 38 + Sure, React re-renders the component with the next `{ clicked: true }` state and updates the DOM to match the returned `<h1>Thanks</h1>` element. 39 + 40 + Seems straightforward. But wait, does *React* do it? Or *React DOM*? 41 + 42 + Updating the DOM sounds like something React DOM would be responsible for. But we’re calling `this.setState()`, not something from React DOM. And our `React.Component` base class is defined inside React itself. 43 + 44 + So how can `setState()` inside `React.Component` update the DOM? 45 + 46 + **Disclaimer: Just like [most](/why-do-react-elements-have-typeof-property/) [other](/how-does-react-tell-a-class-from-a-function/) [posts](/why-do-we-write-super-props/) on this blog, you don’t actually *need* to know any of that to be productive with React. This post is for those who like to see what’s behind the curtain. Completely optional!** 47 + 48 + --- 49 + 50 + We might think that the `React.Component` class contains DOM update logic. 51 + 52 + But if that were the case, how can `this.setState()` work in other environments? For example, components in React Native apps also extend `React.Component`. They call `this.setState()` just like we did above, and yet React Native works with Android and iOS native views instead of the DOM. 53 + 54 + You might also be familiar with React Test Renderer or Shallow Renderer. Both of these testing strategies let you render normal components and call `this.setState()` inside them. But neither of them works with the DOM. 55 + 56 + If you used renderers like [React ART](https://github.com/facebook/react/tree/master/packages/react-art), you might also know that it’s possible to use more than one renderer on the page. (For example, ART components work inside a React DOM tree.) This makes a global flag or variable untenable. 57 + 58 + So somehow **`React.Component` delegates handling state updates to the platform-specific code.** Before we can understand how this happens, let’s dig deeper into how packages are separated and why. 59 + 60 + --- 61 + 62 + There is a common misconception that the React “engine” lives inside the `react` package. This is not true. 63 + 64 + In fact, ever since the [package split in React 0.14](https://reactjs.org/blog/2015/07/03/react-v0.14-beta-1.html#two-packages), the `react` package intentionally only exposes APIs for *defining* components. Most of the *implementation* of React lives in the “renderers”. 65 + 66 + `react-dom`, `react-dom/server`, `react-native`, `react-test-renderer`, `react-art` are some examples of renderers (and you can [build your own](https://github.com/facebook/react/blob/master/packages/react-reconciler/README.md#practical-examples)). 67 + 68 + This is why the `react` package is useful regardless of which platform you target. All its exports, such as `React.Component`, `React.createElement`, `React.Children` utilities and (eventually) [Hooks](https://reactjs.org/docs/hooks-intro.html), are independent of the target platform. Whether you run React DOM, React DOM Server, or React Native, your components would import and use them in the same way. 69 + 70 + In contrast, the renderer packages expose platform-specific APIs like `ReactDOM.render()` that let you mount a React hierarchy into a DOM node. Each renderer provides an API like this. Ideally, most *components* shouldn’t need to import anything from a renderer. This keeps them more portable. 71 + 72 + **What most people imagine as the React “engine” is inside each individual renderer.** Many renderers include a copy of the same code — we call it the [“reconciler”](https://github.com/facebook/react/tree/master/packages/react-reconciler). A [build step](https://reactjs.org/blog/2017/12/15/improving-the-repository-infrastructure.html#migrating-to-google-closure-compiler) smooshes the reconciler code together with the renderer code into a single highly optimized bundle for better performance. (Copying code is usually not great for bundle size but the vast majority of React users only needs one renderer at a time, such as `react-dom`.) 73 + 74 + The takeaway here is that the `react` package only lets you *use* React features but doesn’t know anything about *how* they’re implemented. The renderer packages (`react-dom`, `react-native`, etc) provide the implementation of React features and platform-specific logic. Some of that code is shared (“reconciler”) but that’s an implementation detail of individual renderers. 75 + 76 + --- 77 + 78 + Now we know why *both* `react` and `react-dom` packages need to be updated for new features. For example, when React 16.3 added the Context API, `React.createContext()` was exposed on the React package. 79 + 80 + But `React.createContext()` doesn’t actually *implement* the context feature. The implementation would need to be different between React DOM and React DOM Server, for example. So `createContext()` returns a few plain objects: 81 + 82 + ```jsx 83 + // A bit simplified 84 + function createContext(defaultValue) { 85 + let context = { 86 + _currentValue: defaultValue, 87 + Provider: null, 88 + Consumer: null 89 + }; 90 + context.Provider = { 91 + $$typeof: Symbol.for('react.provider'), 92 + _context: context 93 + }; 94 + context.Consumer = { 95 + $$typeof: Symbol.for('react.context'), 96 + _context: context, 97 + }; 98 + return context; 99 + } 100 + ``` 101 + 102 + When you use `<MyContext.Provider>` or `<MyContext.Consumer>` in the code, it’s the *renderer* that decides how to handle them. React DOM might track context values in one way, but React DOM Server might do it differently. 103 + 104 + **So if you update `react` to 16.3+ but don’t update `react-dom`, you’d be using a renderer that isn’t yet aware of the special `Provider` and `Consumer` types.** This is why an older `react-dom` would [fail saying these types are invalid](https://stackoverflow.com/a/49677020/458193). 105 + 106 + The same caveat applies to React Native. However, unlike React DOM, a React release doesn’t immediately “force” a React Native release. They have an independent release schedule. The updated renderer code is [separately synced](https://github.com/facebook/react-native/commits/master/Libraries/Renderer/oss) into the React Native repository once in a few weeks. This is why features become available in React Native on a different schedule than in React DOM. 107 + 108 + --- 109 + 110 + Okay, so now we know that the `react` package doesn’t contain anything interesting, and the implementation lives in renderers like `react-dom`, `react-native`, and so on. But that doesn’t answer our question. How does `setState()` inside `React.Component` “talk” to the right renderer? 111 + 112 + **The answer is that every renderer sets a special field on the created class.** This field is called `updater`. It’s not something *you* would set — rather, it’s something React DOM, React DOM Server or React Native set right after creating an instance of your class: 113 + 114 + 115 + ```jsx{4,9,14} 116 + // Inside React DOM 117 + const inst = new YourComponent(); 118 + inst.props = props; 119 + inst.updater = ReactDOMUpdater; 120 + 121 + // Inside React DOM Server 122 + const inst = new YourComponent(); 123 + inst.props = props; 124 + inst.updater = ReactDOMServerUpdater; 125 + 126 + // Inside React Native 127 + const inst = new YourComponent(); 128 + inst.props = props; 129 + inst.updater = ReactNativeUpdater; 130 + ``` 131 + 132 + Looking at the [`setState` implementation in `React.Component`](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react/src/ReactBaseClasses.js#L58-L67), all it does is delegate work to the renderer that created this component instance: 133 + 134 + ```jsx 135 + // A bit simplified 136 + setState(partialState, callback) { 137 + // Use the `updater` field to talk back to the renderer! 138 + this.updater.enqueueSetState(this, partialState, callback); 139 + } 140 + ``` 141 + 142 + React DOM Server [might want to](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-dom/src/server/ReactPartialRenderer.js#L442-L448) ignore a state update and warn you, whereas React DOM and React Native would let their copies of the reconciler [handle it](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-reconciler/src/ReactFiberClassComponent.js#L190-L207). 143 + 144 + And this is how `this.setState()` can update the DOM even though it’s defined in the React package. It reads `this.updater` which was set by React DOM, and lets React DOM schedule and handle the update. 145 + 146 + --- 147 + 148 + We know about classes now, but what about Hooks? 149 + 150 + When people first look at the [Hooks proposal API](https://reactjs.org/docs/hooks-intro.html), they often wonder: how does `useState` “know what to do”? The assumption is that it’s more “magical” than a base `React.Component` class with `this.setState()`. 151 + 152 + But as we have seen today, the base class `setState()` implementation has been an illusion all along. It doesn’t do anything except forwarding the call to the current renderer. And `useState` Hook [does exactly the same thing](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react/src/ReactHooks.js#L55-L56). 153 + 154 + **Instead of an `updater` field, Hooks use a “dispatcher” object.** When you call `React.useState()`, `React.useEffect()`, or another built-in Hook, these calls are forwarded to the current dispatcher. 155 + 156 + ```jsx 157 + // In React (simplified a bit) 158 + const React = { 159 + // Real property is hidden a bit deeper, see if you can find it! 160 + __currentDispatcher: null, 161 + 162 + useState(initialState) { 163 + return React.__currentDispatcher.useState(initialState); 164 + }, 165 + 166 + useEffect(initialState) { 167 + return React.__currentDispatcher.useEffect(initialState); 168 + }, 169 + // ... 170 + }; 171 + ``` 172 + 173 + And individual renderers set the dispatcher before rendering your component: 174 + 175 + ```jsx{3,8-9} 176 + // In React DOM 177 + const prevDispatcher = React.__currentDispatcher; 178 + React.__currentDispatcher = ReactDOMDispatcher; 179 + let result; 180 + try { 181 + result = YourComponent(props); 182 + } finally { 183 + // Restore it back 184 + React.__currentDispatcher = prevDispatcher; 185 + } 186 + ``` 187 + 188 + For example, the React DOM Server implementation is [here](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-dom/src/server/ReactPartialRendererHooks.js#L340-L354), and the reconciler implementation shared by React DOM and React Native is [here](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-reconciler/src/ReactFiberHooks.js). 189 + 190 + This is why a renderer such as `react-dom` needs to access the same `react` package that you call Hooks from. Otherwise, your component won’t “see” the dispatcher! This may not work when you have [multiple copies of React](https://github.com/facebook/react/issues/13991) in the same component tree. However, this has always led to obscure bugs so Hooks force you to solve the package duplication before it costs you. 191 + 192 + While we don’t encourage this, you can technically override the dispatcher yourself for advanced tooling use cases. (I lied about `__currentDispatcher` name but you can find the real one in the React repo.) For example, React DevTools will use [a special purpose-built dispatcher](https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-debug-tools/src/ReactDebugHooks.js#L203-L214) to introspect the Hooks tree by capturing JavaScript stack traces. *Don’t repeat this at home.* 193 + 194 + This also means Hooks aren’t inherently tied to React. If in the future more libraries want to reuse the same primitive Hooks, in theory the dispatcher could move to a separate package and be exposed as a first-class API with a less “scary” name. In practice, we’d prefer to avoid premature abstraction until there is a need for it. 195 + 196 + Both the `updater` field and the `__currentDispatcher` object are forms of a generic programming principle called *dependency injection*. In both cases, the renderers “inject” implementations of features like `setState` into the generic React package to keep your components more declarative. 197 + 198 + You don’t need to think about how this works when you use React. We’d like React users to spend more time thinking about their application code than abstract concepts like dependency injection. But if you’ve ever wondered how `this.setState()` or `useState()` know what to do, I hope this helps.
public/how-does-the-development-mode-work/devmode.png

This is a binary file and will not be displayed.

+224
public/how-does-the-development-mode-work/index.md
··· 1 + --- 2 + title: How Does the Development Mode Work? 3 + date: '2019-08-04' 4 + spoiler: Dead code elimination by convention. 5 + cta: 'react' 6 + --- 7 + 8 + If your JavaScript codebase is even moderately complex, **you probably have a way to bundle and run different code in development and production**. 9 + 10 + Bundling and running different code in development and production is powerful. In development mode, React includes many warnings that help you find problems before they lead to bugs. However, the code necessary to detect such mistakes often increases the bundle size and makes the app run slower. 11 + 12 + The slowdown is acceptable in development. In fact, running the code slower in development *might even be beneficial* because it partially compensates for the discrepancy between fast developer machines and an average consumer device. 13 + 14 + In production we don’t want to pay any of that cost. Hence, we omit these checks in production. How does that work? Let’s take a look. 15 + 16 + --- 17 + 18 + 19 + The exact way to run different code in development depends on your JavaScript build pipeline (and whether you have one). At Facebook it looks like this: 20 + 21 + ```jsx 22 + if (__DEV__) { 23 + doSomethingDev(); 24 + } else { 25 + doSomethingProd(); 26 + } 27 + ``` 28 + 29 + Here, `__DEV__` isn’t a real variable. It’s a constant that gets substituted when the modules are stitched together for the browser. The result looks like this: 30 + 31 + ```jsx 32 + // In development: 33 + if (true) { 34 + doSomethingDev(); // 👈 35 + } else { 36 + doSomethingProd(); 37 + } 38 + 39 + // In production: 40 + if (false) { 41 + doSomethingDev(); 42 + } else { 43 + doSomethingProd(); // 👈 44 + } 45 + ``` 46 + 47 + 48 + In production, you’d also run a minifier (for example, [terser](https://github.com/terser-js/terser)) on the code. Most JavaScript minifiers do a limited form of [dead code elimination](https://en.wikipedia.org/wiki/Dead_code_elimination), such as removing `if (false)` branches. So in production you’d only see: 49 + 50 + ```jsx 51 + // In production (after minification): 52 + doSomethingProd(); 53 + ``` 54 + 55 + *(Note that there are significant limits on how effective dead code elimination can be with mainstream JavaScript tools, but that’s a separate topic.)* 56 + 57 + While you might not be using a `__DEV__` magic constant, if you use a popular JavaScript bundler like webpack, there’s probably some other convention you can follow. For example, it’s common to express the same pattern like this: 58 + 59 + ```jsx 60 + if (process.env.NODE_ENV !== 'production') { 61 + doSomethingDev(); 62 + } else { 63 + doSomethingProd(); 64 + } 65 + ``` 66 + 67 + **That’s exactly the pattern used by libraries like [React](https://reactjs.org/docs/optimizing-performance.html#use-the-production-build) and [Vue](https://vuejs.org/v2/guide/deployment.html#Turn-on-Production-Mode) when you import them from npm using a bundler.** (Single-file `<script>` tag builds offer development and production versions as separate `.js` and `.min.js` files.) 68 + 69 + This particular convention originally comes from Node.js. In Node.js, there is a global `process` variable that exposes your system’s environment variables as properties on the [`process.env`](https://nodejs.org/dist/latest-v8.x/docs/api/process.html#process_process_env) object. However, when you see this pattern in a front-end codebase, there isn’t usually any real `process` variable involved. 🤯 70 + 71 + Instead, the whole `process.env.NODE_ENV` expression gets substituted by a string literal at the build time, just like our magic `__DEV__` variable: 72 + 73 + ```jsx 74 + // In development: 75 + if ('development' !== 'production') { // true 76 + doSomethingDev(); // 👈 77 + } else { 78 + doSomethingProd(); 79 + } 80 + 81 + // In production: 82 + if ('production' !== 'production') { // false 83 + doSomethingDev(); 84 + } else { 85 + doSomethingProd(); // 👈 86 + } 87 + ``` 88 + 89 + Because the whole expression is constant (`'production' !== 'production'` is guaranteed to be `false`), a minifier can also remove the other branch. 90 + 91 + ```jsx 92 + // In production (after minification): 93 + doSomethingProd(); 94 + ``` 95 + 96 + Mischief managed. 97 + 98 + --- 99 + 100 + Note that this **wouldn’t work** with more complex expressions: 101 + 102 + ```jsx 103 + let mode = 'production'; 104 + if (mode !== 'production') { 105 + // 🔴 not guaranteed to be eliminated 106 + } 107 + ``` 108 + 109 + JavaScript static analysis tools are not very smart due to the dynamic nature of the language. When they see variables like `mode` rather than static expressions like `false` or `'production' !== 'production'`, they often give up. 110 + 111 + Similarly, dead code elimination in JavaScript often doesn’t work well across the module boundaries when you use the top-level `import` statements: 112 + 113 + ```jsx 114 + // 🔴 not guaranteed to be eliminated 115 + import {someFunc} from 'some-module'; 116 + 117 + if (false) { 118 + someFunc(); 119 + } 120 + ``` 121 + 122 + So you need to write code in a very mechanical way that makes the condition *definitely static*, and ensure that *all code* you want to eliminate is inside of it. 123 + 124 + --- 125 + 126 + For all of this to work, your bundler needs to do the `process.env.NODE_ENV` replacement, and needs to know in which mode you *want* to build the project in. 127 + 128 + A few years ago, it used to be common to forget to configure the environment. You’d often see a project in development mode deployed to production. 129 + 130 + That’s bad because it makes the website load and run slower. 131 + 132 + In the last two years, the situation has significantly improved. For example, webpack added a simple `mode` option instead of manually configuring the `process.env.NODE_ENV` replacement. React DevTools also now displays a red icon on sites with development mode, making it easy to spot and even [report](https://mobile.twitter.com/BestBuySupport/status/1027195363713736704). 133 + 134 + ![Development mode warning in React DevTools](devmode.png) 135 + 136 + Opinionated setups like Create React App, Next/Nuxt, Vue CLI, Gatsby, and others make it even harder to mess up by separating the development builds and production builds into two separate commands. (For example, `npm start` and `npm run build`.) Typically, only a production build can be deployed, so the developer can’t make this mistake anymore. 137 + 138 + There is always an argument that maybe the *production* mode needs to be the default, and the development mode needs to be opt-in. Personally, I don’t find this argument convincing. People who benefit most from the development mode warnings are often new to the library. *They wouldn’t know to turn it on,* and would miss the many bugs that the warnings would have detected early. 139 + 140 + Yes, performance issues are bad. But so is shipping broken buggy experiences to the end users. For example, the [React key warning](https://reactjs.org/docs/lists-and-keys.html#keys) helps prevent bugs like sending a message to the wrong person or buying the wrong product. Developing with this warning disabled is a significant risk for you *and* your users. If it’s off by default, then by the time you find the toggle and turn it on, you’ll have too many warnings to clean up. So most people would toggle it back off. This is why it needs to be on from the start, rather than enabled later. 141 + 142 + Finally, even if development warnings were opt-in, and developers *knew* to turn them on early in development, we’d just go back to the original problem. Someone would accidentally leave them on when deploying to production! 143 + 144 + And we’re back to square one. 145 + 146 + Personally, I believe in **tools that display and use the right mode depending on whether you’re debugging or deploying**. Almost every other environment (whether mobile, desktop, or server) except the web browser has had a way to load and differentiate development and production builds for decades. 147 + 148 + Instead of libraries coming up with and relying on ad-hoc conventions, perhaps it’s time the JavaScript environments see this distinction as a first-class need. 149 + 150 + --- 151 + 152 + Enough with the philosophy! 153 + 154 + Let’s take another look at this code: 155 + 156 + ```jsx 157 + if (process.env.NODE_ENV !== 'production') { 158 + doSomethingDev(); 159 + } else { 160 + doSomethingProd(); 161 + } 162 + ``` 163 + 164 + You might be wondering: if there’s no real `process` object in front-end code, why do libraries like React and Vue rely on it in the npm builds? 165 + 166 + *(To clarify this again: the `<script>` tags you can load in the browser, offered by both React and Vue, don’t rely on this. Instead you have to manually pick between the development `.js` and the production `.min.js` files. The section below is only about using React or Vue with a bundler by `import`ing them from npm.)* 167 + 168 + Like many things in programming, this particular convention has mostly historical reasons. We are still using it because now it’s widely adopted by different tools. Switching to something else is costly and doesn’t buy much. 169 + 170 + So what’s the history behind it? 171 + 172 + Many years before the `import` and `export` syntax was standardized, there were several competing ways to express relationships between modules. Node.js popularized `require()` and `module.exports`, known as [CommonJS](https://en.wikipedia.org/wiki/CommonJS). 173 + 174 + Code published on the npm registry early on was written for Node.js. [Express](https://expressjs.com) was (and probably still is?) the most popular server-side framework for Node.js, and it [used the `NODE_ENV` environment variable](https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production) to enable production mode. Some other npm packages adopted the same convention. 175 + 176 + Early JavaScript bundlers like browserify wanted to make it possible to use code from npm in front-end projects. (Yes, [back then](https://blog.npmjs.org/post/101775448305/npm-and-front-end-packaging) almost nobody used npm for front-end! Can you imagine?) So they extended the same convention already present in the Node.js ecosystem to the front-end code. 177 + 178 + The original “envify” transform was [released in 2013](https://github.com/hughsk/envify/commit/ae8aa26b759cd2115eccbed96f70e7bbdceded97). React was open sourced around that time, and npm with browserify seemed like the best solution for bundling front-end CommonJS code during that era. 179 + 180 + React started providing npm builds (in addition to `<script>` tag builds) from the very beginning. As React got popular, so did the practice of writing modular JavaScript with CommonJS modules and shipping front-end code via npm. 181 + 182 + React needed to remove development-only code in the production mode. Browserify already offered a solution to this problem, so React also adopted the convention of using `process.env.NODE_ENV` for its npm builds. With time, many other tools and libraries, including webpack and Vue, did the same. 183 + 184 + By 2019, browserify has lost quite a bit of mindshare. However, replacing `process.env.NODE_ENV` with `'development'` or `'production'` during a build step is a convention that is as popular as ever. 185 + 186 + *(It would be interesting to see how adoption of ES Modules as a distribution format, rather than just the authoring format, changes the equation. Tell me on Twitter?)* 187 + 188 + --- 189 + 190 + One thing that might still confuse you is that in React *source code* on GitHub, you’ll see `__DEV__` being used as a magic variable. But in the React code on npm, it uses `process.env.NODE_ENV`. How does that work? 191 + 192 + Historically, we’ve used `__DEV__` in the source code to match the Facebook source code. For a long time, React was directly copied into the Facebook codebase, so it needed to follow the same rules. For npm, we had a build step that literally replaced the `__DEV__` checks with `process.env.NODE_ENV !== 'production'` right before publishing. 193 + 194 + This was sometimes a problem. Sometimes, a code pattern relying on some Node.js convention worked well on npm, but broke Facebook, or vice versa. 195 + 196 + Since React 16, we’ve changed the approach. Instead, we now [compile a bundle](https://reactjs.org/blog/2017/12/15/improving-the-repository-infrastructure.html#compiling-flat-bundles) for each environment (including `<script>` tags, npm, and the Facebook internal codebase). So even CommonJS code for npm is compiled to separate development and production bundles ahead of time. 197 + 198 + This means that while the React source code says `if (__DEV__)`, we actually produce *two* bundles for every package. One is already precompiled with `__DEV__ = true` and another is precompiled with `__DEV__ = false`. The entry point for each package on npm “decides” which one to export. 199 + 200 + [For example:](https://unpkg.com/browse/react@16.8.6/index.js) 201 + 202 + ```jsx 203 + if (process.env.NODE_ENV === 'production') { 204 + module.exports = require('./cjs/react.production.min.js'); 205 + } else { 206 + module.exports = require('./cjs/react.development.js'); 207 + } 208 + ``` 209 + 210 + And that’s the only place where your bundler will interpolate either `'development'` or `'production'` as a string, and where your minifier will get rid of the development-only `require`. 211 + 212 + Both `react.production.min.js` and `react.development.js` don’t have any `process.env.NODE_ENV` checks anymore. This is great because *when actually running on Node.js*, accessing `process.env` is [somewhat slow](https://reactjs.org/blog/2017/09/26/react-v16.0.html#better-server-side-rendering). Compiling bundles in both modes ahead of time also lets us optimize the file size [much more consistently](https://reactjs.org/blog/2017/09/26/react-v16.0.html#reduced-file-size), regardless of which bundler or minifier you are using. 213 + 214 + And that’s how it really works! 215 + 216 + --- 217 + 218 + I wish there was a more first-class way to do it without relying on conventions, but here we are. It would be great if modes were a first-class concept in all JavaScript environments, and if there was some way for a browser to surface that some code is running in a development mode when it’s not supposed to. 219 + 220 + On the other hand, it is fascinating how a convention in a single project can propagate through the ecosystem. `EXPRESS_ENV` [became `NODE_ENV`](https://github.com/expressjs/express/commit/03b56d8140dc5c2b574d410bfeb63517a0430451) in 2010 and [spread to front-end](https://github.com/hughsk/envify/commit/ae8aa26b759cd2115eccbed96f70e7bbdceded97) in 2013. Maybe the solution isn’t perfect, but for each project the cost of adopting it was lower than the cost of convincing everyone else to do something different. This teaches a valuable lesson about the top-down versus bottom-up adoption. Understanding how this dynamic plays out distinguishes successful standardization attempts from failures. 221 + 222 + Separating development and production modes is a very useful technique. I recommend using it in your libraries and the application code for the kinds of checks that are too expensive to do in production, but are valuable (and often critical!) to do in development. 223 + 224 + As with any powerful feature, there are some ways you can misuse it. This will be the topic of my next post!
public/making-setinterval-declarative-with-react-hooks/counter_delay.gif

This is a binary file and will not be displayed.

public/making-setinterval-declarative-with-react-hooks/counter_inception.gif

This is a binary file and will not be displayed.

+666
public/making-setinterval-declarative-with-react-hooks/index.md
··· 1 + --- 2 + title: Making setInterval Declarative with React Hooks 3 + date: '2019-02-04' 4 + spoiler: How I learned to stop worrying and love refs. 5 + --- 6 + 7 + If you played with [React Hooks](https://reactjs.org/docs/hooks-intro.html) for more than a few hours, you probably ran into an intriguing problem: using `setInterval` just [doesn’t work](https://stackoverflow.com/questions/53024496/state-not-updating-when-using-react-state-hook-within-setinterval) as you’d expect. 8 + 9 + In the [words](https://mobile.twitter.com/ryanflorence/status/1088606583637061634) of Ryan Florence: 10 + 11 + >I’ve had a lot of people point to setInterval with hooks as some sort of egg on React’s face 12 + 13 + Honestly, I think these people have a point. It *is* confusing at first. 14 + 15 + But I’ve also come to see it not as a flaw of Hooks but as a mismatch between the [React programming model](/react-as-a-ui-runtime/) and `setInterval`. Hooks, being closer to the React programming model than classes, make that mismatch more prominent. 16 + 17 + **There _is_ a way to get them working together very well but it’s a bit unintuitive.** 18 + 19 + In this post, we’ll look at _how_ to make intervals and Hooks play well together, _why_ this solution makes sense, and which *new* capabilities it can give you. 20 + 21 + ----- 22 + 23 + **Disclaimer: this post focuses on a _pathological case_. Even if an API simplifies a hundred use cases, the discussion will always focus on the one that got harder.** 24 + 25 + If you’re new to Hooks and don’t understand what the fuss is about, check out [this introduction](https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889) and the [documentation](https://reactjs.org/docs/hooks-intro.html) instead. This post assumes that you worked with Hooks for more than an hour. 26 + 27 + --- 28 + 29 + ## Just Show Me the Code 30 + 31 + Without further ado, here’s a counter that increments every second: 32 + 33 + ```jsx{6-9} 34 + import React, { useState, useEffect, useRef } from 'react'; 35 + 36 + function Counter() { 37 + let [count, setCount] = useState(0); 38 + 39 + useInterval(() => { 40 + // Your custom logic here 41 + setCount(count + 1); 42 + }, 1000); 43 + 44 + return <h1>{count}</h1>; 45 + } 46 + ``` 47 + 48 + *(Here’s a [CodeSandbox demo](https://codesandbox.io/s/105x531vkq).)* 49 + 50 + This `useInterval` isn’t a built-in React Hook; it’s a [custom Hook](https://reactjs.org/docs/hooks-custom.html) that I wrote: 51 + 52 + ```jsx 53 + import React, { useState, useEffect, useRef } from 'react'; 54 + 55 + function useInterval(callback, delay) { 56 + const savedCallback = useRef(); 57 + 58 + // Remember the latest callback. 59 + useEffect(() => { 60 + savedCallback.current = callback; 61 + }, [callback]); 62 + 63 + // Set up the interval. 64 + useEffect(() => { 65 + function tick() { 66 + savedCallback.current(); 67 + } 68 + if (delay !== null) { 69 + let id = setInterval(tick, delay); 70 + return () => clearInterval(id); 71 + } 72 + }, [delay]); 73 + } 74 + ``` 75 + 76 + *(Here’s a [CodeSandbox demo](https://codesandbox.io/s/105x531vkq) in case you missed it earlier.)* 77 + 78 + **My `useInterval` Hook sets up an interval and clears it after unmounting.** It’s a combo of `setInterval` and `clearInterval` tied to the component lifecycle. 79 + 80 + Feel free to copy paste it in your project or put it on npm. 81 + 82 + **If you don’t care how this works, you can stop reading now! The rest of the blog post is for folks who are ready to take a deep dive into React Hooks.** 83 + 84 + --- 85 + 86 + ## Wait What?! 🤔 87 + 88 + I know what you’re thinking: 89 + 90 + >Dan, this code doesn’t make any sense. What happened to “Just JavaScript”? Admit that React has jumped the shark with Hooks! 91 + 92 + **I thought this too but I changed my mind, and I’m going to change yours.** Before explaining why this code makes sense, I want to show off what it can do. 93 + 94 + --- 95 + 96 + ## Why `useInterval()` Is a Better API 97 + 98 + 99 + To remind you, my `useInterval` Hook accepts a function and a delay: 100 + 101 + ```jsx 102 + useInterval(() => { 103 + // ... 104 + }, 1000); 105 + ``` 106 + 107 + This looks a lot like `setInterval`: 108 + 109 + ```jsx 110 + setInterval(() => { 111 + // ... 112 + }, 1000); 113 + ``` 114 + 115 + **So why not just use `setInterval` directly?** 116 + 117 + This may not be obvious at first, but the difference between the `setInterval` you know and my `useInterval` Hook is that **its arguments are “dynamic”**. 118 + 119 + I’ll illustrate this point with a concrete example. 120 + 121 + --- 122 + 123 + Let’s say we want the interval delay to be adjustable: 124 + 125 + ![Counter with an input that adjusts the interval delay](./counter_delay.gif) 126 + 127 + While you wouldn’t necessarily control the delay with an *input*, adjusting it dynamically can be useful — for example, to poll for some AJAX updates less often while the user has switched to a different tab. 128 + 129 + So how would you do this with `setInterval` in a class? I ended up with this: 130 + 131 + ```jsx{7-26} 132 + class Counter extends React.Component { 133 + state = { 134 + count: 0, 135 + delay: 1000, 136 + }; 137 + 138 + componentDidMount() { 139 + this.interval = setInterval(this.tick, this.state.delay); 140 + } 141 + 142 + componentDidUpdate(prevProps, prevState) { 143 + if (prevState.delay !== this.state.delay) { 144 + clearInterval(this.interval); 145 + this.interval = setInterval(this.tick, this.state.delay); 146 + } 147 + } 148 + 149 + componentWillUnmount() { 150 + clearInterval(this.interval); 151 + } 152 + 153 + tick = () => { 154 + this.setState({ 155 + count: this.state.count + 1 156 + }); 157 + } 158 + 159 + handleDelayChange = (e) => { 160 + this.setState({ delay: Number(e.target.value) }); 161 + } 162 + 163 + render() { 164 + return ( 165 + <> 166 + <h1>{this.state.count}</h1> 167 + <input value={this.state.delay} onChange={this.handleDelayChange} /> 168 + </> 169 + ); 170 + } 171 + } 172 + ``` 173 + 174 + *(Here’s a [CodeSandbox demo](https://codesandbox.io/s/mz20m600mp).)* 175 + 176 + This is not too bad! 177 + 178 + What’s the Hook version looking like? 179 + 180 + <font size="50">🥁🥁🥁</font> 181 + 182 + ```jsx{5-8} 183 + function Counter() { 184 + let [count, setCount] = useState(0); 185 + let [delay, setDelay] = useState(1000); 186 + 187 + useInterval(() => { 188 + // Your custom logic here 189 + setCount(count + 1); 190 + }, delay); 191 + 192 + function handleDelayChange(e) { 193 + setDelay(Number(e.target.value)); 194 + } 195 + 196 + return ( 197 + <> 198 + <h1>{count}</h1> 199 + <input value={delay} onChange={handleDelayChange} /> 200 + </> 201 + ); 202 + } 203 + ``` 204 + 205 + *(Here’s a [CodeSandbox demo](https://codesandbox.io/s/329jy81rlm).)* 206 + 207 + Yeah, *that’s all it takes*. 208 + 209 + Unlike the class version, there is no complexity gap for “upgrading” the `useInterval` Hook example to have a dynamically adjusted delay: 210 + 211 + ```jsx{4,9} 212 + // Constant delay 213 + useInterval(() => { 214 + setCount(count + 1); 215 + }, 1000); 216 + 217 + // Adjustable delay 218 + useInterval(() => { 219 + setCount(count + 1); 220 + }, delay); 221 + ``` 222 + 223 + When `useInterval` Hook sees a different delay, it sets up the interval again. 224 + 225 + **Instead of writing code to *set* and *clear* the interval, I can *declare* an interval with a particular delay — and our `useInterval` Hook makes it happen.** 226 + 227 + What if I want to temporarily *pause* my interval? I can do this with state too: 228 + 229 + ```jsx{6} 230 + const [delay, setDelay] = useState(1000); 231 + const [isRunning, setIsRunning] = useState(true); 232 + 233 + useInterval(() => { 234 + setCount(count + 1); 235 + }, isRunning ? delay : null); 236 + ``` 237 + 238 + *(Here is a [demo](https://codesandbox.io/s/l240mp2pm7)!)* 239 + 240 + This is what gets me excited about Hooks and React all over again. We can wrap the existing imperative APIs and create declarative APIs expressing our intent more closely. Just like with rendering, we can **describe the process at all points in time simultaneously** instead of carefully issuing commands to manipulate it. 241 + 242 + --- 243 + 244 + I hope by this you’re sold on `useInterval()` Hook being a nicer API — at least when we’re doing it from a component. 245 + 246 + **But why is using `setInterval()` and `clearInterval()` annoying with Hooks?** Let’s go back to our counter example and try to implement it manually. 247 + 248 + --- 249 + 250 + ## First Attempt 251 + 252 + I’ll start with a simple example that just renders the initial state: 253 + 254 + ```jsx 255 + function Counter() { 256 + const [count, setCount] = useState(0); 257 + return <h1>{count}</h1>; 258 + } 259 + ``` 260 + 261 + Now I want an interval that increments it every second. It’s a [side effect that needs cleanup](https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup) so I’m going to `useEffect()` and return the cleanup function: 262 + 263 + ```jsx{4-9} 264 + function Counter() { 265 + let [count, setCount] = useState(0); 266 + 267 + useEffect(() => { 268 + let id = setInterval(() => { 269 + setCount(count + 1); 270 + }, 1000); 271 + return () => clearInterval(id); 272 + }); 273 + 274 + return <h1>{count}</h1>; 275 + } 276 + ``` 277 + 278 + *(See the [CodeSandbox demo](https://codesandbox.io/s/7wlxk1k87j).)* 279 + 280 + Seems easy enough? This kind of works. 281 + 282 + **However, this code has a strange behavior.** 283 + 284 + React by default re-applies effects after every render. This is intentional and helps avoid [a whole class of bugs](https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update) that are present in React class components. 285 + 286 + This is usually good because many subscription APIs can happily remove the old and add a new listener at any time. However, `setInterval` isn’t one of them. When we run `clearInterval` and `setInterval`, their timing shifts. If we re-render and re-apply effects too often, the interval never gets a chance to fire! 287 + 288 + We can see the bug by re-rendering our component within a *smaller* interval: 289 + 290 + ```jsx 291 + setInterval(() => { 292 + // Re-renders and re-applies Counter's effects 293 + // which in turn causes it to clearInterval() 294 + // and setInterval() before that interval fires. 295 + ReactDOM.render(<Counter />, rootElement); 296 + }, 100); 297 + ``` 298 + 299 + *(See a [demo](https://codesandbox.io/s/9j86r218y4) of this bug.)* 300 + 301 + --- 302 + 303 + ## Second Attempt 304 + 305 + You might know that `useEffect()` lets us [*opt out*](https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects) of re-applying effects. You can specify a dependency array as a second argument, and React will only re-run the effect if something in that array changes: 306 + 307 + ```jsx{3} 308 + useEffect(() => { 309 + document.title = `You clicked ${count} times`; 310 + }, [count]); 311 + ``` 312 + 313 + When we want to *only* run the effect on mount and cleanup on unmount, we can pass an empty `[]` array of dependencies. 314 + 315 + However, this is a common source of mistakes if you’re not very familiar with JavaScript closures. We’re going to make this mistake right now! (We’ve also built a [lint rule](https://www.npmjs.com/package/eslint-plugin-react-hooks) to surface these bugs early.) 316 + 317 + In the first attempt, our problem was that re-running the effects caused our timer to get cleared too early. We can try to fix it by never re-running them: 318 + 319 + ```jsx{9} 320 + function Counter() { 321 + let [count, setCount] = useState(0); 322 + 323 + useEffect(() => { 324 + let id = setInterval(() => { 325 + setCount(count + 1); 326 + }, 1000); 327 + return () => clearInterval(id); 328 + }, []); 329 + 330 + return <h1>{count}</h1>; 331 + } 332 + ``` 333 + 334 + However, now our counter updates to 1 and stays there. ([See the bug in action](https://codesandbox.io/s/jj0mk6y683).) 335 + 336 + What happened?! 337 + 338 + **The problem is that `useEffect` captures the `count` from the first render.** It is equal to `0`. We never re-apply the effect so the closure in `setInterval` always references the `count` from the first render, and `count + 1` is always `1`. Oops! 339 + 340 + **I can hear your teeth grinding. Hooks are so annoying, right?** 341 + 342 + [One way](https://codesandbox.io/s/j379jxrzjy) to fix it is to replace `setCount(count + 1)` with the “updater” form like `setCount(c => c + 1)`. It can always read fresh state for that variable. But this doesn’t help you read the fresh props, for example. 343 + 344 + [Another fix](https://codesandbox.io/s/00o9o95jyv) is to [`useReducer()`](https://reactjs.org/docs/hooks-reference.html#usereducer). This approach gives you more flexibility. Inside the reducer, you have the access both to current state and fresh props. The `dispatch` function itself never changes so you can pump data into it from any closure. One limitation of `useReducer()` is that you can’t yet emit side effects in it. (However, you could return new state — triggering some effect.) 345 + 346 + **But why is it getting so convoluted?** 347 + 348 + --- 349 + 350 + ## The Impedance Mismatch 351 + 352 + This term is sometimes thrown around, and [Phil Haack](https://haacked.com/archive/2004/06/15/impedance-mismatch.aspx/) explains it like this: 353 + 354 + >One might say Databases are from Mars and Objects are from Venus. Databases do not map naturally to object models. It’s a lot like trying to push the north poles of two magnets together. 355 + 356 + Our “impedance mismatch” is not between Databases and Objects. It is between the React programming model and the imperative `setInterval` API. 357 + 358 + **A React component may be mounted for a while and go through many different states, but its render result describes *all of them at once.*** 359 + 360 + ```jsx 361 + // Describes every render 362 + return <h1>{count}</h1> 363 + ``` 364 + 365 + Hooks let us apply the same declarative approach to effects: 366 + 367 + ```jsx{4} 368 + // Describes every interval state 369 + useInterval(() => { 370 + setCount(count + 1); 371 + }, isRunning ? delay : null); 372 + ``` 373 + 374 + We don’t *set* the interval, but specify *whether* it is set and with what delay. Our Hook makes it happen. A continuous process is described in discrete terms. 375 + 376 + **By contrast, `setInterval` does not describe a process in time — once you set the interval, you can’t change anything about it except clearing it.** 377 + 378 + That’s the mismatch between the React model and the `setInterval` API. 379 + 380 + --- 381 + 382 + Props and state of React components can change. React will re-render them and “forget” everything about the previous render result. It becomes irrelevant. 383 + 384 + The `useEffect()` Hook “forgets” the previous render too. It cleans up the last effect and sets up the next effect. The next effect closes over fresh props and state. This is why our [first attempt](https://codesandbox.io/s/7wlxk1k87j) worked for simple cases. 385 + 386 + **But `setInterval()` does not “forget”.** It will forever reference the old props and state until you replace it — which you can’t do without resetting the time. 387 + 388 + Or wait, can you? 389 + 390 + --- 391 + 392 + ## Refs to the Rescue! 393 + 394 + The problem boils down to this: 395 + 396 + * We do `setInterval(callback1, delay)` with `callback1` from first render. 397 + * We have `callback2` from next render that closes over fresh props and state. 398 + * But we can’t replace an already existing interval without resetting the time! 399 + 400 + **So what if we didn’t replace the interval at all, and instead introduced a mutable `savedCallback` variable pointing to the *latest* interval callback?** 401 + 402 + Now we can see the solution: 403 + 404 + * We `setInterval(fn, delay)` where `fn` calls `savedCallback`. 405 + * Set `savedCallback` to `callback1` after the first render. 406 + * Set `savedCallback` to `callback2` after the next render. 407 + * ??? 408 + * PROFIT 409 + 410 + This mutable `savedCallback` needs to “persist” across the re-renders. So it can’t be a regular variable. We want something more like an instance field. 411 + 412 + [As we can learn from the Hooks FAQ,](https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables) `useRef()` gives us exactly that: 413 + 414 + ```jsx 415 + const savedCallback = useRef(); 416 + // { current: null } 417 + ``` 418 + 419 + *(You might be familiar with [DOM refs](https://reactjs.org/docs/refs-and-the-dom.html) in React. Hooks use the same concept for holding any mutable values. A ref is like a “box” into which you can put anything.)* 420 + 421 + `useRef()` returns a plain object with a mutable `current` property that’s shared between renders. We can save the *latest* interval callback into it: 422 + 423 + ```jsx{8} 424 + function callback() { 425 + // Can read fresh props, state, etc. 426 + setCount(count + 1); 427 + } 428 + 429 + // After every render, save the latest callback into our ref. 430 + useEffect(() => { 431 + savedCallback.current = callback; 432 + }); 433 + ``` 434 + 435 + And then we can read and call it from inside our interval: 436 + 437 + ```jsx{3,8} 438 + useEffect(() => { 439 + function tick() { 440 + savedCallback.current(); 441 + } 442 + 443 + let id = setInterval(tick, 1000); 444 + return () => clearInterval(id); 445 + }, []); 446 + ``` 447 + 448 + Thanks to `[]`, our effect never re-executes, and the interval doesn’t get reset. However, thanks to the `savedCallback` ref, we can always read the callback that we set after the last render, and call it from the interval tick. 449 + 450 + Here’s a complete working solution: 451 + 452 + ```jsx{10,15} 453 + function Counter() { 454 + const [count, setCount] = useState(0); 455 + const savedCallback = useRef(); 456 + 457 + function callback() { 458 + setCount(count + 1); 459 + } 460 + 461 + useEffect(() => { 462 + savedCallback.current = callback; 463 + }); 464 + 465 + useEffect(() => { 466 + function tick() { 467 + savedCallback.current(); 468 + } 469 + 470 + let id = setInterval(tick, 1000); 471 + return () => clearInterval(id); 472 + }, []); 473 + 474 + return <h1>{count}</h1>; 475 + } 476 + ``` 477 + 478 + *(See the [CodeSandbox demo](https://codesandbox.io/s/3499qqr565).)* 479 + 480 + --- 481 + 482 + ## Extracting a Hook 483 + 484 + Admittedly, the above code can be disorienting. It’s mind-bending to mix the opposite paradigms. There’s also a potential to make a mess with mutable refs. 485 + 486 + **I think Hooks provide lower-level primitives than classes — but their beauty is that they enable us to compose and create better declarative abstractions.** 487 + 488 + Ideally, I just want to write this: 489 + 490 + ```jsx{4-6} 491 + function Counter() { 492 + const [count, setCount] = useState(0); 493 + 494 + useInterval(() => { 495 + setCount(count + 1); 496 + }, 1000); 497 + 498 + return <h1>{count}</h1>; 499 + } 500 + ``` 501 + 502 + I’ll copy and paste the body of my ref mechanism into a custom Hook: 503 + 504 + ```jsx 505 + function useInterval(callback) { 506 + const savedCallback = useRef(); 507 + 508 + useEffect(() => { 509 + savedCallback.current = callback; 510 + }); 511 + 512 + useEffect(() => { 513 + function tick() { 514 + savedCallback.current(); 515 + } 516 + 517 + let id = setInterval(tick, 1000); 518 + return () => clearInterval(id); 519 + }, []); 520 + } 521 + ``` 522 + 523 + Currently, the `1000` delay is hardcoded. I want to make it an argument: 524 + 525 + ```jsx 526 + function useInterval(callback, delay) { 527 + ``` 528 + 529 + I will use it when I set up the interval: 530 + 531 + ```jsx 532 + let id = setInterval(tick, delay); 533 + ``` 534 + 535 + Now that the `delay` can change between renders, I need to declare it in the dependencies of my interval effect: 536 + 537 + ```jsx{8} 538 + useEffect(() => { 539 + function tick() { 540 + savedCallback.current(); 541 + } 542 + 543 + let id = setInterval(tick, delay); 544 + return () => clearInterval(id); 545 + }, [delay]); 546 + ``` 547 + 548 + Wait, didn’t we want to avoid resetting the interval effect, and specifically passed `[]` to avoid it? Not quite. We only wanted to avoid resetting it when the *callback* changes. But when the `delay` changes, we *want* to restart the timer! 549 + 550 + Let’s check if our code works: 551 + 552 + ```jsx 553 + function Counter() { 554 + const [count, setCount] = useState(0); 555 + 556 + useInterval(() => { 557 + setCount(count + 1); 558 + }, 1000); 559 + 560 + return <h1>{count}</h1>; 561 + } 562 + 563 + function useInterval(callback, delay) { 564 + const savedCallback = useRef(); 565 + 566 + useEffect(() => { 567 + savedCallback.current = callback; 568 + }); 569 + 570 + useEffect(() => { 571 + function tick() { 572 + savedCallback.current(); 573 + } 574 + 575 + let id = setInterval(tick, delay); 576 + return () => clearInterval(id); 577 + }, [delay]); 578 + } 579 + ``` 580 + 581 + *(Try it on [CodeSandbox](https://codesandbox.io/s/xvyl15375w).)* 582 + 583 + It does! We can now `useInterval()` in any component and not think too much about its implementation details. 584 + 585 + ## Bonus: Pausing the Interval 586 + 587 + Say we want to be able to pause our interval by passing `null` as the `delay`: 588 + 589 + ```jsx{6} 590 + const [delay, setDelay] = useState(1000); 591 + const [isRunning, setIsRunning] = useState(true); 592 + 593 + useInterval(() => { 594 + setCount(count + 1); 595 + }, isRunning ? delay : null); 596 + ``` 597 + 598 + How do we implement this? The answer is: by not setting up an interval. 599 + 600 + ```jsx{6} 601 + useEffect(() => { 602 + function tick() { 603 + savedCallback.current(); 604 + } 605 + 606 + if (delay !== null) { 607 + let id = setInterval(tick, delay); 608 + return () => clearInterval(id); 609 + } 610 + }, [delay]); 611 + ``` 612 + 613 + *(See the [CodeSandbox demo](https://codesandbox.io/s/l240mp2pm7).)* 614 + 615 + That’s it. This code handles all possible transitions: a change of a delay, pausing, or resuming an interval. The `useEffect()` API asks us to spend more upfront effort to describe the setup and cleanup — but adding new cases is easy. 616 + 617 + ## Bonus: Fun Demo 618 + 619 + This `useInterval()` Hook is really fun to play with. When the side effects are declarative, it’s much easier to orchestrate complex behaviors together. 620 + 621 + **For example, we can have a `delay` of one interval be controlled by another:** 622 + 623 + ![Counter that automatically speeds up](./counter_inception.gif) 624 + 625 + ```jsx{10-15} 626 + function Counter() { 627 + const [delay, setDelay] = useState(1000); 628 + const [count, setCount] = useState(0); 629 + 630 + // Increment the counter. 631 + useInterval(() => { 632 + setCount(count + 1); 633 + }, delay); 634 + 635 + // Make it faster every second! 636 + useInterval(() => { 637 + if (delay > 10) { 638 + setDelay(delay / 2); 639 + } 640 + }, 1000); 641 + 642 + function handleReset() { 643 + setDelay(1000); 644 + } 645 + 646 + return ( 647 + <> 648 + <h1>Counter: {count}</h1> 649 + <h4>Delay: {delay}</h4> 650 + <button onClick={handleReset}> 651 + Reset delay 652 + </button> 653 + </> 654 + ); 655 + } 656 + ``` 657 + 658 + *(See the [CodeSandbox demo](https://codesandbox.io/s/znr418qp13)!)* 659 + 660 + ## Closing Thoughts 661 + 662 + Hooks take some getting used to — and *especially* at the boundary of imperative and declarative code. You can create powerful declarative abstractions with them like [React Spring](https://www.react-spring.io/docs/hooks/basics) but they can definitely get on your nerves sometimes. 663 + 664 + This is an early time for Hooks, and there are definitely still patterns we need to work out and compare. Don’t rush to adopt Hooks if you’re used to following well-known “best practices”. There’s still a lot to try and discover. 665 + 666 + I hope this post helps you understand the common pitfalls related to using APIs like `setInterval()` with Hooks, the patterns that can help you overcome them, and the sweet fruit of creating more expressive declarative APIs on top of them.
+245
public/my-decade-in-review/index.md
··· 1 + --- 2 + title: My Decade in Review 3 + date: '2020-01-01' 4 + spoiler: A personal reflection. 5 + --- 6 + 7 + I started this decade as a first-year college student fresh out of high school. I was 17, didn't have a job, didn't have any industry connections, and really didn't know shit. And now you're reading my blog! I would have been proud. 8 + 9 + I've told bits and pieces of my story on different podcasts. Now feels like an appropriate time to write down the parts that were most memorable to me. 10 + 11 + Every person's story is unique and not directly reproducible. I've benefited immensely from the privilege of being born in an upper middle class family and looking like a typical coder stereotype. People took chances on me. Still, I hope that sharing my story can be helpful to compare our experiences. Even if our circumstances are too different, at least you might find some of it entertaining. 12 + 13 + ## 2010 14 + 15 + I was born in Russia and I finished the high school there in 2009. In Russia, higher education is free if you do well enough at tests. I tried my chances with a few colleges. I was particularly hoping to get into one college whose students often won programming competitions (which I thought was cool at the time). 16 + 17 + However, it turned out my math exam scores weren't good enough. So there were not many options I could choose from that had to do with programming. From the remaining options, I picked a college that gave Macbooks to students. (Remember the white plastic ones with GarageBand? They were the best.) 18 + 19 + By the summer of 2010, I had just finished my first year there. It turned out that there wasn’t going to be much programming in the curriculum for two more years. But there was a lot of linear algebra, physics, and other subjects I didn't find particularly interesting. Everything was well in the beginning, but I started slacking off and skipping lectures that I had to wake up early for. My gaps in knowledge gradually snowballed, and most of what I remember from my first year in the university is the anxiety associated with feeling like a total failure. 20 + 21 + Even for subjects I knew well, things didn't quite go as I planned. Our English classes were very rudimentary, and I got a verbal approval from the teacher to skip most of them. But when I came for the final test, I wasn't *allowed* to turn it in unless I pay money for hours of "catch up training" with the same teacher. This experience left me resentful and suspicious of higher education. 22 + 23 + Aside from being a lousy student, I was also in my first serious relationship -- and it wasn't going very well either. I was unhappy, but I thought that you can solve this by continuing to be unhappy and "fixing" it. Unfortunately, I didn't have the wisdom to get out of a non-working relationship for a few more years. 24 + 25 + Now onto the bright side. Professionally, 2010 was an exciting year for me. I got my first job as a software developer! Here's how that happened. 26 + 27 + There was a small venue close to my college that hosted different events. This venue was a "business incubator" -- mind you, not a Silicon Valley kind of business incubator -- but a tiny Russian one. I have no idea what businesses they "incubated". However, they hosted a talk about software development, and I decided to check it out because I was starving for that kind of content. I didn't know any programmers in real life, and I didn't know meetups existed! 28 + 29 + I don't remember what the talk was about now. But I knew the person who gave it was an executive in a Russian-American outsourcing company. I've been programming since 12, so I approached him and asked if they're hiring. He gave me an email, I went through their test exercises, and in a few weeks got the job. 30 + 31 + I started at my first job during the summer of 2010. My salary was $18k/year (yes that’s 18 and not 180). This is peanuts in the developed world, but again, this was Russia -- so the rent was cheap too. I immediately moved out of my mom's apartment and started renting a room for $150 a month. I was excited. With my first salary, I bought an iPhone and marvelled at how good the UI was. 32 + 33 + Summer came and went, and then the second college year started. But it started without me. Now that I was doing actual work and people payed me for it, I lost my last bits of motivation for sitting at lectures and doing exercises. I stopped going there and didn't show up for the midterm exams. I returned my Macbook. The only time I went there again was five years later, to pick up my papers. 34 + 35 + A short digression. I'm not saying colleges are worthless, or that you should drop out. It was the right decision for me, but I knew I could fall back on my family (more on that later) when things are tough. I also already had a job. 36 + 37 + I had the privilege to be seen as knowledgeable *by default* due to my background (a guy who started coding early). People who don't fit this stereotype often get a degree just to gain access to that assumed competence. So there's that. 38 + 39 + ## 2011 40 + 41 + Most of my job was fixing up poor code after cheaper outsourcing companies. Having no industry experience myself, I overengineered every project to try every cool technology I could get my hands on. I even put [random Microsoft Research projects](https://www.microsoft.com/en-us/research/project/moles-isolation-framework-for-net/) in production. Sorry about that. I did some good work too. 42 + 43 + My first interesting work project involved a trip. Our client was an investment group in New York. I still don't know anything about investments, but basically they had an email system that received orders, and those orders needed to go through different levels of approval. They had a service that manages that, but the service was extremely flaky, and nobody could figure out how it works. My job was to go onsite, work from New York for a month, and fix the service. 44 + 45 + The service was written by a contractor from a cheaper outsourcing company. Nine years later, I still remember his name. The most memorable part of that code was a single thirty thousand line function. To figure out what it does, I had to print it on paper, lay out the sheets on my desk, and annotate them with a pencil. It turned out that it was the same block of code, repeated fifty times with different conditions. I guess someone was paid by the number of lines of code. 46 + 47 + I spent that month adding a shitton of logging to figure out what the service does in production, and then rebuilding it from scratch to be less flaky. Working with a non-tech company was a perplexing experience. For example, I couldn't push a bugfix without writing a Word document describing my changes and getting the IT department head to sign off on it. Now *that's* some code review. 48 + 49 + Close to the end of my trip, I went to see a concert at a bar late at night. The next morning I was supposed to present my month of work to the client. My meeting was scheduled for 9am. Unfortunately, I overslept and only woke up at 1pm that day. My manager apologized for me, and I went home bitterly embarrassed. 50 + 51 + There were no repercussions at work. The project was a success overall, and the client knew I'm some weird Russian dude who doesn't know how to groom his hair. But I knew I made a fool of myself. I also wasn't particularly looking forward to more "enterprise projects". Work became a chore. 52 + 53 + I was back in Saint Petersburg in Russia. In the summer, the sky there doesn't go dark. During one night of soul-searching, I hopped from a bar to another with a vague sense of unease. Around 7am, a lightbulb went off in my head. I ate a shawarma, took a subway to the office, waited for HR, and quit my job. 54 + 55 + My friend was planning a trip to Crimea (before it got annexed) and asked if I would like to join. I packed up a tent and an old Nokia phone that held battery for a week. We camped for two weeks, mostly naked, in a fog of alternative mind states. I barely remember anything from that trip except two episodes. 56 + 57 + Once, somebody threatened me with a knife. That person said he would kill me, but he was gone the next day, and everything went on as normal. Another time, I foolishly tried to swim around the cliff alone and almost drowned. I was saved by a rock in the sea that I climbed and passed out on for what felt like an hour. 58 + 59 + This trip acted like a hardware reset. My burnout was cured, and I was ready to write code again. (But don't say I told you to almost die to cure a burnout.) 60 + 61 + The only problem was... my skills were irrelevant! Yikes. 62 + 63 + You see, I was mostly writing desktop software. Has anyone even heard of desktop software? That's not a thing anymore. Either you do backend, or you do mobile, or you do front-end. I didn't know anything about either of them. And I didn't have a job. So I had to move back to live with my Mom. (Thanks, Mom!) 64 + 65 + Soon, I saw a post on a social network. It was written by a Russian guy who came back from the Silicon Valley to Russia. He was looking for people who would volunteer to work on his projects for free, in return for him teaching us web development for free. At the time, that sounded like a good deal to me. 66 + 67 + I joined this program. I found out quickly enough that there was no real teaching involved: we were given a few tutorials from the web, and we mostly learned from helping each other. Luckily, I could afford to do that for some time while I lived at my Mom's place. I learned Git, basics of Python, Django, a bit of CSS and JavaScript, and some Bash to deploy my changes. Hello Web, here I go. 68 + 69 + Nine years later, I'm still unsure how I feel about this program. On the one hand, we were working on his projects for free. On the other hand, we were given full root access, and it was really exciting to be able to push our changes in production and learn from our mistakes. It gave us a structure for learning. It didn't cost us anything, and you could drop out any time. The projects had some social utility due to being around education. This reminded me of open source. 70 + 71 + I still feel grateful to this person for setting up this ad hoc "bootcamp" and being my mentor. But I don't want to imply that working for free is a good way to practice in general. I'm not offering advice here -- I am only telling my story. 72 + 73 + I built a dashboard where we could track our own learning achievements. My mentor suggested that I pitch it as a product to companies that run courses. My brief foray into playing "startups" was awkward. I didn't know what product I was building, and I pitched different things to different people. Essentially, I ended up making several completely different websites for different clients with a single engine, and earned about $200 in the process. I wasted months of my time on it, as well as the time of friends who offered to help. I was ashamed of it, and shut it down. The silver lining was that I became a web developer. 74 + 75 + But I still didn't have a job. 76 + 77 + ## 2012 78 + 79 + As a 20 year old web developer, there was only one place I wanted to work at. It was a Russian social media company. Everybody in Russia used their product. That product was very polished. And the team was considered *cool*. Almost elite. 80 + 81 + Their executives often posted about how well-paid their engineers are. The small team of engineers seemed happy with the technical challenges and how the company treated them. In the Russian tech circles, many knew their names. 82 + 83 + My mentor introduced me to their CTO, and I got a takehome exercise in JavaScript. It involved building a clone of their typeahead where you could select friends to message. I spent two weeks building it. It was pixel perfect in all browsers. I took care to replicate a similar caching and debouncing behavior. 84 + 85 + The onsite interview was a disaster. Clearly, I didn't have the experience at their scale. However, they said they're willing to give me a try if I "understand their product". They gave me a take-home exercise of designing a logged out state for that social media website. They wanted it to show a picture of a feature phone -- many people didn't know the mobile website works on cheap phones. 86 + 87 + I spent a week designing that page. I did a lot of tiny details and even hid some Easter eggs in it. I was proud of myself. However, I couldn't find any decent designs of a feature phone that wouldn't look ugly. Instead, I put a beautiful iPhone design there. So aesthetically pleasing, I thought. 88 + 89 + Of course, I got rejected. I ignored literally the only hard requirement -- why was I so dense? I cried for a few hours because I didn't really want to work anywhere else. I was still living with my Mom and was making no money. 90 + 91 + At the time, I was seriously doubting my skills. Many things seemed "magic" to me. I started having doubts about whether dropping out was a good idea, after all. I signed up for an iOS development course on iTunes U. I also signed up for two courses on Coursera: Compilers and Machine Learning. Maybe they would make me a "real programmer". 92 + 93 + It was lonely to go through these courses on my own. I organized a tiny meetup with a few people from our web development "bootcamp". We would gather and watch different online courses together at my mentor's coworking space. 94 + 95 + A month into it, I got an email. Someone was looking to hire a developer, and he heard about me from a person who went to my meetup. I was sick with mono and ignored the email, but this guy kept emailing me. He wanted to skype. 96 + 97 + Roman Mazurenko was not a typical startup founder. Roman was interested in DIY publishing. Together with a couple of friends, for a few years, he somehow [made Moscow cool](https://youtu.be/Wj2FmNGw47c?t=9). He organized parties and posed for fashion magazines. I didn't know what to expect. But Roman was very down-to-earth and fun to talk to. His dream was to build a DIY publishing platform like in this [concept video](https://vimeo.com/55247463). I would have to move to Moscow and learn iOS development on the go. (By the way, the video guy is not Roman but a friend, and the app in the video is a fake animation made with Flash. Roman was great at crafting smoke and mirrors.) 98 + 99 + I said yes. 100 + 101 + I didn't finish my Compilers and Machine Learning courses. I learned enough to know these topics aren't magic. After that, I lost most of my interest in them. 102 + 103 + ## 2013 104 + 105 + By 2013, my salary was $30k/year -- almost twice what I made at my previous job. While low by US standards, it was pretty decent in Russia. I also negotiated some stock at Stampsy (spoiler alert: it ended up completely worthless). 106 + 107 + Our team had five developers and two designers. We started by developing an iPad app, but neither of us had any real knowledge of iOS. I remember my relief when a teammate figured out how to implement the animation we needed for the first time. Until then, I thought we were doomed. 108 + 109 + For a few months, I literally lived in our office. Looking back at this period, I'm not proud of my life-work balance, and it was not healthy. However, I learned more during these months than in two years before, and I don't regret them. 110 + 111 + Eventually, I moved out of the office. I've started to live in the same flat as Roman. My room cost me $1k/month. It was a spacious flat in the only area of Moscow that I enjoyed, and it was only five minutes of walk from the office. 112 + 113 + We thought that some of the code we wrote might be useful to others. So we started publishing those pieces on GitHub. We didn't expect anything grand, and even getting a couple of contributions was really nice. The most popular project I worked on during that period has 30 stars. To us, that was a *lot*. 114 + 115 + A designer on our team introduced me to Bret Victor's talks -- particularly, to [Inventing on Principle](https://vimeo.com/36579366). I thought it was a very good talk. A *very* good one. 116 + 117 + In April, we released the [iPad app](https://www.youtube.com/watch?v=PjaL0xFbEY8) we've been working on. Apple reached out to our team and asked for assets to feature it in the App Store. We were over the moon. It stayed featured for weeks, and people started using it. 118 + 119 + Our excitement quickly wore down as we realized there was no product market fit. The app was designed to create beautiful magazine-like layouts, but nobody had any beautiful content on an iPad. Also, the iPad camera had a horrible quality. The product didn't make any sense. How could we not realize this? 120 + 121 + My personal relationship was falling apart too. We weren't a good fit, and mostly clung to each other out of fear of being alone. We finally broke up. 122 + 123 + For a few months, I didn't talk to people from our shared mutual circle and focused on work. But I realized that I missed one particular friend. I wrote to her, and she said she missed me too. I arranged a plan for a trip together. 124 + 125 + I caught a cold. As the day of our would-be trip got closer, I felt worse, but I was hoping that maybe I'd be okay. When my train from Moscow to St. Petersburg had arrived, I clearly had a temperature. She said to come to her place anyway. She made me some hot tea, gave me warm socks, and we kissed. I moved in. 126 + 127 + ## 2014 128 + 129 + For me, 2014 was the year of React. 130 + 131 + After a brief existential crisis, we abandoned the iPad app and decided to pivot to a webapp. That meant I had to learn JavaScript, this time for reals. We built a decent prototype with Backbone, but interactive parts were a pain to code. 132 + 133 + My colleague saw React but initially dismissed it. But a few months later, he told me React wasn't actually that bad. I decided to give React a try. Ironically, the first component I converted from Backbone to React was a Like button. 134 + 135 + And it worked well. *Really* well. It felt unlike anything I've seen. 136 + 137 + React wasn't a hard sell for the team. Over the next year we gradually converted all of our UI to React while shipping new features. React and the principle of unidirectional data flow allowed us to develop faster and with fewer bugs. 138 + 139 + We started a private beta, and some photographers enjoyed creating visual stories with it. It was something between Medium, Pinterest, and Tumblr. There wasn't a lot of traction, but it wasn't a complete failure like the iPad app. 140 + 141 + The only problem with using React was that there was virtually no ecosystem. When we just started, there was only one router (which was *not* React Router), and we couldn't figure out how to use it. So we made our own. After React Router came out, we adopted it and [added](https://github.com/ReactTraining/react-router/pull/388) a feature we needed for our product. 142 + 143 + There was no drag and drop solution for our use cases, so I [ported](https://github.com/react-dnd/react-dnd/releases/tag/v0.1.0) my colleague's library to React. I made [a helper](https://github.com/gaearon/react-document-title/releases/tag/v0.1.0) to manage document titles. Wrote [another library](https://github.com/paularmstrong/normalizr/releases/tag/v0.1.1) to normalize API responses. Jing Chen from the React IRC channel suggested the core idea, and it worked! Little did I know that in a few years, Twitter would build their new website with this library and maintain it. 144 + 145 + I wanted to contribute to React itself, too. I reached out to Paul O’Shannessy and asked if there were any pull requests I could work on. I finished [my first task](https://github.com/facebook/react/pull/1601) in a few days, but it didn't get merged until a few months later. Big project release cycles, and all that. I was frustrated by the slowness in response so I put effort into the ecosystem instead. In retrospect, that was a lot more impactful. 146 + 147 + In 2014, I did some of my first public speaking. I gave sort of a lecture about React at our office. It ended up going for two hours, and I'm still surprised that most of the people who showed up were polite enough to stay to the end. 148 + 149 + Later, I signed up to give a talk at the BerlinJS meetup. My topic was "React and Flux". I didn't practice my talk, and I only went through the first half when my time ran out. People rolled their eyes, and I finally learned my lesson. From that point on, I would rehearse every talk from three up to fifteen times. 150 + 151 + In 2014, I got my first email from a Facebook recruiter. I missed it in my inbox and only found it many months later. We still chatted eventually, but it turned out that hiring me in USA wouldn't be easy because I didn't have enough years of experience *and* I dropped out of college. Oops. 152 + 153 + There was one project I started in 2014 that was particularly dear to me. Like most important things in my life, it happened randomly. I was converting our app from require.js to webpack to enable code splitting. I read about a bizarre webpack feature called "hot module replacement" that allowed you to edit CSS without reloading the page. But in webpack, it could work for JavaScript too. 154 + 155 + I was really confused by this feature so I [asked](https://stackoverflow.com/questions/24581873/what-exactly-is-hot-module-replacement-in-webpack) about it on StackOverflow. Webpack was still very new, and its creator noticed my question and left a response. It gave me enough information to see I could tie this feature with React, in the spirit of the first demo from Bret Victor's talk I mentioned earlier. 156 + 157 + I wrote an extremely hacky proof of concept by editing React source code and adding a bunch of global variables. I decided I won't go to sleep until it works, and by 7am I had a demo I could [tweet](https://twitter.com/dan_abramov/status/485611717183819776). Nobody cared about my Twitter before that, but this received some likes and retweets, and those 20 retweets were hugely validating. I knew then I'm not the only one who thinks this is exciting. This proof of concept was a throwaway effort and I didn't have time to keep working on it at my job. I took a vacation, and finished off the [prototype](https://github.com/gaearon/react-hot-loader/tree/v0.1.0) there. 158 + 159 + Quick disclaimer: again, I'm not saying you "need to" work nights or vacations. I'm not glorifying hustle, and there are plenty of people with great careers who did none of that. In fact, if I were better at time management, I could probably find a way to squeeze those non-interrupted hours in my regular day, or to learn to make progress with interruptions. I am sharing this because I'm telling my story, and it would be a lie to pretend I did everything in a 40 hour week. 160 + 161 + ## 2015 162 + 163 + Our product went out of a private beta. It was growing, but slowly and linearly. The company was running out of funding and struggled to raise more money. And I wanted to spend more and more time on my open source projects. 164 + 165 + I also wanted to give my first conference talk. Naturally, I wanted to talk about hot reloading, but I knew somebody already mentioned it at ReactConf, and I thought people wouldn't be excited about it. I decided to add some spice to my [talk proposal](https://github.com/react-europe/cfp-2015/pull/7) by adding "with time travel" -- again, inspired by Bret's demo. The proposal got accepted, and for a few months I didn't think much about it. 166 + 167 + In April, my salary got delayed by several weeks. It went through eventually, but I realized it's time to look for a new job. I found some company using one of my projects, and they agreed to sponsor my work on it for a few months. 168 + 169 + My girlfriend asked if I wanted to get married. I said I thought I'd get married late in my 30s. She asked: "Why?" I couldn't really find any justification for waiting so we soon bought rings and got married. Our wedding has cost us $100. 170 + 171 + The deadline for my talk was approaching. But I had no idea how to implement "time travel". I knew that Elm had something similar, but I was scared to look at it because I worried I'd find out time travel can't be implemented well in JS. 172 + 173 + At the time, there were many Flux libraries. I've tried a few of those, notably Flummox by Andrew Clark, and I had a vague sense that making hot reloading work with Flux would *also* let me implement time travel. Sunil's [gist](https://gist.github.com/threepointone/43f16389fd96561a8b0b) led me to an [idea](https://gist.github.com/gaearon/c02f3eb38724b64ab812): a variant of Flux pattern with a reducer function instead of a store. I had a [neat name](https://twitter.com/dan_abramov/status/597391033513697281) in mind for it already. And I really needed it for my talk! 174 + 175 + I implemented Redux just in time for my demo of time travel. My first talk rehearsal was on Skype. I was sweating, mumbling, and running over it too fast. At the end of my rehearsal, I asked the organizer if my talk was any good. He said "well... people *like* you" which I took as an euphemism for horrible. 176 + 177 + I asked a designer friend from the startup I just quit to help make my slides pretty. I added animations and transitions. The more polished my talk looked, the calmer and more confident I felt. I practiced it a dozen times. 178 + 179 + I flew to Paris for my first technical conference. This was probably the happiest day of my life. For the first time, I put faces next to avatars. There were UI nerds and my personal heroes around me. It felt like going to Hogwarts. 180 + 181 + My talk almost didn't happen. In the morning, I found that my laptop refused to connect to the projector. I only had a few hours left. Christopher Chedeau was kind enough to lend me his laptop, and I transferred my live demo setup to his computer (except for the Sublime license, as you may know if you watched it). 182 + 183 + At first, my demo didn't run on Christopher's laptop because we had different Node versions. The conference WiFi was so bad that downloading another Node version was a non-starter. Luckily, I found an npm command that rebuilds the binaries. It saved my demo. I gave my talk with his computer, and it [went well](https://www.youtube.com/watch?v=xsSnOQynTHs). 184 + 185 + I met many people in the audience who I already knew from Twitter. One of them was Jing Chen. I remembered her from our IRC chat on the React channel, and came to say hi. She asked whether FB recruiters contacted me before, and I said I couldn't get a US visa. Jing asked whether I'm interested in working at the London office, and I had no idea there even *was* a London office! I called my wife and asked if she's up for moving to London. I thought she'd hate the idea, but she immediately said yes. So I said yes to an interview. 186 + 187 + There were four people from FB at the conference, so Jing arranged a full interview right at the conference hotel. It was a regular interview process, except it was in Paris and everyone was sweaty because it was so hot outside. 188 + 189 + Everything happened so spontaneously that I neither had time to prepare, nor to get nervous. At one point I freaked out and panicked because I couldn't write three lines of code that swap two items in an array. I asked Jing to look away for a few seconds. She said "I know you can swap two items", and that gave me the confidence to finish the answer and make it through the interview. I probably didn't pass with flying colors, but I got the offer. 190 + 191 + My talk got really popular. Andrew Clark has already deprecated Flummox -- the most popular Flux library -- in favor of Redux, which he co-wrote with me. People started putting Redux in production. New learners were confused by the README, which was written for early adopters who had all the existing context. I didn't have a job, and it would take me several more months to get a UK visa. 192 + 193 + I started a Patreon to sustain my projects for a few months -- and specifically to write Redux documentation, create small example apps, and record some free videos about it. I raised about $5k/month on Patreon which was more than any salary I made by that point in my life. Folks from Egghead sent me some mic gear, and I recorded my "Getting Started with Redux" course. I can't watch it without cringing today, but it has been very popular and made me good money (around $3k/month in royalties) for quite a while -- even though it was free. 194 + 195 + FB took care of handling most of the immigration process. My wife and I only had to fill some papers, and I had to take an English exam and do some health checks. FB did most of the work to relocate us, including moving our cat from Russia to UK (which cost around $5k). I was hired at engineering level 4, with initial base salary of $100k/year and an initial restricted stock unit grant of $125k vesting over four years. I also had a signing bonus of $18k which helped a lot as we were settling in. (By the way, tech salaries are lower in UK than in US.) 196 + 197 + We arrived to London at the end of November 2015. We've never been to London before. We took a black cab from the airport. We couldn't figure out how to turn off the heating in the cab for ten minutes so we got very sweaty and couldn't see anything through the window. When we turned off the fan and the window cleared up, our eyes were wide like saucers. London was beautiful. 198 + 199 + The next day, Roman Mazurenko got hit to death by a careless driver. He just got his US visa approved, and he came to Moscow to pick up his documents. He once told me that there's something devilish about Moscow. It doesn't just let you go. I would not see my friend again. Not in 2015, not ever. 200 + 201 + Roman has had a sort of a [digital](https://romanmazurenko.com/) [afterlife](https://www.theverge.com/a/luka-artificial-intelligence-memorial-roman-mazurenko-bot), courtesy of his friends. I know for a fact he would've loved the irony of having a two point five star [App Store rating](https://apps.apple.com/us/app/roman-mazurenko/id958946383). 202 + 203 + ## 2016 204 + 205 + New job. New city. New country. Different language. Unfamiliar accent. Big company. Orientation. Meeting rooms. Projects. Teams. Documents. Forms. Shit. Fuck. Fuck. Fuckety Fuck, Shit, Oh Dear, Blimey! 206 + 207 + I barely remember the first few months. I was in a constant state of stress from trying to understand what people are saying without subtitles. What the hell is my manager telling me? Is it rude to ask to repeat again? Spell it for me? 208 + 209 + What, I need to call a lady in Scotland to get a national insurance number _by phone_? I don't get a word she's saying. What even *is* the national insurance? Why do I have a "zero" tax code and why is my salary less than I expected? Wait, people _actually pay taxes_ here? What do I do when I'm sick? What's NHS? 210 + 211 + During my first trip to US in 2016 (part of onboarding), I forgot to eat the whole day, drank a lot of coffee, and had a full-fledged panic attack trying to explain how hot reloading works to a colleague. We called an ambulance, and I got a $800 bill (thankfully, paid by FB -- or at least I don't recall paying it myself). 212 + 213 + Relocation was nerve-wracking even though the company handled most of the difficulties. I thought I did everything in the onboarding instructions, but I forgot to register with the police. (I confused this with registering at the post office, which we also had to do.) I only found out that we screwed up months later, and we were told it might affect our visas. Luckily, it didn't so far. 214 + 215 + Originally, I was supposed to join the React Native team in London. Usually, we hire people to go through Bootcamp and pick a team, but I didn't have that freedom. I was preallocated. However, I wasn't very excited about React Native in particular. I talked to Tom Occhino (he managed the React team at that time), and he suggested that I can join the [React Core](https://reactjs.org/community/team.html) team (based in US) as the only UK member. I was already used to remote work from open source, so I agreed. 216 + 217 + In 2016, there was a React boom, but everybody made their own "boilerplate" with a bundler, watcher, compiler, and so on. React became synonymous with modular JavaScript, ES6, and all the tooling complexity. Christopher Chedeau suggested to prototype a command-line interface for getting started with React. We made the first version in a few weeks and released [Create React App](https://reactjs.org/blog/2016/07/22/create-apps-with-no-configuration.html). 218 + 219 + ## 2017 220 + 221 + Egghead courses continued to bring steady side income with royalties. I didn't think twice before spending them on food delivery or nice clothes. 222 + 223 + It's only in 2017 that I came to realize that these royalties are taxable as foreign income, and that I owe Her Majesty something like $30k. Oops. Like an adult, I got an accountant. Fixing this mess depleted all my savings again. 224 + 225 + At work, we spent most of 2017 rewriting React from scratch. You know the result of it as [React 16](https://reactjs.org/blog/2017/09/26/react-v16.0.html). Sophie tells the story of how we did it [here](https://engineering.fb.com/web/react-16-a-look-inside-an-api-compatible-rewrite-of-our-frontend-ui-library/). 226 + 227 + Aside from taxes, there wasn't a lot happening in my personal life. I was still settling in. I got less shy dealing with bureaucracies. I could make and take phone calls without freaking out. I watched movies without subtitles. I learned how to deal with NHS and with private insurance. I stopped doing side projects. 228 + 229 + ## 2018–2019 230 + 231 + The last two years have been a blur. I'm still too close to them to have a clear perspective on what was important. 232 + 233 + Professionally, our projects are as demanding as ever. If you follow React, you know about [some things](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) [we have been working on](https://reactjs.org/blog/2018/11/13/react-conf-recap.html). I've grown as an engineer, and still have much to learn. Our London team has grown -- I'm not alone now. 234 + 235 + People occasionally recognize me. This is humbling. Someone once recognized me in a sauna and started complaining about React. Please don't be that person. 236 + 237 + I got promoted. I started this blog as a side project. I have [another side project](https://justjavascript.com/) coming, which I've been musing about for the better part of these two years. I met more people from the internet and put more faces to avatars. This is fun. 238 + 239 + I always knew that I liked building UIs. I got hooked on Visual Basic. I spent this decade building UIs, and then building a way to build UIs. And then talking about it and explaining it. But I realize now that my drive to *explain* things is just as important to me as my drive to build. Perhaps, even more important. 240 + 241 + I look forward to doing more of that in the next decade. 242 + 243 + Or, should I say, *this* decade? 244 + 245 + Welcome to the twenties.
+164
public/my-wishlist-for-hot-reloading/index.md
··· 1 + --- 2 + title: My Wishlist for Hot Reloading 3 + date: '2018-12-08' 4 + spoiler: I don't want a lot for Christmas. There is just one thing I need. 5 + --- 6 + 7 + Do you have a project that you approach repeatedly with a mix of success and failure, step aside for a while, and then try again — year after year? For some, it might be a router or a virtual list scroller. For me, it’s hot reloading. 8 + 9 + My first exposure to the idea of changing code on the fly was a brief mention in a book about Erlang that I read as a teenager. Much later, like many others, I fell in love with [Bret Victor’s beautiful demos](https://vimeo.com/36579366). I’ve read somewhere Bret was unhappy with people cherry-picking “easy” parts of his demos and screwing up the big vision. (I don’t know if this is true.) **In either case, to me shipping even small incremental improvements that people take for granted later is a success.** Smarter people than me will work on Next Big Ideas. 10 + 11 + Now, I want to be clear that none of the *ideas* discussed in this post are mine. I’ve been [inspired](https://redux.js.org/introduction/prior-art) by many projects and people. In fact, even people whose projects I’ve never tried occasionally told me I’ve ripped off their stuff. 12 + 13 + I’m not an inventor. If I have a “principle”, it is to take a vision that inspires me, and share it with more people — through words, code, and demos. 14 + 15 + And hot reloading inspires me. 16 + 17 + --- 18 + 19 + I’ve taken several attempts at implementing hot reloading for React. 20 + 21 + In retrospect, [the first demo](https://vimeo.com/100010922) I cobbled together changed my life. It got me my first Twitter followers, first thousand GitHub stars, later first [HN frontpage](https://news.ycombinator.com/item?id=8982620) hit, and even my first [conference talk](https://www.youtube.com/watch?v=xsSnOQynTHs) (bringing Redux into existence, oops). This first iteration worked fairly well. However, soon React moved *away* from `createClass`, making a reliable implementation much more difficult. 22 + 23 + Since then I’ve done [a few more attempts](https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf?source=user_profile---------6------------------) to fix it, each flawed in a different way. One of them is still being used in React Native (hot reloading functions doesn’t work there because of my mistakes — sorry!) 24 + 25 + Frustrated with my inability to work around some issues and the lack of time, I handed React Hot Loader over to a few talented contributors. They have been pushing it forward and found clever workarounds for my design flaws. I am grateful to them for keeping the project in a good state despite the challenges. 26 + 27 + --- 28 + 29 + **To be clear, hot reloading in React is quite usable today.** In fact, this blog uses Gatsby which uses React Hot Loader under the hood. I save this post in my editor and it updates without refreshing. Magic! In some ways, the vision that I worried wouldn’t ever see mainstream usage is already almost boring. 30 + 31 + But there are plenty of people who feel it isn’t as good as it could be. Some dismiss it as a gimmick and that breaks my heart a little bit, but I think what they’re really saying is: **the experience is not seamless.** It’s not worth it if you’re never sure whether a hot reload worked, if it breaks in confusing ways, or if it’s easier to just refresh. I agree with this 100%, but to me it means we have more work to do. And I’m excited to start thinking about what official React support for hot reloading could look like in the future. 32 + 33 + (If you use a language like Elm, Reason or ClojureScript, maybe those problems are already solved in your ecosystem. I’m happy that you’re happy. This won’t stop me from trying and failing to bring good stuff to JavaScript.) 34 + 35 + --- 36 + 37 + I think I’m ready to take another attempt at implementing it. Here’s why. 38 + 39 + Ever since `createClass` stopped being the primary way we define components, **the biggest source of complexity and fragility in hot reloading components was dynamically replacing class methods.** How do you patch existing instances of classes with new “versions” of their methods? The simple answer is “replace them on the prototype” but even with Proxies, in my experience there are too many gnarly edge cases for this to work reliably. 40 + 41 + By comparison, hot reloading functions is easy. A Babel plugin could split any function component exported from a module into two functions: 42 + 43 + ```jsx 44 + // Reassigns the latest version 45 + window.latest_Button = function(props) { 46 + // Your actual code is moved here by a plugin 47 + return <button>Hello</button>; 48 + } 49 + 50 + // Think of this as a "proxy" 51 + // that other components would use 52 + export default function Button(props) { 53 + // Always points to latest version 54 + return window.latest_Button(props); 55 + } 56 + ``` 57 + 58 + Every time this module re-executes after an edit, `window.latest_Button` would point to the latest implementation. Reusing the same `Button` function between module evaluations would let us trick React into not unmounting our component even though we swapped out the implementation. 59 + 60 + For a long time, it seemed to me that implementing reliable hot reloading for functions *alone* would encourage people to write convoluted code just to avoid using classes. But with [Hooks](https://reactjs.org/docs/hooks-intro.html), function components are fully featured so this is not a concern anymore. And this approach “just works” with Hooks: 61 + 62 + ```jsx{4} 63 + // Reassigns the latest version 64 + window.latest_Button = function(props) { 65 + // Your actual code is moved here by a plugin 66 + const [name, setName] = useState('Mary'); 67 + const handleChange = e => setName(e.target.value); 68 + return ( 69 + <> 70 + <input value={name} onChange={handleChange} /> 71 + <h1>Hello, {name}</h1> 72 + </> 73 + ); 74 + } 75 + 76 + // Think of this as a "proxy" 77 + // that other components would use 78 + export default function Button(props) { 79 + // Always points to latest version 80 + return window.latest_Button(props); 81 + } 82 + ``` 83 + 84 + As long as the Hook call order doesn’t change, we can preserve the state even as `window.latest_Button` is replaced between file edits. And replacing event handlers “just works” too — because Hooks rely on closures, and we replace the whole function. 85 + 86 + --- 87 + 88 + This was just a rough sketch of one possible approach. There are more (some are very different). How do we evaluate and compare them? 89 + 90 + Before I get too attached to a specific approach that might be flawed in some way, **I decided to write down a few principles that I think are important for judging any hot reloading implementation for component code.** 91 + 92 + It would be nice to express some of these principles as tests later. These rules aren’t strict and there might be reasonable compromises. But if we decide to break them, that should be an explicit design decision and not something we accidentally discover later. 93 + 94 + Here goes my wish list for hot reloading React components: 95 + 96 + ### Correctness 97 + 98 + * **Hot reloading should be unobservable before the first edit.** Until you save a file, the code should behave exactly as it would if hot reloading was disabled. It’s expected that things like `fn.toString()` don’t match, which is already the case with minification. But it shouldn’t break reasonable application and library logic. 99 + 100 + * **Hot reload shouldn’t break React rules.** Components shouldn’t get their lifecycles called in an unexpected way, accidentally swap state between unrelated trees, or do other non-Reacty things. 101 + 102 + * **Element type should always match the expected type.** Some approaches wrap component types but this can break `<MyThing />.type === MyThing`. This is a common source of bugs and should not happen. 103 + 104 + * **It should be easy to support all React types.** `lazy`, `memo`, `forwardRef` — they should all be supported and it shouldn’t be hard to add support for more. Nested variations like `memo(memo(...))` should also work. We should always remount when the type shape changes. 105 + 106 + * **It shouldn’t reimplement a non-trivial chunk of React.** It’s hard to keep up with React. If a solution reimplements React it poses problems in longer term as React adds features like Suspense. 107 + 108 + * **Re-exports shouldn’t break.** If a component re-exports components from other modules (whether own or from `node_modules`), that shouldn’t cause issues. 109 + 110 + * **Static fields shouldn’t break.** If you define a `ProfilePage.onEnter` method, you’d expect an importing module to be able to read it. Sometimes libraries rely on this so it’s important that it’s possible to read and write static properties, and for component itself to “see” the same values on itself. 111 + 112 + * **It is better to lose local state than to behave incorrectly.** If we can’t reliably patch something (for example, a class), it is better to lose its local state than to do a mixed success effort at updating it. The developer will be suspicious anyway and likely force a refresh. We should be intentional about which cases we’re confident we can handle, and discard the rest. 113 + 114 + * **It is better to lose local state than use an old version.** This is a more specific variation of the previous principle. For example, if a class couldn’t be hot reloaded, the code should force a remount for those components with the new version rather than keep rendering a zombie. 115 + 116 + ### Locality 117 + 118 + * **Editing a module should re-execute as few modules as possible.** Side effects during component module initialization are generally discouraged. But the more code you execute, the more likely something will cause a mess when called twice. We’re writing JavaScript, and React components are islands of (relative) purity but even there we don’t have strong guarantees. So if I edit a module, my hot reloading solution should re-execute that module and try to stop there if possible. 119 + 120 + * **Editing a component shouldn’t destroy the state of its parents or siblings.** Similar to how `setState()` only affects the tree below, editing a component shouldn’t affect anything above it. 121 + 122 + * **Edits to non-React code should propagate upwards.** If you edit a file with constants or pure functions that’s imported from several components, those components should update. It is acceptable to lose module state in such files. 123 + 124 + * **A runtime error introduced during hot reloading should not propagate.** If you make a mistake in one component, it shouldn’t break your whole app. In React, this is usually solved by error boundaries. However, they are too coarse for the countless typos we make while editing. I should be able to make and fix runtime errors while I work on a component without its siblings or parents unmounting. However, errors that *don’t* happen during hot reload (and are legitimate bugs in my code) should go to the closest error boundary. 125 + 126 + * **Preserve own state unless it’s clear the developer doesn’t want to.** If you’re just tweaking styles, it’s frustrating for the state to reset on every edit. On the other hand, if you just changed the state shape or the initial state, you’ll often prefer it to reset. By default we should try our best to preserve state. But if it leads to an error during hot reload, this is often a sign some assumption has changed, so we should reset state and *retry* rendering in that case. Commenting things out and back in is common so it’s important to handle that gracefully. For example, removing Hooks *at the end* shouldn’t reset state. 127 + 128 + * **Discard state when it’s clear the developer wants to.** In some cases we can also proactively detect that the user wants to reset. For example, if the Hook order changed, or if primitive Hooks like `useState` change their initial state type. We can also offer a lightweight annotation that you can use to force a component to reset on every edit. Such as `// !` or some similar convention that’s fast to add and remove while you focus on how component mounts. 129 + 130 + * **Support updating “fixed” things.** If a component is wrapped in `memo()`, hot reload should still update it. If an effect is called with `[]`, it should still be replaced. Code is like an invisible variable. Previously, I thought it was important to force deep updates below for things like `renderRow={this.renderRow}`. But in the Hooks world, we rely on closures anyway this seems unnecessary anymore. A different reference should be sufficient. 131 + 132 + * **Support multiple components in one file.** It is a common pattern that multiple components are defined in the same file. Even if we only keep the state for function components, we want to make sure putting them in one file doesn’t cause them to lose state. Note these can be mutually recursive. 133 + 134 + * **When possible, preserve the state of children.** If you edit a component, it’s always frustrating if its children unintentionally lose state. As long as the element types of children are defined in other files, we expect their state to be preserved. If they’re in the same file, we should do our best effort. 135 + 136 + * **Support custom Hooks.** For well-written custom Hooks (some cases like `useInterval()` can be a bit tricky), hot reloading any arguments (including functions) should work. This shouldn’t need extra work and follows from the design of Hooks. Our solution just shouldn’t get in the way. 137 + 138 + * **Support render props.** This usually doesn’t pose problems but it’s worth verifying they work and get updated as expected. 139 + 140 + * **Support higher-order components.** Wrapping export into a higher-order component like `connect` shouldn’t break hot reloading or state preservation. If you use a component created from a HOC in JSX (such as `styled`), and that component is a class, it’s expected that it loses state when instantiated in the edited file. But a HOC that returns a function component (potentially using Hooks) shouldn’t lose state even if it’s defined in the same file. In fact, even edits to its arguments (e.g. `mapStateToProps`) should be reflected. 141 + 142 + ### Feedback 143 + 144 + * **Both success and failure should provide visual feedback.** You should always be confident whether a hot reload succeeded or failed. In case of a runtime or a syntax error you should see an overlay which should be automatically be dismissed after it is irrelevant. When hot reload is successful, there should be some visual feedback such as flashing updated components or a notification. 145 + 146 + * **A syntax error shouldn’t cause a runtime error or a refresh.** When you edit the code and you have a syntax error, it should be shown in a modal overlay (ideally, with a click-through to the editor). If you make another syntax error, the existing overlay is updated. Hot reloading is only attempted *after* you fix your syntax errors. Syntax error shouldn’t make you lose the state. 147 + 148 + * **A syntax error after reload should still be visible.** If you see a modal syntax error overlay and refresh, you should still be seeing it. It categorically should not let you run the last successful version (I’ve seen that in some setups). 149 + 150 + * **Consider exposing power user tools.** With hot reloading, code itself can be your “terminal”. In addition to the hypothetical `// !` command to force remount, there could be e.g. an `// inspect` command that shows a panel with props values next to the component. Be creative! 151 + 152 + * **Minimize the noise.** DevTools and warning messages shouldn’t expose that we’re doing something special. Avoid breaking `displayName`s or adding useless wrappers to the debug output. 153 + 154 + * **Debugging in major browsers should show the most recent code.** While this doesn’t exactly depend on us, we should do our best to ensure the browser debugger shows the most recent version of any file and that breakpoints work as expected. 155 + 156 + * **Optimize for fast iteration, not long refactoring.** This is JavaScript, not Elm. Any long-running series of edits likely won’t hot reload well due to a bunch of mistakes that need to be fixed one by one. When in doubt, optimize for the use case of tweaking a few components in a tight iteration loop rather than for a big refactor. And be predictable. Keep in mind that if you lose developer’s trust they’ll refresh anyway. 157 + 158 + --- 159 + 160 + This was my wish list for how hot reloading in React — or any component system that offers more than templates — should work. There’s probably more stuff I will add here with time. 161 + 162 + I don’t know how many of these goals we can satisfy with JavaScript. But there’s one more reason I’m looking forward to working on hot reloading again. As an engineer I’m more organized than before. In particular, **I’ve finally learned my lesson to write up requirements like this before diving into another implementation.** 163 + 164 + Maybe this one will actually work! But if it doesn’t, at least I’ve left some breadcrumbs for the next person who tries it.
+98
public/name-it-and-they-will-come/index.md
··· 1 + --- 2 + title: Name It, and They Will Come 3 + date: '2019-03-25' 4 + spoiler: A change starts with a story. 5 + --- 6 + 7 + You’ve discovered something new. 8 + 9 + You haven’t seen solutions *quite like this* before. You try to keep your ego in check and be skeptical. But the butterflies in your stomach won’t listen. 10 + 11 + You don’t want to get carried away, but deep down you already know it: 12 + 13 + **You’re onto something.** 14 + 15 + This idea turns into a project. The first commit is just 500 lines. But in a few days, you build it up just enough to start using it in real code. A few like-minded people join you in improving it. You learn something new about it every day. 16 + 17 + You’re still skeptical but you can’t pretend to ignore it: 18 + 19 + **This idea has wings.** 20 + 21 + You encounter many obstacles. They require you to make changes. Peculiarly, these changes only make the original idea stronger. Usually, you feel like you’re *creating* something. But this time, it feels like you are *discovering* something as if it already existed. You’ve chosen a principle and followed it to the conclusion. 22 + 23 + By now, you’re convinced: 24 + 25 + **This idea deserves to be heard.** 26 + 27 + --- 28 + 29 + If you work at a bureaucratic company, maybe you fight the legal department to open source it. If you are a freelancer, maybe you keep polishing it late at night after the client work is done. Perhaps you wish you were paid for it. But nobody knows about your project just yet. You’re hoping that they will. Someday. 30 + 31 + You pull yourself together to get it ready for the first release. You write more tests, set up the CI, create extensive documentation. You design a beautiful landing page. You’re ready to share your idea with the whole wide world. 32 + 33 + Finally, it’s the launch day. You publish the project on GitHub. You tweet about it and submit the landing page to the popular open source news aggregators. 34 + 35 + ```bash 36 + git push origin master 37 + npm publish 38 + ``` 39 + 40 + **You’re anxious to hear the world’s take on your idea.** 41 + 42 + Maybe they’ll love it. Maybe they’ll hate it. 43 + 44 + All you know is it deserves to be heard. 45 + 46 + --- 47 + 48 + Congratulations! 49 + 50 + Your project hit the front page of a popular news aggregator. Somebody visible in the community tweeted about it too. What are they saying? 51 + 52 + Your heart sinks. 53 + 54 + It’s not that people *didn’t like* the project. You know it has tradeoffs and expected people to talk about them. But that’s not what happened. 55 + 56 + **Instead, the comments are largely *irrelevant* to your idea.** 57 + 58 + The top comment thread picks on the coding style in a README example. It turns into an argument about indentation with over a hundred replies and a brief history of how different programming languages approached formatting. There are obligatory mentions of gofmt and Python. Have you tried Prettier? 59 + 60 + Somebody mentions that open source projects shouldn’t have beautiful landing pages because it’s misleading marketing. What if a junior developer falls for it without fully understanding the fundamentals? 61 + 62 + In a response, somebody argues the landing page design is boring. Additionally, it’s broken in Firefox. Clearly, this means the project author doesn’t care about the open web. Is the web as we know it dying? It’s time for some game theory... 63 + 64 + The next comment is a generic observation about the nature of abstractions, and how they can lead to too much “boilerplate” (or, alternatively, “magic”). The top reply explains that one shouldn’t confuse “simple” with “easy”. Actually, Rich Hickey gave a very good talk about this. Have you watched it? 65 + 66 + Finally, why do we need libraries at all? Some languages do well with a built-in standard library. Is npm a mistake? The leftpad accident could happen again. Should we build npm right into the browser? What about the standards? 67 + 68 + **Confused, you close the tab.** 69 + 70 + --- 71 + 72 + What happened? 73 + 74 + --- 75 + 76 + It might be that your idea is simply not as interesting as you thought. That happens. It might also be that you poorly explained it for a casual visitor. 77 + 78 + However, there might be another reason why you didn’t get relevant feedback. 79 + 80 + **We tend to discuss things that are easy to talk about.** 81 + 82 + Universal shared experiences are easy to talk about. That includes topics like code formatting, verbosity vs magic, configuration vs convention, differences in the community cultures, scandals, tech interviews, industry gossip, macro trends and design opinions. We have a shared vocabulary for all of those things. 83 + 84 + We are also constantly pattern-matching. If some pattern triggers an emotional response (whether relevant or irrelevant to the presented idea), we’ll likely base the first impression on it. Learning by association is a tremendously valuable mental shortcut. However, familiar style may obscure the novel substance. 85 + 86 + **If your idea really _is_ new, there might be no shared vocabulary to discuss it yet.** 87 + 88 + The problem it’s solving might be so ingrained that we don’t even notice it. It’s the elephant in the room. **And we can’t discuss what we never named.** 89 + 90 + --- 91 + 92 + How do you give a name to a problem? 93 + 94 + The same way humans always did. 95 + 96 + **By telling a story.** 97 + 98 + Name it, and they will come.
-1
public/next.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
+324
public/npm-audit-broken-by-design/index.md
··· 1 + --- 2 + title: 'npm audit: Broken by Design' 3 + date: '2021-07-07' 4 + spoiler: "Found 99 vulnerabilities (84 moderately irrelevant, 15 highly irrelevant)" 5 + --- 6 + 7 + Security is important. Nobody wants to be the person advocating for less security. So nobody wants to say it. But somebody has to say it. 8 + 9 + So I guess I’ll say it. 10 + 11 + **The way `npm audit` works is broken. Its rollout as a default after every `npm install` was rushed, inconsiderate, and inadequate for the front-end tooling.** 12 + 13 + Have you heard the story about [the boy who cried wolf?](https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf) Spoiler alert: the wolf eats the sheep. If we don’t want our sheep to be eaten, we need better tools. 14 + 15 + As of today, `npm audit` is a stain on the entire npm ecosystem. The best time to fix it was before rolling it out as a default. The next best time to fix it is now. 16 + 17 + In this post, I will briefly outline how it works, why it’s broken, and what changes I’m hoping to see. 18 + 19 + --- 20 + 21 + *Note: this article is written with a critical and somewhat snarky tone. I understand it’s super hard to maintain massive projects like Node.js/npm, and that mistakes may take a while to become become apparent. I am frustrated only at the situation, not at the people involved. I kept the snarky tone because the level of my frustration has increased over the years, and I don’t want to pretend that the situation isn’t as dire as it really is. Most of all I am frustrated to see all the people for whom this is the first programming experience, as well as all the people who are blocked from deploying their changes due to irrelevant warnings. I am excited that [this issue is being considered](https://twitter.com/bitandbang/status/1412803378279759872) and I will do my best to provide input on the proposed solutions! 💜* 22 + 23 + --- 24 + 25 + ## How does npm audit work? 26 + 27 + *[Skip ahead](#why-is-npm-audit-broken) if you already know how it works.* 28 + 29 + Your Node.js application has a dependency tree. It might look like this: 30 + 31 + ``` 32 + your-app 33 + - view-library@1.0.0 34 + - design-system@1.0.0 35 + - model-layer@1.0.0 36 + - database-layer@1.0.0 37 + - network-utility@1.0.0 38 + ``` 39 + 40 + Most likely, it’s a lot deeper. 41 + 42 + Now say there’s a vulnerability discovered in `network-utility@1.0.0`: 43 + 44 + ``` 45 + your-app 46 + - view-library@1.0.0 47 + - design-system@1.0.0 48 + - model-layer@1.0.0 49 + - database-layer@1.0.0 50 + - network-utility@1.0.0 (Vulnerable!) 51 + ``` 52 + 53 + This gets published in a special registry that `npm` will access next time you run `npm audit`. Since npm v6+, you’ll learn about this after every `npm install`: 54 + 55 + ``` 56 + 1 vulnerabilities (0 moderate, 1 high) 57 + 58 + To address issues that do not require attention, run: 59 + npm audit fix 60 + 61 + To address all issues (including breaking changes), run: 62 + npm audit fix --force 63 + ``` 64 + 65 + You run `npm audit fix`, and npm tries to install the latest `network-utility@1.0.1` with the fix in it. As long as `database-layer` specifies that it depends not on *exactly* on `network-utility@1.0.0` but some permissible range that includes `1.0.1`, the fix “just works” and you get a working application: 66 + 67 + ``` 68 + your-app 69 + - view-library@1.0.0 70 + - design-system@1.0.0 71 + - model-layer@1.0.0 72 + - database-layer@1.0.0 73 + - network-utility@1.0.1 (Fixed!) 74 + ``` 75 + 76 + Alternatively, maybe `database-layer@1.0.0` depends strictly on `network-utility@1.0.0`. In that case, the maintainer of `database-layer` needs to release a new version too, which would allow `network-utility@1.0.1` instead: 77 + 78 + ``` 79 + your-app 80 + - view-library@1.0.0 81 + - design-system@1.0.0 82 + - model-layer@1.0.0 83 + - database-layer@1.0.1 (Updated to allow the fix.) 84 + - network-utility@1.0.1 (Fixed!) 85 + ``` 86 + 87 + Finally, if there is no way to gracefully upgrade the tree, you could try `npm audit fix --force`. This is supposed to be used if `database-layer` doesn’t accept the new version of `network-utility` and _also_ doesn’t release an update to accept it. So you’re kind of taking matters in your own hands, potentially risking breaking changes. Seems like a reasonable option to have. 88 + 89 + **This is how `npm audit` is supposed to work in theory.** 90 + 91 + As someone wise said, in theory there is no difference between theory and practice. But in practice there is. And that’s where all the fun starts. 92 + 93 + ## Why is npm audit broken? 94 + 95 + Let’s see how this works in practice. I’ll use Create React App for my testing. 96 + 97 + If you’re not familiar with it, it’s an integration facade that combines multiple other tools, including Babel, webpack, TypeScript, ESLint, PostCSS, Terser, and others. Create React App takes your JavaScript source code and converts it into a static HTML+JS+CSS folder. **Notably, it does *not* produce a Node.js app.** 98 + 99 + Let’s create a new project! 100 + 101 + ``` 102 + npx create-react-app myapp 103 + ``` 104 + 105 + Immediately upon creating a project, I see this: 106 + 107 + ``` 108 + found 5 vulnerabilities (3 moderate, 2 high) 109 + run `npm audit fix` to fix them, or `npm audit` for details 110 + ``` 111 + 112 + Oh no, that seems bad! My just-created app is already vulnerable! 113 + 114 + Or so npm tells me. 115 + 116 + Let’s run `npm audit` to see what’s up. 117 + 118 + ### First “vulnerability” 119 + 120 + Here is the first problem reported by `npm audit`: 121 + 122 + ``` 123 + ┌───────────────┬──────────────────────────────────────────────────────────────┐ 124 + │ Moderate │ Regular Expression Denial of Service │ 125 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 126 + │ Package │ browserslist │ 127 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 128 + │ Patched in │ >=4.16.5 │ 129 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 130 + │ Dependency of │ react-scripts │ 131 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 132 + │ Path │ react-scripts > react-dev-utils > browserslist │ 133 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 134 + │ More info │ https://npmjs.com/advisories/1747 │ 135 + └───────────────┴──────────────────────────────────────────────────────────────┘ 136 + ``` 137 + 138 + Apparently, `browserslist` is vulnerable. What’s that and how is it used? Create React App generates CSS files optimized for the browsers you target. For example, you can say you only target modern browsers in your `package.json`: 139 + 140 + ```jsx 141 + "browserslist": { 142 + "production": [ 143 + ">0.2%", 144 + "not dead", 145 + "not op_mini all" 146 + ], 147 + "development": [ 148 + "last 1 chrome version", 149 + "last 1 firefox version", 150 + "last 1 safari version" 151 + ] 152 + } 153 + ``` 154 + 155 + Then it won’t include outdated flexbox hacks in the output. Since multiple tools rely on the same configuration format for the browsers you target, Create React App uses the shared `browserslist` package to parse the configuration file. 156 + 157 + So what’s the vulnerability here? [“Regular Expression Denial of Service”](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) means that there is a regex in `browserslist` that, with malicious input, could become very slow. So an attacker can craft a special configuration string that, when passed to `browserslist`, could slow it down exponentially. This sounds bad... 158 + 159 + Wait, what?! Let’s remember how your app works. You have a configuration file _on your machine_. You _build_ your project. You get static HTML+CSS+JS in a folder. You put it on static hosting. There is simply **no way** for your application user to affect your `package.json` configuration. **This doesn’t make any sense.** If the attacker already has access to your machine and can change your configuration files, you have a much bigger problem than slow regular expressions! 160 + 161 + Okay, so I guess this “Moderate” “vulnerability” was neither moderate nor a vulnerability in the context of a project. Let’s keep going. 162 + 163 + **Verdict: this “vulnerability” is absurd in this context.** 164 + 165 + ### Second “vulnerability” 166 + 167 + Here is the next issue `npm audit` has helpfully reported: 168 + 169 + ``` 170 + ┌───────────────┬──────────────────────────────────────────────────────────────┐ 171 + │ Moderate │ Regular expression denial of service │ 172 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 173 + │ Package │ glob-parent │ 174 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 175 + │ Patched in │ >=5.1.2 │ 176 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 177 + │ Dependency of │ react-scripts │ 178 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 179 + │ Path │ react-scripts > webpack-dev-server > chokidar > glob-parent │ 180 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 181 + │ More info │ https://npmjs.com/advisories/1751 │ 182 + └───────────────┴──────────────────────────────────────────────────────────────┘ 183 + ``` 184 + 185 + Let’s look at the `webpack-dev-server > chokidar > glob-parent` dependency chain. Here, `webpack-dev-server` is a **development-only** server that’s used to quickly serve your app **locally**. It uses `chokidar` to watch your filesystem for changes (such as when you save a file in your editor). And it uses [`glob-parent`](https://www.npmjs.com/package/glob-parent) in order to extract a part of the filesystem path from a filesystem watch pattern. 186 + 187 + Unfortunately, `glob-parent` is vulnerable! If an attacker supplies a specially crafted filepath, it could make this function exponentially slow, which would... 188 + 189 + Wait, what?! The development server is on your computer. The files are on your computer. The file watcher is using the configuration that *you* have specified. None of this logic leaves your computer. If your attacker is sophisticated enough to log into *your machine* during local development, the last thing they’ll want to do is to craft special long filepaths to slow down your development. They’ll want to steal your secrets instead. **So this whole threat is absurd.** 190 + 191 + Looks like this “Moderate” “vulnerability” was neither moderate nor a vulnerability in the context of a project. 192 + 193 + **Verdict: this “vulnerability” is absurd in this context.** 194 + 195 + ### Third “vulnerability” 196 + 197 + Let’s have a look at this one: 198 + 199 + ``` 200 + ┌───────────────┬──────────────────────────────────────────────────────────────┐ 201 + │ Moderate │ Regular expression denial of service │ 202 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 203 + │ Package │ glob-parent │ 204 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 205 + │ Patched in │ >=5.1.2 │ 206 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 207 + │ Dependency of │ react-scripts │ 208 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 209 + │ Path │ react-scripts > webpack > watchpack > watchpack-chokidar2 > │ 210 + │ │ chokidar > glob-parent │ 211 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 212 + │ More info │ https://npmjs.com/advisories/1751 │ 213 + └───────────────┴──────────────────────────────────────────────────────────────┘ 214 + ``` 215 + 216 + Wait, it’s the same thing as above, but through a different dependency path. 217 + 218 + **Verdict: this “vulnerability” is absurd in this context.** 219 + 220 + ### Fourth “vulnerability” 221 + 222 + Oof, this one looks really bad! **`npm audit` has the nerve to show it in red color:** 223 + 224 + ``` 225 + ┌───────────────┬──────────────────────────────────────────────────────────────┐ 226 + │ High │ Denial of Service │ 227 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 228 + │ Package │ css-what │ 229 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 230 + │ Patched in │ >=5.0.1 │ 231 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 232 + │ Dependency of │ react-scripts │ 233 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 234 + │ Path │ react-scripts > @svgr/webpack > @svgr/plugin-svgo > svgo > │ 235 + │ │ css-select > css-what │ 236 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 237 + │ More info │ https://npmjs.com/advisories/1754 │ 238 + └───────────────┴──────────────────────────────────────────────────────────────┘ 239 + ``` 240 + 241 + What is this “high” severity issue? “Denial of service.” I don’t want service to be denied! That would be really bad... Unless... 242 + 243 + Let’s look at the [issue](https://www.npmjs.com/advisories/1754). Apparently [`css-what`](https://www.npmjs.com/package/css-what), which is a parser for CSS selectors, can be slow with specially crafted input. This parser is used by a plugin that generates React components from SVG files. 244 + 245 + So what this means is that if the attacker takes control of my development machine or my source code, they put a special SVG file that will have a specially crafted CSS selector in it, which will make my build slow. That checks out... 246 + 247 + Wait, what?! If the attacker can modify my app’s source code, they’ll probably just put a bitcoin miner in it. Why would they add SVG files into my app, unless you can mine bitcoins with SVG? Again, this doesn’t make *any* sense. 248 + 249 + **Verdict: this “vulnerability” is absurd in this context.** 250 + 251 + So much for the “high” severity. 252 + 253 + ### Fifth “vulnerability” 254 + 255 + ``` 256 + ┌───────────────┬──────────────────────────────────────────────────────────────┐ 257 + │ High │ Denial of Service │ 258 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 259 + │ Package │ css-what │ 260 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 261 + │ Patched in │ >=5.0.1 │ 262 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 263 + │ Dependency of │ react-scripts │ 264 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 265 + │ Path │ react-scripts > optimize-css-assets-webpack-plugin > cssnano │ 266 + │ │ > cssnano-preset-default > postcss-svgo > svgo > css-select │ 267 + │ │ > css-what │ 268 + ├───────────────┼──────────────────────────────────────────────────────────────┤ 269 + │ More info │ https://npmjs.com/advisories/1754 │ 270 + 271 + └───────────────┴──────────────────────────────────────────────────────────────┘ 272 + ``` 273 + 274 + This is just the same exact thing as above. 275 + 276 + **Verdict: this “vulnerability” is absurd in this context.** 277 + 278 + ### Shall we keep going? 279 + 280 + So far the boy has cried wolf five times. Two of them are duplicates. The rest are absurd non-issues in the context of how these dependencies are used. 281 + 282 + Five false alarms wouldn’t be too bad. 283 + 284 + **Unfortunately, there are hundreds.** 285 + 286 + Here are a [few](https://github.com/facebook/create-react-app/issues/11053) [typical](https://github.com/facebook/create-react-app/issues/11092) threads, but there are many more [linked from here](https://github.com/facebook/create-react-app/issues/11174): 287 + 288 + <img src="https://imgur.com/ABDK4Ky.png" alt="Screenshot of many GH threads" /> 289 + 290 + **I’ve spent several hours looking through every `npm audit` issue reported to us over the last several months, and they all appear to be false positives in the context of a build tool dependency like Create React App.** 291 + 292 + Of course, they are possible to fix. We could relax some of the top-level dependencies to not be exact (leading to bugs in patches slipping in more often). We could make more releases just to stay ahead of this security theater. 293 + 294 + But this is inadequate. Imagine if your tests failed 99% of the times for bogus reasons! This wastes person-decades of effort and makes everyone miserable: 295 + 296 + * **It makes beginners miserable** because they run into this as their first programming experience in the Node.js ecosystem. As if installing Node.js/npm was not confusing enough (good luck if you added `sudo` somewhere because a tutorial told you), this is what they’re greeted with when they try examples online or even when they create a project. A beginner doesn’t know what a RegExp *is*. Of course they don’t have the know-how to be able to tell whether a RegExp DDoS or prototype pollution is something to worry about when they’re using a build tool to produce a static HTML+CSS+JS site. 297 + * **It makes experienced app developers miserable** because they have to either waste time doing obviously unnecessary work, or fight with their security departments trying to explain how `npm audit` is a broken tool unsuitable for real security audits _by design_. Yeah, somehow it was made a default in this state. 298 + * **It makes maintainers miserable** because instead of working on bugfixes and improvements, they have to pull in bogus vulnerability fixes that can’t possibly affect their project because otherwise their users are frustrated, scared, or both. 299 + * **Someday, it will make our users miserable** because we have trained an entire generation of developers to either not understand the warnings due to being overwhelmed, or to simply _ignore_ them because they always show up but the experienced developers (correctly) tell them there is no real issue in each case. 300 + 301 + It doesn’t help that `npm audit fix` (which the tool suggests using) is buggy. I ran `npm audit fix --force` today and it **downgraded** the main dependency to a three-year-old version with actual _real_ vulnerabilities. Thanks, npm, great job. 302 + 303 + ## What next? 304 + 305 + I don’t know how to solve this. But I didn’t add this system in the first place, so I’m probably not the best person to solve it. All I know is it’s horribly broken. 306 + 307 + There are a few possible solutions that I have seen. 308 + 309 + * **Move dependency to `devDependencies` if it doesn’t run in production.** This offers a way to specify that some dependency isn’t used in production code paths, so there is no risk associated with it. However, this solution is flawed: 310 + - `npm audit` still warns for development dependencies by default. You have to _know_ to run `npm audit --production` to not see the warnings from development dependencies. People who know to do that probably already don’t trust it anyway. This also doesn’t help beginners or people working at companies whose security departments want to audit everything. 311 + - `npm install` still uses information from plain `npm audit`, so you will effectively still see all the false positives every time you install something. 312 + - As any security professional will tell you, development dependencies actually _are_ an attack vector, and perhaps one of the most dangerous ones because it’s so hard to detect and the code runs with high trust assumptions. **This is why the situation is so bad in particular: any real issue gets buried below dozens of non-issues that `npm audit` is training people and maintainers to ignore.** It’s only a matter of time until this happens. 313 + * **Inline all dependencies during publish.** This is what I’m increasingly seeing packages similar to Create React App do. For example, both [Vite](https://unpkg.com/browse/vite@2.4.1/dist/node/) and [Next.js](https://unpkg.com/browse/next@11.0.1/dist/) simply bundle their dependencies directly in the package instead of relying on the npm `node_modules` mechanism. From a maintainer’s point of view, [the upsides are clear](https://github.com/vitejs/vite/blob/main/.github/contributing.md#notes-on-dependencies): you get faster boot time, smaller downloads, and — as a nice bonus — no bogus vulnerability reports from your users. It’s a neat way to game the system but I’m worried about the incentives npm is creating for the ecosystem. Inlining dependencies kind of goes against the whole point of npm. 314 + * **Offer some way to counter-claim vulnerability reports.** The problem is not entirely unknown to Node.js and npm, of course. Different people have worked on different suggestions to fix it. For example, there is a [proposal](https://github.com/npm/rfcs/pull/18) for a way to manually resolve audit warnings so that they don’t display again. However, this still places the burden on app users, which don’t necessarily have context on what vulnerabilities deeper in the tree are real or bogus. I also have a [proposal](https://twitter.com/dan_abramov/status/1412380714012594178): I need a way to mark for my users that a certain vulnerability can’t possibly affect them. If you don’t trust my judgement, why are you running my code on your computer? I’d be happy to discuss other options too. 315 + 316 + The root of the issue is that npm added a default behavior that, in many situations, leads to a 99%+ false positive rate, creates an incredibly confusing first programming experience, makes people fight with security departments, makes maintainers never want to deal with Node.js ecosystem ever again, and at some point will lead to actually bad vulnerabilities slipping in unnnoticed. 317 + 318 + Something has to be done. 319 + 320 + In the meantime, I am planning to close all GitHub issues from `npm audit` that I see going forward that don’t correspond to a _real_ vulnerability that can affect the project. I invite other maintainers to adopt the same policy. This will create frustration for our users, but the core of the issue is with npm. I am done with this security theater. Node.js/npm have all the power to fix the problem. I am in contact with them, and I hope to see this problem prioritized. 321 + 322 + Today, `npm audit` is broken by design. 323 + 324 + Beginners, experienced developers, maintainers, security departments, and, most importantly — our users — deserve better.
+39
public/on-let-vs-const/index.md
··· 1 + --- 2 + title: On let vs const 3 + date: '2019-12-22' 4 + spoiler: So which one should I use? 5 + --- 6 + 7 + My [previous post](/what-is-javascript-made-of/) included this paragraph: 8 + 9 + >**`let` vs `const` vs `var`**: Usually you want `let`. If you want to forbid assignment to this variable, you can use `const`. (Some codebases and coworkers are pedantic and force you to use `const` when there is only one assignment.) 10 + 11 + This turned out to be very controversial, sparking conversations on Twitter and Reddit. It seems that the majority view (or at least, the most vocally expressed view) is that one should *use `const` wherever possible,* only falling back to `let` where necessary, as can be enforced with the [`prefer-const`](https://eslint.org/docs/rules/prefer-const) ESLint rule. 12 + 13 + In this post, I will briefly summarize some of the arguments and counter-arguments I've encountered, as well as my personal conclusion on this topic. 14 + 15 + ## Why `prefer-const` 16 + 17 + * **One Way to Do It**: It is mental overhead to have to choose between `let` and `const` every time. A rule like "always use `const` where it works" lets you stop thinking about it and can be enforced by a linter. 18 + * **Reassignments May Cause Bugs**: In a longer function, it can be easy to miss when a variable is reassigned. This may cause bugs. Particularly in closures, `const` gives you confidence you'll always "see" the same value. 19 + * **Learning About Mutation**: Folks new to JavaScript often get confused thinking `const` implies immutability. However, one could argue that it's important to learn the difference between variable mutation and assignment anyway, and preferring `const` forces you to confront this distinction early on. 20 + * **Meaningless Assignments**: Sometimes, an assignment doesn't make sense at all. For example, with React Hooks, the values you get from a Hook like `useState` are more like parameters. They flow in one direction. Seeing an error on their assignment helps you learn earlier about the React data flow. 21 + * **Performance Benefits**: There are occasional claims that JavaScript engines could make code using `const` run faster due to the knowledge the variable won't be reassigned. 22 + 23 + ## Why Not `prefer-const` 24 + 25 + * **Loss of Intent**: If we force `const` everywhere it can work, we lose the ability to communicate whether it was *important* for something to not be reassigned. 26 + * **Confusion with Immutability**: In every discussion about why you should prefer `const`, someone always confuses with immutability. This is unsurprising, as both assignment and mutation use the same `=` operator. In response, people are usually told that they should "just learn the language". However, the counter-argument is that if a feature that prevents mostly beginner mistakes is confusing to beginners, it isn't very helpful. And unfortunately, it doesn't help prevent mutation mistakes which span across modules and affect everyone. 27 + * **Pressure to Avoid Redeclaring**: A `const`-first codebase creates a pressure to not use `let` for conditionally assigned variables. For example, you might write `const a = cond ? b : c` instead of an `if` condition, even if both `b` and `c` branches are convoluted and giving them explicit names is awkward. 28 + * **Reassignments May Not Cause Bugs**: There are three common cases when reassignments cause bugs: when the scope is very large (such as module scope or huge functions), when the value is a parameter (so it's unexpected that it would be equal to something other than what was passed), and when a variable is used in a nested function. However, in many codebases most variables won't satisfy either of those cases, and parameters can't be marked as constant at all. 29 + * **No Performance Benefits**: It is my understanding that the engines are already aware of which variables are only assigned once -- even if you use `var` or `let`. If we insist on speculating, we could just as well speculate that extra checks can *create* performance cost rather than reduce it. But really, engines are smart. 30 + 31 + ## My Conclusion 32 + 33 + I don't care. 34 + 35 + I would use whatever convention already exists in the codebase. 36 + 37 + If you care, use a linter that automates checking and fixing this so that changing `let` to `const` doesn't become a delay in code review. 38 + 39 + Finally, remember that linters exist to serve *you*. If a linter rule annoys you and your team, delete it. It may not be worth it. Learn from your own mistakes.
+23
public/optimized-for-change/index.md
··· 1 + --- 2 + title: Optimized for Change 3 + date: '2018-12-12' 4 + spoiler: What makes a great API? 5 + --- 6 + 7 + What makes a great API? 8 + 9 + *Good* API design is memorable and unambiguous. It encourages readable, correct and performant code, and helps developers fall into [the pit of success](https://blog.codinghorror.com/falling-into-the-pit-of-success/). 10 + 11 + I call these design aspects “first order” because they are the first things a library developer tends to focus on. You might have to compromise on some of them and make tradeoffs but at least they’re always on your mind. 12 + 13 + However, unless you’re sending a rover to Mars, your code will probably change over time. And so will the code of your API consumers. 14 + 15 + The best API designers I know don’t stop at the “first order” aspects like readability. They dedicate just as much, if not more, effort to what I call the “second order” API design: **how code using this API would evolve over time.** 16 + 17 + A slight change in requirements can make the most elegant code fall apart. 18 + 19 + *Great* APIs anticipate that. They anticipate that you’ll want to move some code. Copy and paste some part. Rename it. Unify special cases into a generic reusable helper. Unwind an abstraction back into specific cases. Add a hack. Optimize a bottleneck. Throw away a part and start it anew. Make a mistake. Navigate between the cause and the effect. Fix a bug. Review the fix. 20 + 21 + Great APIs not only let you fall into a pit of success, but help you *stay* there. 22 + 23 + They’re optimized for change.
public/preparing-for-tech-talk-part-1-motivation/cauldron.jpg

This is a binary file and will not be displayed.

+84
public/preparing-for-tech-talk-part-1-motivation/index.md
··· 1 + --- 2 + title: 'Preparing for a Tech Talk, Part 1: Motivation' 3 + date: '2018-12-26' 4 + spoiler: Here’s my recipe for a good talk idea. 5 + --- 6 + 7 + I’ve done a [few](https://www.youtube.com/watch?v=xsSnOQynTHs) [tech](https://www.youtube.com/watch?v=nLF0n9SACd4) [talks](https://www.youtube.com/watch?v=dpw9EHDh2bM) that I think went well. 8 + 9 + Sometimes people ask me how I prepare for a talk. For every speaker, the answer is very personal. I’m just sharing what works for me. 10 + 11 + **This is the first post in a series** where I explain my process preparing for a tech talk — from conceiving the idea to the actual day of the presentation: 12 + 13 + * **Preparing for a Tech Talk, Part 1: Motivation (*this post*)** 14 + * **[Preparing for a Tech Talk, Part 2: What, Why, and How](/preparing-for-tech-talk-part-2-what-why-and-how/)** 15 + * **[Preparing for a Tech Talk, Part 3: Content](/preparing-for-tech-talk-part-3-content/)** 16 + * To be continued 17 + 18 + <p /> 19 + 20 + --- 21 + 22 + **In this post, I will only focus on the first step: why and how I pick a topic.** It’s not rich in practical tips but might help you ask yourself the right questions. 23 + 24 + --- 25 + 26 + What motivates *you* to give a talk? 27 + 28 + Maybe giving talks is a part of your current job. Maybe you want to gain more recognition in the industry so you can land a better job or get a raise. Maybe you’re out there to bring more attention to your hobby or work project. 29 + 30 + We’ll call these motivations *external*. They are about what other people think of you and your work. But if you already had all the respect and money that you wanted, would you still choose to give a talk? Why? 31 + 32 + Maybe you find it rewarding to teach people. Maybe you enjoy learning, and giving a talk is a nice excuse to dig deeper. Maybe you want to start or change the conversation about a topic. Maybe you want to amplify or critique an idea. 33 + 34 + Such *internal* motivations aren’t a proxy for another desire like professional recognition. These are the things that have intrinsic value to you. Different people are driven by different internal motivations. It’s helpful to be aware of yours. You can sometimes trace them all the way back to your childhood. 35 + 36 + For example, here’s mine: 37 + 38 + * **I enjoy sharing ideas that inspire me.** Sometimes, an idea transforms the way I think. It opens many doors that I didn’t even know existed. But it’s lonely behind those doors. I want others to join me so that they can show me even more interesting doors inside. For me, a talk is a way to collect, curate, and amplify ideas that I find tasteful. (As a teenager I made mixtapes for crushes with no interest in my music taste. Now I do talks! Life, uh, finds a way.) 39 + 40 + * **I enjoy re-explaining things in a simpler way.** When I understand an idea, I get a very pleasant feeling — better than eating sweets. But learning doesn’t come easy to me. So when I finally “get” something, I want to share that feeling with the people who are still struggling. I try to remember what it was like *before* the a-ha moment to help others “make the jump” while watching my talks. (I was also insufferable as a child because I insisted that everyone asks me questions. A talk is a more productive way to channel that energy.) 41 + 42 + --- 43 + 44 + Combining these two internal motivations gives me a recipe for a personally satisfying talk: **share an inspiring idea by re-explaining it in a simpler way**. 45 + 46 + --- 47 + 48 + That is my formula. Yours might be different — think about it! Which talks made you feel in a special way? What are the structural similarities between them? (We’ll discuss the talk structure more in the next posts in this series.) 49 + 50 + ![Luna Lovegood invoking a Patronus Charm. Image © 2007 Warner Bros. Ent](./patronus.jpg) 51 + 52 + Giving a talk that’s aligned with your motivations is helpful in several ways: 53 + 54 + 1. **It’s easier to pick a topic.** My formula is “explain an inspiring idea and why you should care about it”. I can create talk proposals by applying this formula to any interesting concept that I learned. I’ll always have something to talk about as long as I’m listening to smart people with good ideas that deserve more exposure. There are many other possible formulas — find yours. 55 + 56 + 2. **It’s less scary on stage.** I get terrified 30 seconds before the talk but *the moment I start talking, I’m in my element*. The drive to share an inspiring idea overtakes the fear of being judged or doing something wrong. (Of course, this only works with good preparation which we’ll talk about in the next posts.) 57 + 58 + 3. **It’s more convincing.** I can’t phrase it better than [Sophie did](https://mobile.twitter.com/sophiebits/status/1077723835481284608): if you’re enthusiastic about a topic, you can get the audience to care too. Enthusiasm doesn’t necessarily mean being loud or waving hands. Even if you’re calm, people can feel when there’s an emotional conviction behind a talk. (This is also why we can vibe to a [song](https://www.youtube.com/watch?v=6SWIwW9mg8s) even if we don’t understand the words.) 59 + 60 + --- 61 + 62 + There’s one more reason it helps when you’re genuinely excited about a topic. Feeling that you’re a part of something bigger does wonders for confidence. 63 + 64 + My talks aren’t about *me* — they’re about an idea, and I’m just a messenger. Thousands of people on the livestream and in the audience aren’t really there to judge me (even if they think so). They came to experience the idea that I brought to share. My role is just to be a conduit from one mind to another. A lot of nerves and pressure from the talks disappeared after internalizing this. 65 + 66 + --- 67 + 68 + Finding a formula that’s consistent with your motivations helps you establish your own voice. But how do you find a *specific* topic to which you can apply it? 69 + 70 + **In my experience, good talks start as conversations.** Somebody explains an idea to me, and then I try to explain it to someone else. I talk about it to a dozen people, and eventually I find explanations that “click”. Sometimes there’s a thought that seems neglected or misunderstood, and I try to get individual people to see it in a different light. 71 + 72 + For me, a talk is just a way to generalize those conversations and make them one-to-many rather than one-to-one. It’s like a “library” you extract out of the “application code” of many in-person and social media conversations. 73 + 74 + So if you want to give a great talk, *talking* to people is a good way to start. 75 + 76 + ![Hermione Granger making a potion. Vials have text imposed on top: "motivations" and "conversations". Cauldron is a metaphor for your talk. Image © 2001 Warner Bros. Ent](./cauldron.jpg) 77 + 78 + --- 79 + 80 + In this post, I described the framework that I find helpful for thinking about talk ideas. Again, I want to emphasize I’m just sharing what works for me — there are [many kinds of talks](https://mobile.twitter.com/jackiehluo/status/1077717283026411520) and your outlook on this may be very different. 81 + 82 + In the next posts in this series, I will talk about preparing the talk outline, slides, rehearsing the talks, and what I do on the day of the presentation. 83 + 84 + **Next in this series: [Preparing for a Tech Talk, Part 2: What, Why, and How](/preparing-for-tech-talk-part-2-what-why-and-how/)**.
public/preparing-for-tech-talk-part-1-motivation/patronus.jpg

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why-beyond-react-16.es.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why-beyond-react-16.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why-hot-reloading.es.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why-hot-reloading.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why-introducing-hooks.es.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why-introducing-hooks.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why.es.png

This is a binary file and will not be displayed.

public/preparing-for-tech-talk-part-2-what-why-and-how/how-what-why.png

This is a binary file and will not be displayed.

+109
public/preparing-for-tech-talk-part-2-what-why-and-how/index.md
··· 1 + --- 2 + title: 'Preparing for a Tech Talk, Part 2: What, Why, and How' 3 + date: '2019-01-07' 4 + spoiler: We need to go deeper. 5 + --- 6 + 7 + I’ve done a [few](https://www.youtube.com/watch?v=xsSnOQynTHs) [tech](https://www.youtube.com/watch?v=nLF0n9SACd4) [talks](https://www.youtube.com/watch?v=dpw9EHDh2bM) that I think went well. 8 + 9 + Sometimes people ask me how I prepare for a talk. For every speaker, the answer is very personal. I’m just sharing what works for me. 10 + 11 + **This is the second post in a series** where I explain my process preparing for a tech talk — from conceiving the idea to the actual day of the presentation: 12 + 13 + * **[Preparing for a Tech Talk, Part 1: Motivation](/preparing-for-tech-talk-part-1-motivation/)** 14 + * **Preparing for a Tech Talk, Part 2: What, Why, and How (*this post*)** 15 + * **[Preparing for a Tech Talk, Part 3: Content](/preparing-for-tech-talk-part-3-content/)** 16 + * To be continued 17 + 18 + <p /> 19 + 20 + --- 21 + 22 + **In this post, I will focus on finding the What, Why, and How of my talk.** Doing this early helps me avoid a lot of headache at a later stage. 23 + 24 + --- 25 + 26 + If you haven’t seen [Inception](https://en.wikipedia.org/wiki/Inception), watch it today. It’s an enjoyable blockbuster with mindbending visuals and a stimulating plot. But as [Ryan Florence](https://mobile.twitter.com/ryanflorence) taught me, it also contains good advice for creating a memorable talk. 27 + 28 + That movie is about putting ideas into other people’s heads while they sleep. This might sound a bit invasive (and is illegal in the movie). But if you signed up to give a tech talk, that’s a pretty accurate description of your challenge. 29 + 30 + --- 31 + 32 + **What is the one thing that you want people to take away from your talk?** I try to formulate it as a sentence early on. This idea shouldn’t be longer than a dozen words. People will forget most of what you say so you need to pick carefully *what* you want to stick. It’s the seed you want to plant in their heads. 33 + 34 + ![Spinning top from the Inception movie](./totem.jpg) 35 + 36 + For example, here’s the core ideas of my talks. 37 + 38 + * [Hot Reloading](https://www.youtube.com/watch?v=xsSnOQynTHs): “Functional principles improve the development workflow.” 39 + 40 + * [Beyond React 16](https://www.youtube.com/watch?v=nLF0n9SACd4): “Waiting for CPU and IO has a unified solution.” 41 + 42 + * [Introducing Hooks](https://www.youtube.com/watch?v=dpw9EHDh2bM): “Hooks make stateful logic reusable.” 43 + 44 + I don’t always explicitly *say* the central idea out loud or write it on a slide, but it is always the intellectual backbone of my talk. Everything I say and show must ultimately work towards supporting this idea. I want to prove it to you. 45 + 46 + --- 47 + 48 + An idea is the **“What”** of my talk. But there is also **“How”** and **“Why”**: 49 + 50 + ![Pyramid: “How” is on top of “What”. “What” is on top of “Why”.](./how-what-why.png) 51 + 52 + **“How”** is my method for delivering the idea to the audience. Personally, I prefer live demos, but there are many things that can work. I will talk more about “How” in the later blog posts in this series. 53 + 54 + We’ve just discussed **“What”** which is the core idea of the talk. It’s the thought I want to plant in your head and the insight I want you to walk away with. It’s what I want people to share with their friends and colleagues. 55 + 56 + Which brings us to **“Why”**. 57 + 58 + --- 59 + 60 + To explain **“Why”**, I’ll quote this dialog from the Inception movie: 61 + 62 + **(warning: spoilers!)** 63 + 64 + >**Cobb:** "I will split up my father's empire." Now, this is obviously an idea that Robert himself will choose to reject. Which is why we need to plant it deep in his subconscious. The subconscious is motivated by emotion, right? Not reason. We need to find a way to translate this into an emotional concept. 65 + > 66 + >**Arthur:** How do you translate a business strategy into an emotion? 67 + > 68 + >**Cobb:** That's what we're here to figure out, right. Now Robert's relationship with his father is stressed, to say the least. 69 + > 70 + >**Eames:** Well can we run with that? We could suggest him breaking up his fathers company as a "screw you" to the old man. 71 + > 72 + >**Cobb:** No, cause I think positive emotion trumps negative emotion every time. We all yearn for reconciliation, for catharsis. We need Robert Fischer to have a positive emotional reaction to all this. 73 + > 74 + >**Eames:** Alright, well, try this? "My father accepts that I want to create for myself, not follow in his footsteps." 75 + > 76 + >**Cobb:** That might work. 77 + 78 + Now, I’m not suggesting that you break up empires with your talk. 79 + 80 + But there must be a [reason](/preparing-for-tech-talk-part-1-motivation/) you get out in front of thousands of people to speak about something. You *believe* in something — and you want others to share that feeling. **This is the “Why” — the emotional core of your talk.** 81 + 82 + --- 83 + 84 + Here’s the example “What”, “Why”, and “How” from my talks. 85 + 86 + <a href="https://www.youtube.com/watch?v=xsSnOQynTHs" target="_blank">![How: “Live demo”. What: “Functional principles improve the developer experience”. Why: “Create your own tools to make programming fun”.](how-what-why-hot-reloading.png)</a> 87 + 88 + *(The above pyramid is for [Hot reloading with time travel](https://www.youtube.com/watch?v=xsSnOQynTHs))* 89 + 90 + 91 + <a href="https://www.youtube.com/watch?v=nLF0n9SACd4" target="_blank">![How: “Live demo”. What: “Waiting for CPU and IO has a unified solution”. Why: “React cares about both user and developer experience”.](how-what-why-beyond-react-16.png)</a> 92 + 93 + *(The above pyramid is for [Beyond React 16](https://www.youtube.com/watch?v=nLF0n9SACd4))* 94 + 95 + <a href="https://www.youtube.com/watch?v=dpw9EHDh2bM" target="_blank">![How: “Live demo”. What: “Hooks make stateful logic reusable. Why: “Hooks reveal the true nature of React”.](how-what-why-introducing-hooks.png)</a> 96 + 97 + *(The above pyramid is for [Introducing Hooks](https://www.youtube.com/watch?v=dpw9EHDh2bM))* 98 + 99 + A memorable talk takes a concise idea, makes the audience care about it, and has a clear and convincing execution. That’s the “What”, “Why”, and “How”. 100 + 101 + --- 102 + 103 + In this post, I described how I organize the core ideas of my talks. Again, I want to emphasize I’m just sharing what works for me — there are [many kinds of talks](https://mobile.twitter.com/jackiehluo/status/1077717283026411520) and your outlook on this may be very different. 104 + 105 + In the next posts in this series, I will talk about preparing the talk outline, slides, rehearsing the talks, and what I do on the day of the presentation. 106 + 107 + **Next in this series: [Preparing for a Tech Talk, Part 3: Content](/preparing-for-tech-talk-part-3-content/)**. 108 + 109 + **Previous in this series: [Preparing for a Tech Talk, Part 1: Motivation](/preparing-for-tech-talk-part-1-motivation/)**.
public/preparing-for-tech-talk-part-2-what-why-and-how/totem.jpg

This is a binary file and will not be displayed.

+138
public/preparing-for-tech-talk-part-3-content/index.md
··· 1 + --- 2 + title: 'Preparing for a Tech Talk, Part 3: Content' 3 + date: '2019-07-10' 4 + spoiler: Turning an idea into a talk. 5 + --- 6 + 7 + I’ve done a [few](https://www.youtube.com/watch?v=xsSnOQynTHs) [tech](https://www.youtube.com/watch?v=nLF0n9SACd4) [talks](https://www.youtube.com/watch?v=dpw9EHDh2bM) that I think went well. 8 + 9 + Sometimes people ask me how I prepare for a talk. For every speaker, the answer is very personal. I’m just sharing what works for me. 10 + 11 + **This is the third post in a series** where I explain my process preparing for a tech talk — from conceiving the idea to the actual day of the presentation: 12 + 13 + * **[Preparing for a Tech Talk, Part 1: Motivation](/preparing-for-tech-talk-part-1-motivation/)** 14 + * **[Preparing for a Tech Talk, Part 2: What, Why, and How](/preparing-for-tech-talk-part-2-what-why-and-how/)** 15 + * **Preparing for a Tech Talk, Part 3: Content (*this post*)** 16 + * To be continued 17 + 18 + <p /> 19 + 20 + --- 21 + 22 + **In this post, I will focus on my process of creating the slides and the actual content of my presentation.** 23 + 24 + --- 25 + 26 + There are two ways to build something. 27 + 28 + You can build **top-down**, where you start with a crude overall outline and then gradually refine each individual part. Or you can build **bottom-up**, starting with a small but polished fragment, and then growing everything else around it. This might remind you of how some image formats always load from top to bottom, while others start blurry at first but then get sharper as more data is loaded. 29 + 30 + I usually **combine these approaches** when preparing talks. 31 + 32 + --- 33 + 34 + ### Top-Down Pass: The Outline 35 + 36 + After I know [what the talk is about](/preparing-for-tech-talk-part-2-what-why-and-how/), **I write a rough outline. It is a bullet point list of every thought I want to include.** It doesn’t need to be polished or clear to anyone other than me. I’m just throwing things at the wall to see what sticks. 37 + 38 + An outline usually starts with a lot of gaps and unknowns: 39 + 40 + ``` 41 + - intro 42 + - hi, I'm Dan 43 + - I work on React 44 + - problems 45 + - wrapper hell 46 + - ??? 47 + - demo 48 + - ??? how to avoid people getting stressed thinking it's a breaking change 49 + - state 50 + - effects 51 + - ??? which example to pick 52 + - maybe explain dependencies 53 + - custom Hooks <----- "aha" moment 54 + - links 55 + - stress there's no breaking changes 56 + - ??? 57 + - something philosophical and reassuring 58 + ``` 59 + 60 + Many initial thoughts in the outline might not make the final cut. In fact, writing an outline is a great way to separate the ideas that contribute to the ["what" and "why"](/preparing-for-tech-talk-part-2-what-why-and-how/) of the talk from the "filler" that should be removed. 61 + 62 + The outline is a living draft. It can be vague at first. I continously tweak the outline as I work on the talk. Eventually, it might end up looking more like this: 63 + 64 + ``` 65 + - intro 66 + - hi, I'm Dan 67 + - I work on React 68 + - problems 69 + - wrapper hell 70 + - long components 71 + - fixing one makes the other worse 72 + - should we give up 73 + - lol mixins? 74 + - crossroads 75 + - maybe we can't fix this 76 + - but what if we can? 77 + - we have a proposal 78 + - no breaking changes 79 + - demo 80 + - state Hook 81 + - more than one state Hook 82 + - mention rules 83 + - effect Hook 84 + - effect cleanup 85 + - custom Hooks <----- "aha" moment 86 + - recap 87 + - no breaking changes 88 + - you can try now 89 + - link to the rfc 90 + - outro 91 + - make it personal 92 + - hook : component :: electron : atom 93 + - logo + "hooks have been here all along" 94 + ``` 95 + 96 + But sometimes pieces don’t fall into place until after all the slides are done. 97 + 98 + **The outline helps me keep the structure digestible.** For my talk structure, I often follow the [“Hero’s Journey”](http://www.tlu.ee/~rajaleid/montaazh/Hero%27s%20Journey%20Arch.pdf) pattern that you’ll find in popular culture everywhere, e.g. in Harry Potter books. You start with some conflict (“Sirius is going after you”, “Death Eaters are crashing the Quidditch Cup”, “Snape takes a shady oath”, etc). Then there’s some setup (buy some books, learn some spells). Eventually there’s an energy peak where we beat the villain. Then Dumbledore says something meta and paradoxical, and we all go back home. 99 + 100 + My mental template for talks looks something like: 101 + 102 + 1. Establish some conflict or a problem to get the viewer interested. 103 + 2. Walk them through the main “aha” moment. (The "what" of my talk.) 104 + 3. Recap how what we did solves the posed problem. 105 + 4. Finish it off with something that appeals to emotions (The "why" of my talk). 106 + - This part lands especially well if there's some unexpected layer or symmetry that only becomes clear at the end. If I get [goosebumps](https://en.wikipedia.org/wiki/Frisson), it's good. 107 + 108 + Of course, a structure like this is just a form — and an overused one. So it’s really up to you to fill it up with engaging material and add your own twist. If the talk content itself isn’t engaging, wrapping it up in a cliche won’t help it. 109 + 110 + **The outline also helps me find inconsistencies.** For example, maybe an idea in the middle needs some other concept that I only introduce later. Then I need to reorder them. The outline provides a bird’s eye view of all the thoughts I wanted to mention, and helps ensure the flow between them is tight and makes sense. 111 + 112 + ### Bottom-Up Pass: The High-Energy Section 113 + 114 + Writing the outline is a top-down process. But I also start working bottom-up on something concrete like the slides or a demo in parallel. 115 + 116 + **In particular, I try to build up a proof of concept of the “high energy” part of my talk as soon as possible.** For example, it could be a moment when a crucial idea is explained or demoed. How do I go about explaining it? What exactly am I going to say or do, and is it going to be sufficient? Do I need slides? Demos? Both? Do I need to use pictures? Animations? What is the exact sequence of my words and actions? Would I want to watch this talk for this explanation alone? 117 + 118 + This part is the hardest for me because I usually end up making many versions that I throw away. It requires a special frame of mind when I can deeply focus, allow myself to try silly things, and then feel free to destroy it all. 119 + 120 + I spend a lot of time picking headers, figuring out the sequencing of a live demo, tweaking the animations, and searching for memes. **Most of this work is throwaway** (e.g. I usually end up deleting all the memes), but this stage really defines the talk for me. My goal is to find the closest route from *not knowing* to *knowing* an idea — so I can share that journey later with the audience. 121 + 122 + After I feel good about the high-energy part of my talk, I check if the outline I wrote before still makes sense. At this point, I often realize that I should throw away 60% of my previous outline, and rewrite it to focus on a smaller idea. 123 + 124 + ### Do Many Dry Runs 125 + 126 + I continue both top-down work (outline) and bottom-up work (building out concrete sections) until there are no more blank spaces. When I have the first draft version of the whole talk, I lock myself in a room and pretend to actually *give* the talk for the first time. It’s messy, I stumble a lot, stop sentences midway and try different ones, and so on — but I get through the whole thing. 127 + 128 + That helps me measure how much I’ll need to cut. The first attempt often ends up much larger than the time slot, but I also often notice that some sections were distracting. So I cut them out, tweak the slides to better match what I want to say, and try to give the whole talk again. 129 + 130 + I repeat this process for several days as I keep polishing the slides and the flow. This is a good time to start practicing with other people. I usually start with a single friend, and later do a few dry runs with a small audience (at most 15 people). This is a good way to get some early feedback, but most importantly it's how I get used to this talk and learn to feel comfortable giving it. 131 + 132 + I prefer not to write down complete sentences or real speaker notes. That stresses me out because I feel the pressure to actually follow them and freak out if I miss something. Instead, I prefer to rehearse the talk enough times (from 3 to 20) that the sentences I want to say for any given slide "come to me" without thinking too much. It's easier to tell a story you've told many times before. 133 + 134 + --- 135 + 136 + In this post, I described how I prepare the content for my talks. In the next post, I will share some tips about what I do on the day of the talk itself. 137 + 138 + **Previous in this series: [Preparing for a Tech Talk, Part 2: What, Why, and How](/preparing-for-tech-talk-part-2-what-why-and-how/)**.
+1127
public/react-as-a-ui-runtime/index.md
··· 1 + --- 2 + title: React as a UI Runtime 3 + date: '2019-02-02' 4 + spoiler: An in-depth description of the React programming model. 5 + cta: 'react' 6 + --- 7 + 8 + Most tutorials introduce React as a UI library. This makes sense because React *is* a UI library. That’s literally what the tagline says! 9 + 10 + ![React homepage screenshot: "A JavaScript library for building user interfaces"](./react.png) 11 + 12 + I’ve written about the challenges of creating [user interfaces](/the-elements-of-ui-engineering/) before. But this post talks about React in a different way — more as a [programming runtime](https://en.wikipedia.org/wiki/Runtime_system). 13 + 14 + **This post won’t teach you anything about creating user interfaces.** But it might help you understand the React programming model in more depth. 15 + 16 + --- 17 + 18 + **Note: If you’re _learning_ React, check out [the docs](https://reactjs.org/docs/getting-started.html#learn-react) instead.** 19 + 20 + <p><font size="60">⚠️</font></p> 21 + 22 + **This is a deep dive — THIS IS NOT a beginner-friendly post.** In this post, I’m describing most of the React programming model from first principles. I don’t explain how to use it — just how it works. 23 + 24 + It’s aimed at experienced programmers and folks working on other UI libraries who asked about some tradeoffs chosen in React. I hope you’ll find it useful! 25 + 26 + **Many people successfully use React for years without thinking about most of these topics.** This is definitely a programmer-centric view of React rather than, say, a [designer-centric one](http://mrmrs.cc/writing/developing-ui/). But I don’t think it hurts to have resources for both. 27 + 28 + With that disclaimer out of the way, let’s go! 29 + 30 + --- 31 + 32 + ## Host Tree 33 + 34 + Some programs output numbers. Other programs output poems. Different languages and their runtimes are often optimized for a particular set of use cases, and React is no exception to that. 35 + 36 + React programs usually output **a tree that may change over time**. It might be a [DOM tree](https://www.npmjs.com/package/react-dom), an [iOS hierarchy](https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/View%20Hierarchy.html), a tree of [PDF primitives](https://react-pdf.org/), or even of [JSON objects](https://reactjs.org/docs/test-renderer.html). However, usually, we want to represent some UI with it. We’ll call it a “*host* tree” because it is a part of the *host environment* outside of React — like DOM or iOS. The host tree usually has [its](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) [own](https://developer.apple.com/documentation/uikit/uiview/1622616-addsubview) imperative API. React is a layer on top of it. 37 + 38 + So what is React useful for? Very abstractly, it helps you write a program that predictably manipulates a complex host tree in response to external events like interactions, network responses, timers, and so on. 39 + 40 + A specialized tool works better than a generic one when it can impose and benefit from particular constraints. React makes a bet on two principles: 41 + 42 + * **Stability.** The host tree is relatively stable and most updates don’t radically change its overall structure. If an app rearranged all its interactive elements into a completely different combination every second, it would be difficult to use. Where did that button go? Why is my screen dancing? 43 + 44 + * **Regularity.** The host tree can be broken down into UI patterns that look and behave consistently (such as buttons, lists, avatars) rather than random shapes. 45 + 46 + **These principles happen to be true for most UIs.** However, React is ill-suited when there are no stable “patterns” in the output. For example, React may help you write a Twitter client but won’t be very useful for a [3D pipes screensaver](https://www.youtube.com/watch?v=Uzx9ArZ7MUU). 47 + 48 + ## Host Instances 49 + 50 + The host tree consists of nodes. We’ll call them “host instances”. 51 + 52 + In the DOM environment, host instances are regular DOM nodes — like the objects you get when you call `document.createElement('div')`. On iOS, host instances could be values uniquely identifying a native view from JavaScript. 53 + 54 + Host instances have their own properties (e.g. `domNode.className` or `view.tintColor`). They may also contain other host instances as children. 55 + 56 + (This has nothing to do with React — I’m describing the host environments.) 57 + 58 + There is usually an API to manipulate host instances. For example, the DOM provides APIs like `appendChild`, `removeChild`, `setAttribute`, and so on. In React apps, you usually don’t call these APIs. That’s the job of React. 59 + 60 + ## Renderers 61 + 62 + A *renderer* teaches React to talk to a specific host environment and manage its host instances. React DOM, React Native, and even [Ink](https://mobile.twitter.com/vadimdemedes/status/1089344289102942211) are React renderers. You can also [create your own React renderer](https://github.com/facebook/react/tree/master/packages/react-reconciler). 63 + 64 + React renderers can work in one of two modes. 65 + 66 + The vast majority of renderers are written to use the “mutating” mode. This mode is how the DOM works: we can create a node, set its properties, and later add or remove children from it. The host instances are completely mutable. 67 + 68 + React can also work in a [“persistent”](https://en.wikipedia.org/wiki/Persistent_data_structure) mode. This mode is for host environments that don’t provide methods like `appendChild()` but instead clone the parent tree and always replace the top-level child. Immutability on the host tree level makes multi-threading easier. [React Fabric](https://facebook.github.io/react-native/blog/2018/06/14/state-of-react-native-2018) takes advantage of that. 69 + 70 + As a React user, you never need to think about these modes. I only want to highlight that React isn’t just an adapter from one mode to another. Its usefulness is orthogonal to the target low-level view API paradigm. 71 + 72 + ## React Elements 73 + 74 + In the host environment, a host instance (like a DOM node) is the smallest building block. In React, the smallest building block is a *React element*. 75 + 76 + A React element is a plain JavaScript object. It can *describe* a host instance. 77 + 78 + ```jsx 79 + // JSX is a syntax sugar for these objects. 80 + // <button className="blue" /> 81 + { 82 + type: 'button', 83 + props: { className: 'blue' } 84 + } 85 + ``` 86 + 87 + A React element is lightweight and has no host instance tied to it. Again, it is merely a *description* of what you want to see on the screen. 88 + 89 + Like host instances, React elements can form a tree: 90 + 91 + ```jsx 92 + // JSX is a syntax sugar for these objects. 93 + // <dialog> 94 + // <button className="blue" /> 95 + // <button className="red" /> 96 + // </dialog> 97 + { 98 + type: 'dialog', 99 + props: { 100 + children: [{ 101 + type: 'button', 102 + props: { className: 'blue' } 103 + }, { 104 + type: 'button', 105 + props: { className: 'red' } 106 + }] 107 + } 108 + } 109 + ``` 110 + 111 + *(Note: I omitted [some properties](/why-do-react-elements-have-typeof-property/) that aren’t important to this explanation.)* 112 + 113 + However, remember that **React elements don’t have their own persistent identity.** They’re meant to be re-created and thrown away all the time. 114 + 115 + React elements are immutable. For example, you can’t change the children or a property of a React element. If you want to render something different later, you will *describe* it with a new React element tree created from scratch. 116 + 117 + I like to think of React elements as being like frames in a movie. They capture what the UI should look like at a specific point in time. They don’t change. 118 + 119 + ## Entry Point 120 + 121 + Each React renderer has an “entry point”. It’s the API that lets us tell React to render a particular React element tree inside a container host instance. 122 + 123 + For example, React DOM entry point is `ReactDOM.render`: 124 + 125 + ```jsx 126 + ReactDOM.render( 127 + // { type: 'button', props: { className: 'blue' } } 128 + <button className="blue" />, 129 + document.getElementById('container') 130 + ); 131 + ``` 132 + 133 + When we say `ReactDOM.render(reactElement, domContainer)`, we mean: **“Dear React, make the `domContainer` host tree match my `reactElement`.”** 134 + 135 + React will look at the `reactElement.type` (in our example, `'button'`) and ask the React DOM renderer to create a host instance for it and set the properties: 136 + 137 + ```jsx{3,4} 138 + // Somewhere in the ReactDOM renderer (simplified) 139 + function createHostInstance(reactElement) { 140 + let domNode = document.createElement(reactElement.type); 141 + domNode.className = reactElement.props.className; 142 + return domNode; 143 + } 144 + ``` 145 + 146 + In our example, effectively React will do this: 147 + 148 + ```jsx{1,2} 149 + let domNode = document.createElement('button'); 150 + domNode.className = 'blue'; 151 + 152 + domContainer.appendChild(domNode); 153 + ``` 154 + 155 + If the React element has child elements in `reactElement.props.children`, React will recursively create host instances for them too on the first render. 156 + 157 + ## Reconciliation 158 + 159 + What happens if we call `ReactDOM.render()` twice with the same container? 160 + 161 + ```jsx{2,11} 162 + ReactDOM.render( 163 + <button className="blue" />, 164 + document.getElementById('container') 165 + ); 166 + 167 + // ... later ... 168 + 169 + // Should this *replace* the button host instance 170 + // or merely update a property on an existing one? 171 + ReactDOM.render( 172 + <button className="red" />, 173 + document.getElementById('container') 174 + ); 175 + ``` 176 + 177 + Again, React’s job is to *make the host tree match the provided React element tree*. The process of figuring out *what* to do to the host instance tree in response to new information is sometimes called [reconciliation](https://reactjs.org/docs/reconciliation.html). 178 + 179 + There are two ways to go about it. A simplified version of React could blow away the existing tree and re-create it from scratch: 180 + 181 + ```jsx 182 + let domContainer = document.getElementById('container'); 183 + // Clear the tree 184 + domContainer.innerHTML = ''; 185 + // Create the new host instance tree 186 + let domNode = document.createElement('button'); 187 + domNode.className = 'red'; 188 + domContainer.appendChild(domNode); 189 + ``` 190 + 191 + But in DOM, this is slow and loses important information like focus, selection, scroll state, and so on. Instead, we want React to do something like this: 192 + 193 + ```jsx 194 + let domNode = domContainer.firstChild; 195 + // Update existing host instance 196 + domNode.className = 'red'; 197 + ``` 198 + 199 + In other words, React needs to decide when to _update_ an existing host instance to match a new React element, and when to create a _new_ one. 200 + 201 + This raises a question of *identity*. The React element may be different every time, but when does it refer to the same host instance conceptually? 202 + 203 + In our example, it’s simple. We used to render a `<button>` as a first (and only) child, and we want to render a `<button>` in the same place again. We already have a `<button>` host instance there so why re-create it? Let’s just reuse it. 204 + 205 + This is pretty close to how React thinks about it. 206 + 207 + **If an element type in the same place in the tree “matches up” between the previous and the next renders, React reuses the existing host instance.** 208 + 209 + Here is an example with comments showing roughly what React does: 210 + 211 + ```jsx{9,10,16,26,27} 212 + // let domNode = document.createElement('button'); 213 + // domNode.className = 'blue'; 214 + // domContainer.appendChild(domNode); 215 + ReactDOM.render( 216 + <button className="blue" />, 217 + document.getElementById('container') 218 + ); 219 + 220 + // Can reuse host instance? Yes! (button → button) 221 + // domNode.className = 'red'; 222 + ReactDOM.render( 223 + <button className="red" />, 224 + document.getElementById('container') 225 + ); 226 + 227 + // Can reuse host instance? No! (button → p) 228 + // domContainer.removeChild(domNode); 229 + // domNode = document.createElement('p'); 230 + // domNode.textContent = 'Hello'; 231 + // domContainer.appendChild(domNode); 232 + ReactDOM.render( 233 + <p>Hello</p>, 234 + document.getElementById('container') 235 + ); 236 + 237 + // Can reuse host instance? Yes! (p → p) 238 + // domNode.textContent = 'Goodbye'; 239 + ReactDOM.render( 240 + <p>Goodbye</p>, 241 + document.getElementById('container') 242 + ); 243 + ``` 244 + 245 + The same heuristic is used for child trees. For example, when we update a `<dialog>` with two `<button>`s inside, React first decides whether to re-use the `<dialog>`, and then repeats this decision procedure for each child. 246 + 247 + ## Conditions 248 + 249 + If React only reuses host instances when the element types “match up” between updates, how can we render conditional content? 250 + 251 + Say we want to first show only an input, but later render a message before it: 252 + 253 + ```jsx{12} 254 + // First render 255 + ReactDOM.render( 256 + <dialog> 257 + <input /> 258 + </dialog>, 259 + domContainer 260 + ); 261 + 262 + // Next render 263 + ReactDOM.render( 264 + <dialog> 265 + <p>I was just added here!</p> 266 + <input /> 267 + </dialog>, 268 + domContainer 269 + ); 270 + ``` 271 + 272 + In this example, the `<input>` host instance would get re-created. React would walk the element tree, comparing it with the previous version: 273 + 274 + * `dialog → dialog`: Can reuse the host instance? **Yes — the type matches.** 275 + * `input → p`: Can reuse the host instance? **No, the type has changed!** Need to remove the existing `input` and create a new `p` host instance. 276 + * `(nothing) → input`: Need to create a new `input` host instance. 277 + 278 + So effectively the update code executed by React would be like: 279 + 280 + ```jsx{1,2,8,9} 281 + let oldInputNode = dialogNode.firstChild; 282 + dialogNode.removeChild(oldInputNode); 283 + 284 + let pNode = document.createElement('p'); 285 + pNode.textContent = 'I was just added here!'; 286 + dialogNode.appendChild(pNode); 287 + 288 + let newInputNode = document.createElement('input'); 289 + dialogNode.appendChild(newInputNode); 290 + ``` 291 + 292 + This is not great because *conceptually* the `<input>` hasn’t been *replaced* with `<p>` — it just moved. We don’t want to lose its selection, focus state, and content due to re-creating the DOM. 293 + 294 + While this problem has an easy fix (which we’ll get to in a minute), it doesn’t occur often in React applications. It’s interesting to see why. 295 + 296 + In practice, you would rarely call `ReactDOM.render` directly. Instead, React apps tend to be broken down into functions like this: 297 + 298 + ```jsx 299 + function Form({ showMessage }) { 300 + let message = null; 301 + if (showMessage) { 302 + message = <p>I was just added here!</p>; 303 + } 304 + return ( 305 + <dialog> 306 + {message} 307 + <input /> 308 + </dialog> 309 + ); 310 + } 311 + ``` 312 + 313 + This example doesn’t suffer from the problem we just described. It might be easier to see why if we use object notation instead of JSX. Look at the `dialog` child element tree: 314 + 315 + ```jsx{12-15} 316 + function Form({ showMessage }) { 317 + let message = null; 318 + if (showMessage) { 319 + message = { 320 + type: 'p', 321 + props: { children: 'I was just added here!' } 322 + }; 323 + } 324 + return { 325 + type: 'dialog', 326 + props: { 327 + children: [ 328 + message, 329 + { type: 'input', props: {} } 330 + ] 331 + } 332 + }; 333 + } 334 + ``` 335 + 336 + **Regardless of whether `showMessage` is `true` or `false`, the `<input>` is the second child and doesn’t change its tree position between renders.** 337 + 338 + If `showMessage` changes from `false` to `true`, React would walk the element tree, comparing it with the previous version: 339 + 340 + * `dialog → dialog`: Can reuse the host instance? **Yes — the type matches.** 341 + * `(null) → p`: Need to insert a new `p` host instance. 342 + * `input → input`: Can reuse the host instance? **Yes — the type matches.** 343 + 344 + And the code executed by React would be similar to this: 345 + 346 + ```jsx 347 + let inputNode = dialogNode.firstChild; 348 + let pNode = document.createElement('p'); 349 + pNode.textContent = 'I was just added here!'; 350 + dialogNode.insertBefore(pNode, inputNode); 351 + ``` 352 + 353 + No input state is lost now. 354 + 355 + ## Lists 356 + 357 + Comparing the element type at the same position in the tree is usually enough to decide whether to reuse or re-create the corresponding host instance. 358 + 359 + But this only works well if child positions are static and don’t re-order. In our example above, even though `message` could be a “hole”, we still knew that the input goes after the message, and there are no other children. 360 + 361 + With dynamic lists, we can’t be sure the order is ever the same: 362 + 363 + ```jsx 364 + function ShoppingList({ list }) { 365 + return ( 366 + <form> 367 + {list.map(item => ( 368 + <p> 369 + You bought {item.name} 370 + <br /> 371 + Enter how many do you want: <input /> 372 + </p> 373 + ))} 374 + </form> 375 + ) 376 + } 377 + ``` 378 + 379 + If the `list` of our shopping items is ever re-ordered, React will see that all `p` and `input` elements inside have the same type, and won’t know to move them. (From React’s point of view, the *items themselves* changed, not their order.) 380 + 381 + The code executed by React to re-order 10 items would be something like: 382 + 383 + ```jsx 384 + for (let i = 0; i < 10; i++) { 385 + let pNode = formNode.childNodes[i]; 386 + let textNode = pNode.firstChild; 387 + textNode.textContent = 'You bought ' + items[i].name; 388 + } 389 + ``` 390 + 391 + So instead of *re-ordering* them, React would effectively *update* each of them. This can create performance issues and possible bugs. For example, the content of the first input would stay reflected in first input *after* the sort — even though conceptually they might refer to different products in your shopping list! 392 + 393 + **This is why React nags you to specify a special property called `key` every time you include an array of elements in your output:** 394 + 395 + ```jsx{5} 396 + function ShoppingList({ list }) { 397 + return ( 398 + <form> 399 + {list.map(item => ( 400 + <p key={item.productId}> 401 + You bought {item.name} 402 + <br /> 403 + Enter how many do you want: <input /> 404 + </p> 405 + ))} 406 + </form> 407 + ) 408 + } 409 + ``` 410 + 411 + A `key` tells React that it should consider an item to be *conceptually* the same even if it has different *positions* inside its parent element between renders. 412 + 413 + When React sees `<p key="42">` inside a `<form>`, it will check if the previous render also contained `<p key="42">` inside the same `<form>`. This works even if `<form>` children changed their order. React will reuse the previous host instance with the same key if it exists, and re-order the siblings accordingly. 414 + 415 + Note that the `key` is only relevant within a particular parent React element, such as a `<form>`. React won’t try to “match up” elements with the same keys between different parents. (React doesn’t have idiomatic support for moving a host instance between different parents without re-creating it.) 416 + 417 + What’s a good value for a `key`? An easy way to answer this is to ask: **when would _you_ say an item is the “same” even if the order changed?** For example, in our shopping list, the product ID uniquely identifies it between siblings. 418 + 419 + ## Components 420 + 421 + We’ve already seen functions that return React elements: 422 + 423 + ```jsx 424 + function Form({ showMessage }) { 425 + let message = null; 426 + if (showMessage) { 427 + message = <p>I was just added here!</p>; 428 + } 429 + return ( 430 + <dialog> 431 + {message} 432 + <input /> 433 + </dialog> 434 + ); 435 + } 436 + ``` 437 + 438 + They are called *components*. They let us create our own “toolbox” of buttons, avatars, comments, and so on. Components are the bread and butter of React. 439 + 440 + Components take one argument — an object hash. It contains “props” (short for “properties”). Here, `showMessage` is a prop. They’re like named arguments. 441 + 442 + ## Purity 443 + 444 + React components are assumed to be pure with respect to their props. 445 + 446 + ```jsx 447 + function Button(props) { 448 + // 🔴 Doesn't work 449 + props.isActive = true; 450 + } 451 + ``` 452 + 453 + In general, mutation is not idiomatic in React. (We’ll talk more about the idiomatic way to update the UI in response to events later.) 454 + 455 + However, *local mutation* is absolutely fine: 456 + 457 + ```jsx{2,5} 458 + function FriendList({ friends }) { 459 + let items = []; 460 + for (let i = 0; i < friends.length; i++) { 461 + let friend = friends[i]; 462 + items.push( 463 + <Friend key={friend.id} friend={friend} /> 464 + ); 465 + } 466 + return <section>{items}</section>; 467 + } 468 + ``` 469 + 470 + We created `items` *while rendering* and no other component “saw” it so we can mutate it as much as we like before handing it off as part of the render result. There is no need to contort your code to avoid local mutation. 471 + 472 + Similarly, lazy initialization is fine despite not being fully “pure”: 473 + 474 + ```jsx 475 + function ExpenseForm() { 476 + // Fine if it doesn't affect other components: 477 + SuperCalculator.initializeIfNotReady(); 478 + 479 + // Continue rendering... 480 + } 481 + ``` 482 + 483 + As long as calling a component multiple times is safe and doesn’t affect the rendering of other components, React doesn’t care if it’s 100% pure in the strict FP sense of the word. [Idempotence](https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation) is more important to React than purity. 484 + 485 + That said, side effects that are directly visible to the user are not allowed in React components. In other words, merely *calling* a component function shouldn’t by itself produce a change on the screen. 486 + 487 + ## Recursion 488 + 489 + How do we *use* components from other components? Components are functions so we *could* call them: 490 + 491 + ```jsx 492 + let reactElement = Form({ showMessage: true }); 493 + ReactDOM.render(reactElement, domContainer); 494 + ``` 495 + 496 + However, this is *not* the idiomatic way to use components in the React runtime. 497 + 498 + Instead, the idiomatic way to use a component is with the same mechanism we’ve already seen before — React elements. **This means that you don’t directly call the component function, but instead let React later do it for you**: 499 + 500 + ```jsx 501 + // { type: Form, props: { showMessage: true } } 502 + let reactElement = <Form showMessage={true} />; 503 + ReactDOM.render(reactElement, domContainer); 504 + ``` 505 + 506 + And somewhere inside React, your component will be called: 507 + 508 + ```jsx 509 + // Somewhere inside React 510 + let type = reactElement.type; // Form 511 + let props = reactElement.props; // { showMessage: true } 512 + let result = type(props); // Whatever Form returns 513 + ``` 514 + 515 + Component function names are by convention capitalized. When the JSX transform sees `<Form>` rather than `<form>`, it makes the object `type` itself an identifier rather than a string: 516 + 517 + ```jsx 518 + console.log(<form />.type); // 'form' string 519 + console.log(<Form />.type); // Form function 520 + ``` 521 + 522 + There is no global registration mechanism — we literally refer to `Form` by name when typing `<Form />`. If `Form` doesn’t exist in local scope, you’ll see a JavaScript error just like you normally would with a bad variable name. 523 + 524 + **Okay, so what does React do when an element type is a function? It calls your component, and asks what element _that_ component wants to render.** 525 + 526 + This process continues recursively and is described in more detail [here](https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html). In short, it looks like this: 527 + 528 + - **You:** `ReactDOM.render(<App />, domContainer)` 529 + - **React:** Hey `App`, what do you render to? 530 + - `App`: I render `<Layout>` with `<Content>` inside. 531 + - **React:** Hey `Layout`, what do you render to? 532 + - `Layout`: I render my children in a `<div>`. My child was `<Content>` so I guess that goes into the `<div>`. 533 + - **React:** Hey `<Content>`, what do you render to? 534 + - `Content`: I render an `<article>` with some text and a `<Footer>` inside. 535 + - **React:** Hey `<Footer>`, what do you render to? 536 + - `Footer`: I render a `<footer>` with some more text. 537 + - **React:** Okay, here you go: 538 + 539 + ```jsx 540 + // Resulting DOM structure 541 + <div> 542 + <article> 543 + Some text 544 + <footer>some more text</footer> 545 + </article> 546 + </div> 547 + ``` 548 + 549 + This is why we say reconciliation is recursive. When React walks the element tree, it might meet an element whose `type` is a component. It will call it and keep descending down the tree of returned React elements. Eventually, we’ll run out of components, and React will know what to change in the host tree. 550 + 551 + The same reconciliation rules we already discussed apply here too. If the `type` at the same position (as determined by index and optional `key`) changes, React will throw away the host instances inside, and re-create them. 552 + 553 + ## Inversion of Control 554 + 555 + You might be wondering: why don’t we just call components directly? Why write `<Form />` rather than `Form()`? 556 + 557 + **React can do its job better if it “knows” about your components rather than if it only sees the React element tree after recursively calling them.** 558 + 559 + ```jsx 560 + // 🔴 React has no idea Layout and Article exist. 561 + // You're calling them. 562 + ReactDOM.render( 563 + Layout({ children: Article() }), 564 + domContainer 565 + ) 566 + 567 + // ✅ React knows Layout and Article exist. 568 + // React calls them. 569 + ReactDOM.render( 570 + <Layout><Article /></Layout>, 571 + domContainer 572 + ) 573 + ``` 574 + 575 + This is a classic example of [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control). There’s a few interesting properties we get by letting React take control of calling our components: 576 + 577 + * **Components become more than functions.** React can augment component functions with features like *local state* that are tied to the component identity in the tree. A good runtime provides fundamental abstractions that match the problem at hand. As we already mentioned, React is oriented specifically at programs that render UI trees and respond to interactions. If you called components directly, you’d have to build these features yourself. 578 + 579 + * **Component types participate in the reconciliation.** By letting React call your components, you also tell it more about the conceptual structure of your tree. For example, when you move from rendering `<Feed>` to the `<Profile>` page, React won’t attempt to re-use host instances inside them — just like when you replace `<button>` with a `<p>`. All state will be gone — which is usually good when you render a conceptually different view. You wouldn't want to preserve input state between `<PasswordForm>` and `<MessengerChat>` even if the `<input>` position in the tree accidentally “lines up” between them. 580 + 581 + * **React can delay the reconciliation.** If React takes control over calling our components, it can do many interesting things. For example, it can let the browser do some work between the component calls so that re-rendering a large component tree [doesn’t block the main thread](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html). Orchestrating this manually without reimplementing a large part of React is difficult. 582 + 583 + * **A better debugging story.** If components are first-class citizens that the library is aware of, we can build [rich developer tools](https://github.com/facebook/react-devtools) for introspection in development. 584 + 585 + The last benefit to React calling your component functions is *lazy evaluation*. Let’s see what this means. 586 + 587 + ## Lazy Evaluation 588 + 589 + When we call functions in JavaScript, arguments are evaluated before the call: 590 + 591 + ```jsx 592 + // (2) This gets computed second 593 + eat( 594 + // (1) This gets computed first 595 + prepareMeal() 596 + ); 597 + ``` 598 + 599 + This is usually what JavaScript developers expect because JavaScript functions can have implicit side effects. It would be surprising if we called a function, but it wouldn’t execute until its result gets somehow “used” in JavaScript. 600 + 601 + However, React components are [relatively](#purity) pure. There is absolutely no need to execute it if we know its result won’t get rendered on the screen. 602 + 603 + Consider this component putting `<Comments>` inside a `<Page>`: 604 + 605 + ```jsx{11} 606 + function Story({ currentUser }) { 607 + // return { 608 + // type: Page, 609 + // props: { 610 + // user: currentUser, 611 + // children: { type: Comments, props: {} } 612 + // } 613 + // } 614 + return ( 615 + <Page user={currentUser}> 616 + <Comments /> 617 + </Page> 618 + ); 619 + } 620 + ``` 621 + 622 + The `Page` component can render the children given to it inside some `Layout`: 623 + 624 + ```jsx{4} 625 + function Page({ user, children }) { 626 + return ( 627 + <Layout> 628 + {children} 629 + </Layout> 630 + ); 631 + } 632 + ``` 633 + 634 + *(`<A><B /></A>` in JSX is the same as `<A children={<B />} />`.)* 635 + 636 + But what if it has an early exit condition? 637 + 638 + ```jsx{2-4} 639 + function Page({ user, children }) { 640 + if (!user.isLoggedIn) { 641 + return <h1>Please log in</h1>; 642 + } 643 + return ( 644 + <Layout> 645 + {children} 646 + </Layout> 647 + ); 648 + } 649 + ``` 650 + 651 + If we called `Comments()` as a function, it would execute immediately regardless of whether `Page` wants to render them or not: 652 + 653 + ```jsx{4,8} 654 + // { 655 + // type: Page, 656 + // props: { 657 + // children: Comments() // Always runs! 658 + // } 659 + // } 660 + <Page> 661 + {Comments()} 662 + </Page> 663 + ``` 664 + 665 + But if we pass a React element, we don’t execute `Comments` ourselves at all: 666 + 667 + ```jsx{4,8} 668 + // { 669 + // type: Page, 670 + // props: { 671 + // children: { type: Comments } 672 + // } 673 + // } 674 + <Page> 675 + <Comments /> 676 + </Page> 677 + ``` 678 + 679 + This lets React decide when and *whether* to call it. If our `Page` component ignores its `children` prop and renders 680 + `<h1>Please log in</h1>` instead, React won’t even attempt to call the `Comments` function. What’s the point? 681 + 682 + This is good because it both lets us avoid unnecessary rendering work that would be thrown away, and makes the code less fragile. (We don’t care if `Comments` throws or not when the user is logged out — it won’t be called.) 683 + 684 + ## State 685 + 686 + We talked [earlier](#reconciliation) about identity and how an element’s conceptual “position” in the tree tells React whether to re-use a host instance or create a new one. Host instances can have all kinds of local state: focus, selection, input, etc. We want to preserve this state between updates that conceptually render the same UI. We also want to predictably destroy it when we render something conceptually different (such as moving from `<SignupForm>` to `<MessengerChat>`). 687 + 688 + **Local state is so useful that React lets *your own* components have it too.** Components are still functions but React augments them with features that are useful for UIs. Local state tied to the position in the tree is one of these features. 689 + 690 + We call these features *Hooks*. For example, `useState` is a Hook. 691 + 692 + ```jsx{2,6,7} 693 + function Example() { 694 + const [count, setCount] = useState(0); 695 + 696 + return ( 697 + <div> 698 + <p>You clicked {count} times</p> 699 + <button onClick={() => setCount(count + 1)}> 700 + Click me 701 + </button> 702 + </div> 703 + ); 704 + } 705 + ``` 706 + 707 + It returns a pair of values: the current state and a function that updates it. 708 + 709 + The [array destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring) syntax lets us give arbitrary names to our state variables. For example, I called this pair `count` and `setCount`, but it could’ve been `banana` and `setBanana`. In the text below, I will use `setState` to refer to the second value regardless of its actual name in the specific examples. 710 + 711 + *(You can learn more about `useState` and other Hooks provided by React [here](https://reactjs.org/docs/hooks-intro.html).)* 712 + 713 + ## Consistency 714 + 715 + Even if we want to split the reconciliation process itself into [non-blocking](https://www.youtube.com/watch?v=mDdgfyRB5kg) chunks of work, we should still perform the actual host tree operations in a single synchronous swoop. This way we can ensure that the user doesn’t see a half-updated UI, and that the browser doesn’t perform unnecessary layout and style recalculation for intermediate states that the user shouldn’t see. 716 + 717 + This is why React splits all work into the “render phase” and the “commit phase”. *Render phase* is when React calls your components and performs reconciliation. It is safe to interrupt and [in the future](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) will be asynchronous. *Commit phase* is when React touches the host tree. It is always synchronous. 718 + 719 + 720 + ## Memoization 721 + 722 + When a parent schedules an update by calling `setState`, by default React reconciles its whole child subtree. This is because React can’t know whether an update in the parent would affect the child or not, and by default, React opts to be consistent. This may sound very expensive but in practice, it’s not a problem for small and medium-sized subtrees. 723 + 724 + When trees get too deep or wide, you can tell React to [memoize](https://en.wikipedia.org/wiki/Memoization) a subtree and reuse previous render results during shallow equal prop changes: 725 + 726 + ```jsx{5} 727 + function Row({ item }) { 728 + // ... 729 + } 730 + 731 + export default React.memo(Row); 732 + ``` 733 + 734 + Now `setState` in a parent `<Table>` component would skip over reconciling `Row`s whose `item` is referentially equal to the `item` rendered last time. 735 + 736 + You can get fine-grained memoization at the level of individual expressions with the [`useMemo()` Hook](https://reactjs.org/docs/hooks-reference.html#usememo). The cache is local to component tree position and will be destroyed together with its local state. It only holds one last item. 737 + 738 + React intentionally doesn’t memoize components by default. Many components always receive different props so memoizing them would be a net loss. 739 + 740 + ## Raw Models 741 + 742 + Ironically, React doesn’t use a “reactivity” system for fine-grained updates. In other words, any update at the top triggers reconciliation instead of updating just the components affected by changes. 743 + 744 + This is an intentional design decision. [Time to interactive](https://calibreapp.com/blog/time-to-interactive/) is a crucial metric in consumer web applications, and traversing models to set up fine-grained listeners spends that precious time. Additionally, in many apps, interactions tend to result either in small (button hover) or large (page transition) updates, in which case fine-grained subscriptions are a waste of memory resources. 745 + 746 + One of the core design principles of React is that it works with raw data. If you have a bunch of JavaScript objects received from the network, you can pump them directly into your components with no preprocessing. There are no gotchas about which properties you can access, or unexpected performance cliffs when a structure slightly changes. React rendering is O(*view size*) rather than O(*model size*), and you can significantly cut the *view size* with [windowing](https://react-window.now.sh/#/examples/list/fixed-size). 747 + 748 + There are some kinds of applications where fine-grained subscriptions are beneficial — such as stock tickers. This is a rare example of “everything constantly updating at the same time”. While imperative escape hatches can help optimize such code, React might not be the best fit for this use case. Still, you can implement your own fine-grained subscription system on top of React. 749 + 750 + **Note that there are common performance issues that even fine-grained subscriptions and “reactivity” systems can’t solve.** For example, rendering a *new* deep tree (which happens on every page transition) without blocking the browser. Change tracking doesn’t make it faster — it makes it slower because we have to do more work to set up subscriptions. Another problem is that we have to wait for data before we can start rendering the view. In React, we aim to solve both of these problems with [Concurrent Rendering](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html). 751 + 752 + 753 + ## Batching 754 + 755 + Several components may want to update state in response to the same event. This example is contrived but it illustrates a common pattern: 756 + 757 + ```jsx{4,14} 758 + function Parent() { 759 + let [count, setCount] = useState(0); 760 + return ( 761 + <div onClick={() => setCount(count + 1)}> 762 + Parent clicked {count} times 763 + <Child /> 764 + </div> 765 + ); 766 + } 767 + 768 + function Child() { 769 + let [count, setCount] = useState(0); 770 + return ( 771 + <button onClick={() => setCount(count + 1)}> 772 + Child clicked {count} times 773 + </button> 774 + ); 775 + } 776 + ``` 777 + 778 + When an event is dispatched, the child’s `onClick` fires first (triggering its `setState`). Then the parent calls `setState` in its own `onClick` handler. 779 + 780 + If React immediately re-rendered components in response to `setState` calls, we’d end up rendering the child twice: 781 + 782 + ```jsx{4,8} 783 + *** Entering React's browser click event handler *** 784 + Child (onClick) 785 + - setState 786 + - re-render Child // 😞 unnecessary 787 + Parent (onClick) 788 + - setState 789 + - re-render Parent 790 + - re-render Child 791 + *** Exiting React's browser click event handler *** 792 + ``` 793 + 794 + The first `Child` render would be wasted. And we couldn’t make React skip rendering `Child` for the second time because the `Parent` might pass some different data to it based on its updated state. 795 + 796 + **This is why React batches updates inside event handlers:** 797 + 798 + ```jsx 799 + *** Entering React's browser click event handler *** 800 + Child (onClick) 801 + - setState 802 + Parent (onClick) 803 + - setState 804 + *** Processing state updates *** 805 + - re-render Parent 806 + - re-render Child 807 + *** Exiting React's browser click event handler *** 808 + ``` 809 + 810 + The `setState` calls in components wouldn’t immediately cause a re-render. Instead, React would execute all event handlers first, and then trigger a single re-render batching all of those updates together. 811 + 812 + Batching is good for performance but can be surprising if you write code like: 813 + 814 + ```jsx 815 + const [count, setCount] = useState(0); 816 + 817 + function increment() { 818 + setCount(count + 1); 819 + } 820 + 821 + function handleClick() { 822 + increment(); 823 + increment(); 824 + increment(); 825 + } 826 + ``` 827 + 828 + If we start with `count` set to `0`, these would just be three `setCount(1)` calls. To fix this, `setState` provides an overload that accepts an “updater” function: 829 + 830 + ```jsx 831 + const [count, setCount] = useState(0); 832 + 833 + function increment() { 834 + setCount(c => c + 1); 835 + } 836 + 837 + function handleClick() { 838 + increment(); 839 + increment(); 840 + increment(); 841 + } 842 + ``` 843 + 844 + React would put the updater functions in a queue, and later run them in sequence, resulting in a re-render with `count` set to `3`. 845 + 846 + When state logic gets more complex than a few `setState` calls, I recommend expressing it as a local state reducer with the [`useReducer` Hook](https://reactjs.org/docs/hooks-reference.html#usereducer). It’s like an evolution of this “updater” pattern where each update is given a name: 847 + 848 + ```jsx 849 + const [counter, dispatch] = useReducer((state, action) => { 850 + if (action === 'increment') { 851 + return state + 1; 852 + } else { 853 + return state; 854 + } 855 + }, 0); 856 + 857 + function handleClick() { 858 + dispatch('increment'); 859 + dispatch('increment'); 860 + dispatch('increment'); 861 + } 862 + ``` 863 + 864 + The `action` argument can be anything, although an object is a common choice. 865 + 866 + ## Call Tree 867 + 868 + A programming language runtime usually has a [call stack](https://medium.freecodecamp.org/understanding-the-javascript-call-stack-861e41ae61d4). When a function `a()` calls `b()` which itself calls `c()`, somewhere in the JavaScript engine there’s a data structure like `[a, b, c]` that “keeps track” of where you are and what code to execute next. Once you exit out of `c`, its call stack frame is gone — poof! It’s not needed anymore. We jump back into `b`. By the time we exit `a`, the call stack is empty. 869 + 870 + Of course, React itself runs in JavaScript and obeys JavaScript rules. But we can imagine that internally React has some kind of its own call stack to remember which component we are currently rendering, e.g. `[App, Page, Layout, Article /* we're here */]`. 871 + 872 + React is different from a general purpose language runtime because it’s aimed at rendering UI trees. These trees need to “stay alive” for us to interact with them. The DOM doesn’t disappear after our first `ReactDOM.render()` call. 873 + 874 + This may be stretching the metaphor but I like to think of React components as being in a “call tree” rather than just a “call stack”. When we go “out” of the `Article` component, its React “call tree” frame doesn’t get destroyed. We need to keep the local state and references to the host instances [somewhere](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7). 875 + 876 + These “call tree” frames *are* destroyed along with their local state and host instances, but only when the [reconciliation](#reconciliation) rules say it’s necessary. If you ever read React source, you might have seen these frames being referred to as [Fibers](https://en.wikipedia.org/wiki/Fiber_(computer_science)). 877 + 878 + Fibers are where the local state actually lives. When the state is updated, React marks the Fibers below as needing reconciliation, and calls those components. 879 + 880 + ## Context 881 + 882 + In React, we pass things down to other components as props. Sometimes, the majority of components need the same thing — for example, the currently chosen visual theme. It gets cumbersome to pass it down through every level. 883 + 884 + In React, this is solved by [Context](https://reactjs.org/docs/context.html). It is essentially like [dynamic scoping](http://wiki.c2.com/?DynamicScoping) for components. It’s like a wormhole that lets you put something on the top, and have every child at the bottom be able to read it and re-render when it changes. 885 + 886 + ```jsx 887 + const ThemeContext = React.createContext( 888 + 'light' // Default value as a fallback 889 + ); 890 + 891 + function DarkApp() { 892 + return ( 893 + <ThemeContext.Provider value="dark"> 894 + <MyComponents /> 895 + </ThemeContext.Provider> 896 + ); 897 + } 898 + 899 + function SomeDeeplyNestedChild() { 900 + // Depends on where the child is rendered 901 + const theme = useContext(ThemeContext); 902 + // ... 903 + } 904 + ``` 905 + 906 + When `SomeDeeplyNestedChild` renders, `useContext(ThemeContext)` will look for the closest `<ThemeContext.Provider>` above it in the tree, and use its `value`. 907 + 908 + (In practice, React maintains a context stack while it renders.) 909 + 910 + If there’s no `ThemeContext.Provider` above, the result of `useContext(ThemeContext)` call will be the default value specified in the `createContext()` call. In our example, it is `'light'`. 911 + 912 + 913 + ## Effects 914 + 915 + We mentioned earlier that React components shouldn’t have observable side effects during rendering. But side effects are sometimes necessary. We may want to manage focus, draw on a canvas, subscribe to a data source, and so on. 916 + 917 + In React, this is done by declaring an effect: 918 + 919 + ```jsx{4-6} 920 + function Example() { 921 + const [count, setCount] = useState(0); 922 + 923 + useEffect(() => { 924 + document.title = `You clicked ${count} times`; 925 + }); 926 + 927 + return ( 928 + <div> 929 + <p>You clicked {count} times</p> 930 + <button onClick={() => setCount(count + 1)}> 931 + Click me 932 + </button> 933 + </div> 934 + ); 935 + } 936 + ``` 937 + 938 + When possible, React defers executing effects until after the browser re-paints the screen. This is good because code like data source subscriptions shouldn’t hurt [time to interactive](https://calibreapp.com/blog/time-to-interactive/) and [time to first paint](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint). (There's a [rarely used](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) Hook that lets you opt out of that behavior and do things synchronously. Avoid it.) 939 + 940 + Effects don’t just run once. They run both after a component is shown to the user for the first time, and after it updates. Effects can close over current props and state, such as with `count` in the above example. 941 + 942 + Effects may require cleanup, such as in case of subscriptions. To clean up after itself, an effect can return a function: 943 + 944 + ```jsx 945 + useEffect(() => { 946 + DataSource.addSubscription(handleChange); 947 + return () => DataSource.removeSubscription(handleChange); 948 + }); 949 + ``` 950 + 951 + React will execute the returned function before applying this effect the next time, and also before the component is destroyed. 952 + 953 + Sometimes, re-running the effect on every render can be undesirable. You can tell React to [skip](https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects) applying an effect if certain variables didn’t change: 954 + 955 + ```jsx{3} 956 + useEffect(() => { 957 + document.title = `You clicked ${count} times`; 958 + }, [count]); 959 + ``` 960 + 961 + However, it is often a premature optimization and can lead to problems if you’re not familiar with how JavaScript closures work. 962 + 963 + For example, this code is buggy: 964 + 965 + ```jsx 966 + useEffect(() => { 967 + DataSource.addSubscription(handleChange); 968 + return () => DataSource.removeSubscription(handleChange); 969 + }, []); 970 + ``` 971 + 972 + It is buggy because `[]` says “don’t ever re-execute this effect”. But the effect closes over `handleChange` which is defined outside of it. And `handleChange` might reference any props or state: 973 + 974 + ```jsx 975 + function handleChange() { 976 + console.log(count); 977 + } 978 + ``` 979 + 980 + If we never let the effect re-run, `handleChange` will keep pointing at the version from the first render, and `count` will always be `0` inside of it. 981 + 982 + To solve this, make sure that if you specify the dependency array, it includes **all** things that can change, including the functions: 983 + 984 + ```jsx{4} 985 + useEffect(() => { 986 + DataSource.addSubscription(handleChange); 987 + return () => DataSource.removeSubscription(handleChange); 988 + }, [handleChange]); 989 + ``` 990 + 991 + Depending on your code, you might still see unnecessary resubscriptions because `handleChange` itself is different on every render. The [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) Hook can help you with that. Alternatively, you can just let it re-subscribe. For example, browser’s `addEventListener` API is extremely fast, and jumping through hoops to avoid calling it might cause more problems than it’s worth. 992 + 993 + *(You can learn more about `useEffect` and other Hooks provided by React [here](https://reactjs.org/docs/hooks-effect.html).)* 994 + 995 + ## Custom Hooks 996 + 997 + Since Hooks like `useState` and `useEffect` are function calls, we can compose them into our own Hooks: 998 + 999 + ```jsx{2,8} 1000 + function MyResponsiveComponent() { 1001 + const width = useWindowWidth(); // Our custom Hook 1002 + return ( 1003 + <p>Window width is {width}</p> 1004 + ); 1005 + } 1006 + 1007 + function useWindowWidth() { 1008 + const [width, setWidth] = useState(window.innerWidth); 1009 + useEffect(() => { 1010 + const handleResize = () => setWidth(window.innerWidth); 1011 + window.addEventListener('resize', handleResize); 1012 + return () => { 1013 + window.removeEventListener('resize', handleResize); 1014 + }; 1015 + }); 1016 + return width; 1017 + } 1018 + ``` 1019 + 1020 + Custom Hooks let different components share reusable stateful logic. Note that the *state itself* is not shared. Each call to a Hook declares its own isolated state. 1021 + 1022 + *(You can learn more about writing your own Hooks [here](https://reactjs.org/docs/hooks-custom.html).)* 1023 + 1024 + ## Static Use Order 1025 + 1026 + You can think of `useState` as a syntax for defining a “React state variable”. It’s not *really* a syntax, of course. We’re still writing JavaScript. But we are looking at React as a runtime environment, and because React tailors JavaScript to describing UI trees, its features sometimes live closer to the language space. 1027 + 1028 + If `use` *were* a syntax, it would make sense for it to be at the top level: 1029 + 1030 + ```jsx{3} 1031 + // 😉 Note: not a real syntax 1032 + component Example(props) { 1033 + const [count, setCount] = use State(0); 1034 + 1035 + return ( 1036 + <div> 1037 + <p>You clicked {count} times</p> 1038 + <button onClick={() => setCount(count + 1)}> 1039 + Click me 1040 + </button> 1041 + </div> 1042 + ); 1043 + } 1044 + ``` 1045 + 1046 + What would putting it into a condition or a callback or outside a component even mean? 1047 + 1048 + ```jsx 1049 + // 😉 Note: not a real syntax 1050 + 1051 + // This is local state... of what? 1052 + const [count, setCount] = use State(0); 1053 + 1054 + component Example() { 1055 + if (condition) { 1056 + // What happens to it when condition is false? 1057 + const [count, setCount] = use State(0); 1058 + } 1059 + 1060 + function handleClick() { 1061 + // What happens to it when we leave a function? 1062 + // How is this different from a variable? 1063 + const [count, setCount] = use State(0); 1064 + } 1065 + ``` 1066 + 1067 + React state is local to the *component* and its identity in the tree. If `use` were a real syntax it would make sense to scope it to the top-level of a component too: 1068 + 1069 + 1070 + ```jsx 1071 + // 😉 Note: not a real syntax 1072 + component Example(props) { 1073 + // Only valid here 1074 + const [count, setCount] = use State(0); 1075 + 1076 + if (condition) { 1077 + // This would be a syntax error 1078 + const [count, setCount] = use State(0); 1079 + } 1080 + ``` 1081 + 1082 + This is similar to how `import` only works at the top level of a module. 1083 + 1084 + **Of course, `use` is not actually a syntax.** (It wouldn’t bring much benefit and would create a lot of friction.) 1085 + 1086 + However, React *does* expect that all calls to Hooks happen only at the top level of a component and unconditionally. These [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) can be enforced with [a linter plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks). There have been heated arguments about this design choice but in practice, I haven’t seen it confusing people. I also wrote about why commonly proposed alternatives [don’t work](https://overreacted.io/why-do-hooks-rely-on-call-order/). 1087 + 1088 + Internally, Hooks are implemented as [linked lists](https://dev.to/aspittel/thank-u-next-an-introduction-to-linked-lists-4pph). When you call `useState`, we move the pointer to the next item. When we exit the component’s [“call tree” frame](#call-tree), we save the resulting list there until the next render. 1089 + 1090 + [This article](https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e) provides a simplified explanation for how Hooks work internally. Arrays might be an easier mental model than linked lists: 1091 + 1092 + 1093 + ```jsx 1094 + // Pseudocode 1095 + let hooks, i; 1096 + function useState() { 1097 + i++; 1098 + if (hooks[i]) { 1099 + // Next renders 1100 + return hooks[i]; 1101 + } 1102 + // First render 1103 + hooks.push(...); 1104 + } 1105 + 1106 + // Prepare to render 1107 + i = -1; 1108 + hooks = fiber.hooks || []; 1109 + // Call the component 1110 + YourComponent(); 1111 + // Remember the state of Hooks 1112 + fiber.hooks = hooks; 1113 + ``` 1114 + 1115 + *(If you’re curious, the real code is [here](https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.new.js).)* 1116 + 1117 + This is roughly how each `useState()` call gets the right state. As we’ve learned [earlier](#reconciliation), “matching things up” isn’t new to React — reconciliation relies on the elements matching up between renders in a similar way. 1118 + 1119 + ## What’s Left Out 1120 + 1121 + We’ve touched on pretty much all important aspects of the React runtime environment. If you finished this page, you probably know React in more detail than 90% of its users. And there’s nothing wrong with that! 1122 + 1123 + There are some parts I left out — mostly because they’re unclear even to us. React doesn’t currently have a good story for multipass rendering, i.e. when the parent render needs information about the children. Also, the [error handling API](https://reactjs.org/docs/error-boundaries.html) doesn’t yet have a Hooks version. It’s possible that these two problems can be solved together. Concurrent Mode is not stable yet, and there are interesting questions about how Suspense fits into this picture. Maybe I’ll do a follow-up when they’re fleshed out and Suspense is ready for more than [lazy loading](https://reactjs.org/blog/2018/10/23/react-v-16-6.html#reactlazy-code-splitting-with-suspense). 1124 + 1125 + **I think it speaks to the success of React’s API that you can get very far without ever thinking about most of these topics.** Good defaults like the reconciliation heuristics do the right thing in most cases. Warnings, like the `key` warning, nudge you when you risk shooting yourself in the foot. 1126 + 1127 + If you’re a UI library nerd, I hope this post was somewhat entertaining and clarified how React works in more depth. Or maybe you decided React is too complicated and you’ll never look at it again. In either case, I’d love to hear from you on Twitter! Thank you for reading.
public/react-as-a-ui-runtime/react.png

This is a binary file and will not be displayed.

+195
public/the-bug-o-notation/index.md
··· 1 + --- 2 + title: The “Bug-O” Notation 3 + date: '2019-01-25' 4 + spoiler: What is the 🐞(n) of your API? 5 + --- 6 + 7 + When you write performance-sensitive code, it’s a good idea to keep in mind its algorithmic complexity. It is often expressed with the [Big-O notation](https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/). 8 + 9 + Big-O is a measure of **how much slower the code will get as you throw more data at it**. For example, if a sorting algorithm has O(<i>n<sup>2</sup></i>) complexity, sorting ×50 times more items will be roughly 50<sup>2</sup> = 2,500 times slower. Big O doesn’t give you an exact number, but it helps you understand how an algorithm *scales*. 10 + 11 + Some examples: O(<i>n</i>), O(<i>n</i> log <i>n</i>), O(<i>n<sup>2</sup></i>), O(<i>n!</i>). 12 + 13 + 14 + However, **this post isn’t about algorithms or performance**. It’s about APIs and debugging. It turns out, API design involves very similar considerations. 15 + 16 + --- 17 + 18 + A significant part of our time goes into finding and fixing mistakes in our code. Most developers would like to find bugs faster. As satisfactory as it may be in the end, it sucks to spend the whole day chasing a single bug when you could have implemented something from your roadmap. 19 + 20 + Debugging experience influences our choice of abstractions, libraries, and tools. Some API and language designs make a whole class of mistakes impossible. Some create endless problems. **But how can you tell which one is which?** 21 + 22 + Many online discussions about APIs are primarily concerned with aesthetics. But that [doesn’t say much](/optimized-for-change/) about what it feels like to use an API in practice. 23 + 24 + **I have a metric that helps me think about this. I call it the *Bug-O* notation:** 25 + 26 + <p><font size="40">🐞(<i>n</i>)</font></p> 27 + 28 + The Big-O describes how much an algorithm slows down as the inputs grow. The *Bug-O* describes how much an API slows *you* down as your codebase grows. 29 + 30 + --- 31 + 32 + For example, consider this code that manually updates the DOM over time with imperative operations like `node.appendChild()` and `node.removeChild()` and no clear structure: 33 + 34 + ```jsx 35 + function trySubmit() { 36 + // Section 1 37 + let spinner = createSpinner(); 38 + formStatus.appendChild(spinner); 39 + submitForm().then(() => { 40 + // Section 2 41 + formStatus.removeChild(spinner); 42 + let successMessage = createSuccessMessage(); 43 + formStatus.appendChild(successMessage); 44 + }).catch(error => { 45 + // Section 3 46 + formStatus.removeChild(spinner); 47 + let errorMessage = createErrorMessage(error); 48 + let retryButton = createRetryButton(); 49 + formStatus.appendChild(errorMessage); 50 + formStatus.appendChild(retryButton) 51 + retryButton.addEventListener('click', function() { 52 + // Section 4 53 + formStatus.removeChild(errorMessage); 54 + formStatus.removeChild(retryButton); 55 + trySubmit(); 56 + }); 57 + }) 58 + } 59 + ``` 60 + 61 + The problem with this code isn’t that it’s “ugly”. We’re not talking about aesthetics. **The problem is that if there is a bug in this code, I don’t know where to start looking.** 62 + 63 + **Depending on the order in which the callbacks and events fire, there is a combinatorial explosion of the number of codepaths this program could take.** In some of them, I’ll see the right messages. In others, I’ll see multiple spinners, failure and error messages together, and possibly crashes. 64 + 65 + This function has 4 different sections and no guarantees about their ordering. My very non-scientific calculation tells me there are 4×3×2×1 = 24 different orders in which they could run. If I add four more code segments, it’ll be 8×7×6×5×4×3×2×1 — *forty thousand* combinations. Good luck debugging that. 66 + 67 + **In other words, the Bug-O of this approach is 🐞(<i>n!</i>)** where *n* is the number of code segments touching the DOM. Yeah, that’s a *factorial*. Of course, I’m not being very scientific here. Not all transitions are possible in practice. But on the other hand, each of these segments can run more than once. <span style={{wordBreak: "keep-all"}}>🐞(*¯\\\_(ツ)\_/¯*)</span> might be more accurate but it’s still pretty bad. We can do better. 68 + 69 + --- 70 + 71 + To improve the Bug-O of this code, we can limit the number of possible states and outcomes. We don't need any library to do this. It’s just a matter of enforcing some structure on our code. Here is one way we could do it: 72 + 73 + ```jsx 74 + let currentState = { 75 + step: 'initial', // 'initial' | 'pending' | 'success' | 'error' 76 + }; 77 + 78 + function trySubmit() { 79 + if (currentState.step === 'pending') { 80 + // Don't allow to submit twice 81 + return; 82 + } 83 + setState({ step: 'pending' }); 84 + submitForm().then(() => { 85 + setState({ step: 'success' }); 86 + }).catch(error => { 87 + setState({ step: 'error', error }); 88 + }); 89 + } 90 + 91 + function setState(nextState) { 92 + // Clear all existing children 93 + formStatus.innerHTML = ''; 94 + 95 + currentState = nextState; 96 + switch (nextState.step) { 97 + case 'initial': 98 + break; 99 + case 'pending': 100 + formStatus.appendChild(spinner); 101 + break; 102 + case 'success': 103 + let successMessage = createSuccessMessage(); 104 + formStatus.appendChild(successMessage); 105 + break; 106 + case 'error': 107 + let errorMessage = createErrorMessage(nextState.error); 108 + let retryButton = createRetryButton(); 109 + formStatus.appendChild(errorMessage); 110 + formStatus.appendChild(retryButton); 111 + retryButton.addEventListener('click', trySubmit); 112 + break; 113 + } 114 + } 115 + ``` 116 + 117 + This code might not look too different. It’s even a bit more verbose. But it is *dramatically* simpler to debug because of this line: 118 + 119 + ```jsx{3} 120 + function setState(nextState) { 121 + // Clear all existing children 122 + formStatus.innerHTML = ''; 123 + 124 + // ... the code adding stuff to formStatus ... 125 + ``` 126 + 127 + By clearing out the form status before doing any manipulations, we ensure that our DOM operations always start from scratch. This is how we can fight the inevitable [entropy](/the-elements-of-ui-engineering/) — by *not* letting the mistakes accumulate. This is the coding equivalent of “turning it off and on again”, and it works amazingly well. 128 + 129 + **If there is a bug in the output, we only need to think *one* step back — to the previous `setState` call.** The Bug-O of debugging a rendering result is 🐞(*n*) where *n* is the number of rendering code paths. Here, it’s just four (because we have four cases in a `switch`). 130 + 131 + We might still have race conditions in *setting* the state, but debugging those is easier because each intermediate state can be logged and inspected. We can also disallow any undesired transitions explicitly: 132 + 133 + ```jsx 134 + function trySubmit() { 135 + if (currentState.step === 'pending') { 136 + // Don't allow to submit twice 137 + return; 138 + } 139 + ``` 140 + 141 + Of course, always resetting the DOM comes with a tradeoff. Naïvely removing and recreating the DOM every time would destroy its internal state, lose focus, and cause terrible performance problems in larger applications. 142 + 143 + That’s why libraries like React can be helpful. They let you *think* in the paradigm of always recreating the UI from scratch without necessarily doing it: 144 + 145 + ```jsx 146 + function FormStatus() { 147 + let [state, setState] = useState({ 148 + step: 'initial' 149 + }); 150 + 151 + function handleSubmit(e) { 152 + e.preventDefault(); 153 + if (state.step === 'pending') { 154 + // Don't allow to submit twice 155 + return; 156 + } 157 + setState({ step: 'pending' }); 158 + submitForm().then(() => { 159 + setState({ step: 'success' }); 160 + }).catch(error => { 161 + setState({ step: 'error', error }); 162 + }); 163 + } 164 + 165 + let content; 166 + switch (state.step) { 167 + case 'pending': 168 + content = <Spinner />; 169 + break; 170 + case 'success': 171 + content = <SuccessMessage />; 172 + break; 173 + case 'error': 174 + content = ( 175 + <> 176 + <ErrorMessage error={state.error} /> 177 + <RetryButton onClick={handleSubmit} /> 178 + </> 179 + ); 180 + break; 181 + } 182 + 183 + return ( 184 + <form onSubmit={handleSubmit}> 185 + {content} 186 + </form> 187 + ); 188 + } 189 + ``` 190 + 191 + The code may look different, but the principle is the same. The component abstraction enforces boundaries so that you know no *other* code on the page could mess with its DOM or state. Componentization helps reduce the Bug-O. 192 + 193 + In fact, if *any* value looks wrong in the DOM of a React app, you can trace where it comes from by looking at the code of components above it in the React tree one by one. No matter the app size, tracing a rendered value is 🐞(*tree height*). 194 + 195 + **Next time you see an API discussion, consider: what is the 🐞(*n*) of common debugging tasks in it?** What about existing APIs and principles you’re deeply familiar with? Redux, CSS, inheritance — they all have their own Bug-O.
+62
public/the-elements-of-ui-engineering/index.md
··· 1 + --- 2 + title: 'The Elements of UI Engineering' 3 + date: '2018-12-30' 4 + spoiler: What makes UI engineering difficult? 5 + cta: 'react' 6 + --- 7 + 8 + In my [previous post](/things-i-dont-know-as-of-2018/), I talked about admitting our knowledge gaps. You might conclude that I suggest settling for mediocrity. I don’t! This is a broad field. 9 + 10 + I strongly believe that you can “begin anywhere” and don’t need to learn technologies in any particular order. But I also place great value in gaining expertise. Personally I’ve mostly been interested in creating user interfaces. 11 + 12 + **I’ve been mulling over what it is that I *do* know about and consider valuable.** Sure, I’m familiar with a few technologies (e.g. JavaScript and React). But the more important lessons from experience are elusive. I never tried to put them into words. This is my first attempt to catalog and describe some of them. 13 + 14 + --- 15 + 16 + There are plenty of “learning roadmaps” about technologies and libraries. Which library is going to be in vogue in 2019? What about 2020? Should you learn Vue or React? Angular? What about Redux or Rx? Do you need to learn Apollo? REST or GraphQL? It’s easy to get lost. What if the author is wrong? 17 + 18 + **My biggest learning breakthroughs weren’t about a particular technology.** Rather, I learned the most when I struggled to solve a particular UI problem. Sometimes, I would later discover libraries or patterns that helped me. In other cases, I’d come up with my own solutions (both good and bad ones). 19 + 20 + It’s this combination of understanding the *problems*, experimenting with the *solutions*, and applying different *strategies* that led to the most rewarding learning experiences in my life. **This post focuses on just the problems.** 21 + 22 + --- 23 + 24 + If you worked a user interface, you’ve likely dealt with at least some of these challenges — either directly or using a library. In either case, I encourage you to create a tiny app with _no_ libraries, and play with reproducing and solving these problems. There’s no one right solution to any of them. Learning comes from exploring the problem space and trying different possible tradeoffs. 25 + 26 + --- 27 + 28 + * **Consistency.** You click on a “Like” button and the text updates: “You and 3 other friends liked this post.” You click it again, and the text flips back. Sounds easy. But maybe a label like this exists in several places on the screen. Maybe there is some other visual indication (such as the button background) that needs to change. The list of “likers” that was previously fetched from the server and is visible on hover should now include your name. If you navigate to another screen and go back, the post shouldn’t “forget” it was liked. Even local consistency *alone* creates a set of challenges. But other users might also modify the data we display (e.g. by liking a post we’re viewing). How do we keep the same data in sync on different parts of the screen? How and when do we make the local data consistent with the server, and the other way around? 29 + 30 + * **Responsiveness.** People can only tolerate a lack of visual feedback to their actions for a limited time. For *continuous* actions like gestures and scroll, this limit is low. (Even skipping a single 16ms frame feels “janky”.) For *discrete* actions like clicks, there is research saying users perceive any < 100ms delays as equally fast. If an action takes longer, we need to show a visual indicator. But there are some counter-intuitive challenges. Indicators that cause the page layout to “jump” or that go through several loading “stages” can make the action *feel longer* than it was. Similarly, handling an interaction within 20ms at the cost of dropping an animation frame can *feel slower* than handling it within 30ms and no dropped frames. Brains aren’t benchmarks. How do we keep our apps responsive to different kinds of inputs? 31 + 32 + * **Latency.** Both computations and network access take time. *Sometimes* we can ignore the computational cost if it doesn’t hurt the responsiveness on our target devices (make sure to test your app on the low-end device spectrum). But handling network latency is unavoidable — it can take seconds! Our app can’t just freeze waiting for the data or code to load. This means any action that depends on new data, code, or assets is potentially asynchronous and needs to handle the “loading” case. But that can happen for almost every screen. How do we gracefully handle latency without displaying a “cascade” of spinners or empty “holes”? How do we avoid “jumpy” layout? And how do we change async dependencies without “rewiring” our code every time? 33 + 34 + * **Navigation.** We expect that the UI remains “stable” as we interact with it. Things shouldn’t disappear from right under our noses. Navigation, whether started within the app (e.g. clicking a link) or due to an external event (e.g. clicking the “back” button), should also respect this principle. For example, switching between `/profile/likes` and `/profile/follows` tabs on a profile screen shouldn’t clear a search input outside the tabbed view. Even navigating to *another* screen is like walking into a room. People expect to go back later and find things as they left them (with, perhaps, some new items). If you’re in the middle of a feed, click on a profile, and go back, it’s frustrating to lose your position in the feed — or wait for it to load again. How do we architect our app to handle arbitrary navigation without losing important context? 35 + 36 + * **Staleness.** We can make the “back” button navigation instant by introducing a local cache. In that cache, we can “remember” some data for quick access even if we could theoretically refetch it. But caching brings its own problems. Caches can get stale. If I change an avatar, it should update in the cache too. If I make a new post, it needs to appear in the cache immediately, or the cache needs to be invalidated. This can become difficult and error-prone. What if the posting fails? How long does the cache stay in memory? When we refetch the feed, do we “stitch” the newly fetched feed with the cached one, or throw the cache away? How is pagination or sorting represented in the cache? 37 + 38 + * **Entropy.** The second law of thermodynamics says something like “with time, things turn into a mess” (well, not exactly). This applies to user interfaces too. We can’t predict the exact user interactions and their order. At any point in time, our app may be in one of a mind-boggling number of possible states. We do our best to make the result predictable and limited by our design. We don’t want to look at a bug screenshot and wonder “how did _that_ happen”. For *N* possible states, there are *N×(N–1)* possible transitions between them. For example, if a button can be in one of 5 different states (normal, active, hover, danger, disabled), the code updating the button must be correct for 5×4=20 possible transitions — or forbid some of them. How do we tame the combinatorial explosion of possible states and make visual output predictable? 39 + 40 + * **Priority.** Some things are more important than others. A dialog might need to appear physically “above” the button that spawned it and “break out” of its container’s clip boundaries. A newly scheduled task (e.g. responding to a click) might be more important than a long-running task that already started (e.g. rendering next posts below the screen fold). As our app grows, parts of its code written by different people and teams compete for limited resources like processor, network, screen estate, and the bundle size budget. Sometimes you can rank the contenders on a shared scale of “importance”, like the CSS `z-index` property. [But it rarely ends well.](https://devblogs.microsoft.com/oldnewthing/20050607-00/?p=35413) Every developer is biased to think _their_ code is important. And if everything is important, then nothing is! How do we get independent widgets to *cooperate* instead of fighting for resources? 41 + 42 + * **Accessibility.** Inaccessible websites are *not* a niche problem. For example, in UK disability affects 1 in 5 people. [(Here’s a nice infographic.)](https://www.abrightclearweb.com/web-accessibility-in-the-uk/) I’ve felt this personally too. Though I’m only 26, I struggle to read websites with thin fonts and low contrast. I try to use the trackpad less often, and I dread the day I’ll have to navigate poorly implemented websites by keyboard. We need to make our apps not horrible to people with difficulties — and the good news is that there’s a lot of low-hanging fruit. It starts with education and tooling. But we also need to make it easy for product developers to do the right thing. What can we do to make accessibility a *default* rather than an afterthought? 43 + 44 + * **Internationalization.** Our app needs to work all over the world. Not only do people speak different languages, but we also need to support right-to-left layouts with the least amount of effort from product engineers. How do we support different languages without sacrificing latency and responsiveness? 45 + 46 + * **Delivery.** We need to get our application code to the user’s computer. What transport and format do we use? This might sound straightforward but there are many tradeoffs here. For example, native apps tend to load all code in advance at the cost of a huge app size. Web apps tend to have smaller initial payload at the cost of more latency during use. How do we choose at which point to introduce latency? How do we optimize our delivery based on the usage patterns? What kind of data would we need for an optimal solution? 47 + 48 + * **Resilience.** You might like bugs if you’re an entomologist, but you probably don’t enjoy seeing them in your programs. However, some of your bugs will inevitably get to production. What happens then? Some bugs cause wrong but well-defined behavior. For example, maybe your code displays incorrect visual output under some condition. But what if the rendering code *crashes*? Then we can’t meaningfully continue because the visual output would be inconsistent. A crash rendering a single post shouldn’t “bring down” an entire feed or get it into a semi-broken state that causes further crashes. How do we write code in a way that isolates rendering and fetching failures and keeps the rest of the app running? What does fault tolerance mean for user interfaces? 49 + 50 + * **Abstraction.** In a tiny app, we can hardcode a lot of special cases to account for the above problems. But apps tend to grow. We want to be able to [reuse, fork, and join](/optimized-for-change/) parts of our code, and work on it collectively. We want to define clear boundaries between the pieces familiar to different people, and avoid making often-changing logic too rigid. How do we create abstractions that hide implementation details of a particular UI part? How do we avoid re-introducing the same problems that we just solved as our app grows? 51 + 52 + --- 53 + 54 + Of course, there are many problems I haven’t mentioned. This list is by no means exhaustive! For example, I haven’t talked about the designer and engineering collaboration, or debugging and testing. Maybe another time. 55 + 56 + It’s tempting to read about these problems with a particular view library or a data fetching library in mind as a solution. But I encourage you to pretend that these libraries don’t exist, and read again from that perspective. How would *you* approach solving these issues? Give them a try on a tiny app! (I’d love to see your experiments on GitHub — feel free to tweet me in response.) 57 + 58 + What’s interesting about these problems is that most of them show up at any scale. You can see them both in small widgets like a typeahead or a tooltip, and in huge apps like Twitter and Facebook. 59 + 60 + **Think of a non-trivial UI element from an app you enjoy using, and go through this list of problems. Can you describe some of the tradeoffs chosen by its developers? Try to recreate a similar behavior from scratch!** 61 + 62 + I learned a lot about UI engineering by experimenting with these problems in small apps without using libraries. I recommend the same to anyone who wants to gain a deeper appreciation for the tradeoffs in UI engineering.
+23
public/the-wet-codebase/index.md
··· 1 + --- 2 + title: 'The WET Codebase' 3 + date: '2020-07-13' 4 + spoiler: Come waste your time with me. 5 + --- 6 + 7 + The [Don’t Repeat Yourself](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) Wikipedia article states: 8 + 9 + >Violations of DRY are typically referred to as WET solutions, which is commonly taken to stand for "write every time", "write everything twice", "we enjoy typing" or "waste everyone's time". 10 + 11 + But as any Phish fan can tell you, [wasting time](https://www.youtube.com/watch?v=Zg2tVuXXkpk) is actually good! 12 + 13 + A year ago, I gave a conference talk, and I want to share it today with those of you who haven’t watched it. This talk isn’t about React, or even JavaScript. 14 + 15 + <a target="_blank" href="https://www.deconstructconf.com/2019/dan-abramov-the-wet-codebase">![Slide from the talk](./wet_codebase.png)</a> 16 + 17 + **[Watch: The Wet Codebase](https://www.deconstructconf.com/2019/dan-abramov-the-wet-codebase)** *(includes transcript)* 18 + 19 + In this talk, my aim was to show *why* strict adherence to writing code that is free of duplication inevitably leads to **software we can’t understand**. While you could watch this talk by yourself, I tried to make it a good starting point for a team discussion. If you drop it in Slack, tell me what your teammates thought! 20 + 21 + I hope you’ll enjoy watching this talk as much as I enjoyed presenting it. I would like to thank [Gary](https://twitter.com/garybernhardt/) and the team for organizing a stellar conference. I’m also thankful to [Sebastian](https://twitter.com/sebmarkbage), [Sandi](https://twitter.com/sandimetz), and [Cheng](https://twitter.com/_chenglou) for the talks that inspired this one. 22 + 23 + I miss conferences. Hope to see you all soon.
public/the-wet-codebase/wet_codebase.png

This is a binary file and will not be displayed.

+95
public/things-i-dont-know-as-of-2018/index.md
··· 1 + --- 2 + title: Things I Don’t Know as of 2018 3 + date: '2018-12-28' 4 + spoiler: We can admit our knowledge gaps without devaluing our expertise. 5 + --- 6 + 7 + People often assume that I know far more than I actually do. That’s not a bad problem to have and I’m not complaining. (Folks from minority groups often suffer the opposite bias despite their hard-earned credentials, and that *sucks*.) 8 + 9 + **In this post I’ll offer an incomplete list of programming topics that people often wrongly assume that I know.** I’m not saying *you* don’t need to learn them — or that I don’t know *other* useful things. But since I’m not in a vulnerable position myself right now, I can be honest about this. 10 + 11 + Here’s why I think it’s important. 12 + 13 + --- 14 + 15 + First, there is often an unrealistic expectation that an experienced engineer knows every technology in their field. Have you seen a “learning roadmap” that consists of a hundred libraries and tools? It’s useful — but intimidating. 16 + 17 + What’s more, no matter how experienced you get, you may still find yourself switching between feeling capable, inadequate (“Impostor syndrome”), and overconfident (“Dunning–Kruger effect”). It depends on your environment, job, personality, teammates, mental state, time of day, and so on. 18 + 19 + Experienced developers sometimes open up about their insecurities to encourage beginners. But there’s a world of difference between a seasoned surgeon who still gets the jitters and a student holding their first scalpel! 20 + 21 + Hearing how “we’re all junior developers” can be disheartening and sound like empty talk to the learners faced with an actual gap in knowledge. Feel-good confessions from well-intentioned practitioners like me can’t bridge it. 22 + 23 + Still, even experienced engineers have many knowledge gaps. This post is about mine, and I encourage those who can afford similar vulnerability to share their own. But let’s not devalue our experience while we do that. 24 + 25 + **We can admit our knowledge gaps, may or may not feel like impostors, and still have deeply valuable expertise that takes years of hard work to develop.** 26 + 27 + --- 28 + 29 + With that disclaimer out of the way, here’s just a few things I don’t know: 30 + 31 + * **Unix commands and Bash.** I can `ls` and `cd` but I look up everything else. I get the concept of piping but I’ve only used it in simple cases. I don’t know how to use `xargs` to create complex chains, or how to compose and redirect different output streams. I also never properly learned Bash so I can only write very simple (and often buggy) shell scripts. 32 + 33 + * **Low-level languages.** I understand Assembly lets you store things in memory and jump around the code but that’s about it. I wrote a few lines of C and understand what a pointer is, but I don’t know how to use `malloc` or other manual memory management techniques. Never played with Rust. 34 + 35 + * **Networking stack.** I know computers have IP addresses, and DNS is how we resolve hostnames. I know there’s low level protocols like TCP/IP to exchange packets that (maybe?) ensure integrity. That’s it — I’m fuzzy on details. 36 + 37 + * **Containers.** I have no idea about how to use Docker or Kubernetes. (Are those related?) I have a vague idea that they let me spin up a separate VM in a predictable way. Sounds cool but I haven’t tried it. 38 + 39 + * **Serverless.** Also sounds cool. Never tried it. I don’t have a clear idea of how that model changes backend programming (if it does at all). 40 + 41 + * **Microservices.** If I understand correctly, this just means “many API endpoints talking to each other”. I don’t know what the practical advantages or downsides of this approach are because I haven’t worked with it. 42 + 43 + * **Python.** I feel bad about this one — I *have* worked with Python for several years at some point and I’ve never bothered to actually learn it. There are many things there like import behavior that are completely opaque to me. 44 + 45 + * **Node backends.** I understand how to run Node, used some APIs like `fs` for build tooling, and can set up Express. But I’ve never talked from Node to a database and don’t really know how to write a backend in it. I’m also not familiar with React frameworks like Next beyond a “hello world”. 46 + 47 + * **Native platforms.** I tried learning Objective C at some point but it didn’t work out. I haven’t learned Swift either. Same about Java. (I could probably pick it up though since I worked with C#.) 48 + 49 + * **Algorithms.** The most you’ll get out of me is bubble sort and maybe quicksort on a good day. I can probably do simple graph traversing tasks if they’re tied to a particular practical problem. I understand the O(n) notation but my understanding isn’t much deeper than “don’t put loops inside loops”. 50 + 51 + * **Functional languages.** Unless you count JavaScript, I’m not fluent in any traditionally functional language. (I’m only fluent in C# and JavaScript — and I already forgot most of C#.) I struggle to read either LISP-inspired (like Clojure), Haskell-inspired (like Elm), or ML-inspired (like OCaml) code. 52 + 53 + * **Functional terminology.** Map and reduce is as far as I go. I don’t know monoids, functors, etc. I know what a monad is but maybe that’s an illusion. 54 + 55 + * **Modern CSS.** I don’t know Flexbox or Grid. Floats are my jam. 56 + 57 + * **CSS Methodologies.** I used BEM (meaning the CSS part, not the original BEM) but that’s all I know. I haven’t tried OOCSS or other methodologies. 58 + 59 + * **SCSS / Sass.** Never got to learn them. 60 + 61 + * **CORS.** I dread these errors! I know I need to set up some headers to fix them but I’ve wasted hours here in the past. 62 + 63 + * **HTTPS / SSL.** Never set it up. Don’t know how it works beyond the idea of private and public keys. 64 + 65 + * **GraphQL.** I can read a query but I don’t really know how to express stuff with nodes and edges, when to use fragments, and how pagination works there. 66 + 67 + * **Sockets.** My mental model is they let computers talk to each other outside the request/response model but that’s about all I know. 68 + 69 + * **Streams.** Aside from Rx Observables, I haven’t worked with streams closely. I used old Node streams one or two times but always messed up error handling. 70 + 71 + * **Electron.** Never tried it. 72 + 73 + * **TypeScript.** I understand the concept of types and can read annotations but I’ve never written it. The few times I tried, I ran into difficulties. 74 + 75 + * **Deployment and devops.** I can manage to send some files over FTP or kill some processes but that’s the limit of my devops skills. 76 + 77 + * **Graphics.** Whether it’s canvas, SVG, WebGL or low-level graphics, I’m not productive in it. I get the overall idea but I’d need to learn the primitives. 78 + 79 + Of course this list is not exhaustive. There are many things that I don’t know. 80 + 81 + --- 82 + 83 + It might seem like a strange thing to discuss. It even feels wrong to write it. Am I boasting of my ignorance? My intended takeaway from this post is that: 84 + 85 + * **Even your favorite developers may not know many things that you know.** 86 + 87 + * **Regardless of your knowledge level, your confidence can vary greatly.** 88 + 89 + * **Experienced developers have valuable expertise despite knowledge gaps.** 90 + 91 + I’m aware of my knowledge gaps (at least, some of them). I can fill them in later if I become curious or if I need them for a project. 92 + 93 + This doesn’t devalue my knowledge and experience. There’s plenty of things that I can do well. For example, learning technologies when I need them. 94 + 95 + >Update: I also [wrote](/the-elements-of-ui-engineering/) about a few things that I know.
-1
public/vercel.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
+74
public/what-are-the-react-team-principles/index.md
··· 1 + --- 2 + title: What Are the React Team Principles? 3 + date: '2019-12-25' 4 + spoiler: UI Before API. 5 + cta: 'react' 6 + --- 7 + 8 + During my time on the React team, I’ve been lucky to see how [Jordan](https://twitter.com/jordwalke), [Sebastian](https://twitter.com/sebmarkbage), [Sophie](https://twitter.com/sophiebits) and other tenured team members approach problems. In this post, I'm distilling what I learned from them into a few high-level technical principles. These principles aren’t meant to be exhaustive. This is my personal attempt to formalize observations about how the React team operates — other team members may have different perspectives. 9 + 10 + ## UI Before API 11 + 12 + Every abstraction has its own quirks when deployed at scale. How do those quirks manifest themselves in the user interfaces? Can you tell when an app is built with a certain abstraction? 13 + 14 + Abstractions have a direct effect on the user experiences — enabling, perpetuating, or even making some of them impossible. This is why when designing APIs, we don’t start with the abstraction itself. Instead, we start with the desired user experience, and work backwards to the abstraction. 15 + 16 + Sometimes as we work backwards, we realize we need to change the whole approach to enable the right user experience. We can’t see that if we start with the API. So we put UI before API. 17 + 18 + ## Absorb the Complexity 19 + 20 + Making React internals simple is not a goal. We are willing to make React internals complex if that complexity lets product developers keep their code easier to understand and modify. 21 + 22 + We want to allow the product development be decentralized and collaborative. Often, that means that we bear the brunt of centralization. React can’t be split into small simple loosely coupled modules because in order to do its job, something has to act as the coordinator. That’s what React is. 23 + 24 + By raising the abstraction level, we make product developers more powerful. They benefit from the system as a whole having certain predictable properties. But this means that every new N+1st feature we introduce has to work well with all of the N existing features. This is why contributing new features to React is so difficult both at the design and the implementation side. This is why we don’t get many “core” open source contributions. 25 + 26 + We absorb the complexity to stop its bleeding into the product code. 27 + 28 + ## Hacks, Then Idioms 29 + 30 + Every API creates new restrictions. Sometimes these restrictions prevent people from shipping delightful user experiences. We provide escape hatches so people can work around us where necessary. 31 + 32 + Hacks can’t survive for long because they are fragile. Product developers then have to choose whether they prefer to take a maintenance hit by supporting the hack, or to degrade the user experience but remove the hack. Often, the user experience loses — or the hack prevents further improvements to it. 33 + 34 + We need to allow hacks using escape hatches, and observe which hacks people put in practice. Our job is to eventually provide an idiomatic solution for hacks that exist in the name of better user experience. Sometimes, a solution might take years. We prefer a flexible hack to entrenching a poor idiom. 35 + 36 + ## Enable Local Reasoning 37 + 38 + There aren’t many things you can do in a code editor. You can add some lines or remove them. Or copy and paste something. And yet many abstractions make these basic operations difficult. 39 + 40 + For example, MVC frameworks tend to make it unsafe to delete some part of the rendering output. This is because parents imperatively call methods on their children (which aren’t there after you removed them). By contrast, in React it’s usually safe to just delete lines of code in your render tree. This is a win. 41 + 42 + When designing APIs, we assume the person only has local knowledge about the piece of code they’re working on. When the intended effect is local, we want to prevent surprising outcomes. For example, we usually expect adding code to be safe. We expect removing and changing code to clearly point to the whole trail of code that needs to be considered as part of this change. We can’t assume the knowledge of the whole codebase when changing a single file. 43 + 44 + When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible. Warnings, type checks, and developer tooling can help, but they are limited by the API design. If the API is not restrictive enough, local reasoning is impossible. For example, this is why `findDOMNode()` is bad. It requires global knowledge. 45 + 46 + ## Progressive Complexity 47 + 48 + Some frameworks choose to have a fork in the road. They provide two ways to do something: an easy way, and a powerful way. The easy way is nice to learn, but at some point you hit a wall. When that happens, you have to undo your past work, and reimplement it differently. 49 + 50 + We prefer that implementing a complex thing isn’t too much different in structure from implementing a simple thing. For example, we don’t offer a separate template DSL “for the simple cases” because it creates a fork in the road. We’re willing to compromise on the barrier to entry if we think you’re going to want the fully featured mechanism soon anyway. 51 + 52 + Sometimes, the “easy way” and the “powerful way” are two different frameworks. Then you also have to rewrite. It’s nice when we can avoid that, too. For example, adding server rendering is an optimization that requires some extra effort in React but not a full-on rewrite. 53 + 54 + ## Contain the Damage 55 + 56 + Top-down tools like code budgets are important. However, over the long term, our standards will slip, features will ship under deadlines, and products will be unmaintained. When we can’t rely on everyone playing along, our role as a coordinator is to contain the damage. 57 + 58 + If some UI code is slow or over budget, we need to do what we can to reduce its negative effects on loading time and interactions with other parts of the UI. Ideally, the developer should only “pay” for the features they use, and the user should only “pay” for the UI they interact with. Concurrent Mode features including Time Slicing and Selective Hydration are different levers to address that. 59 + 60 + Because the library code overhead is relatively stable but the application code is unbounded, we tend to focus on containing the damage in the application code instead of the fixed cost of library code. 61 + 62 + ## Trust the Theory 63 + 64 + Sometimes, we know an approach is a dead end. Maybe it works today, but we are already aware of its limitations, and they fundamentally prevent us from achieving the desirable user experience. We pivot away from investing into such approach as soon as it’s viable to do so. 65 + 66 + We want to avoid getting stuck in a local maxima. If another approach makes a lot more sense in theory, we’re willing to invest the effort to get there, even if it takes many years. There will be many obstacles and pragmatic compromises we might have to make to get there. But we trust that if we keep chipping away at them, eventually the theory wins. 67 + 68 + ## What Are Your Team’s Principles? 69 + 70 + These are a few fundamental principles I've observed in how we work, but I've probably missed a bunch of them. I also haven't touched on the less technical principles around how we roll out APIs, communicate changes, and so on. That could be a topic for another day. 71 + 72 + Does your team have a set of principles? I’d love to hear about them. 73 + 74 + *This article was originally posted [here](https://react.christmas/2019/24).*
+61
public/what-is-javascript-made-of/index.md
··· 1 + --- 2 + title: What Is JavaScript Made Of? 3 + date: '2019-12-20' 4 + spoiler: Getting a closure on JavaScript. 5 + --- 6 + 7 + During my first few years of using JavaScript, I felt like a fraud. Even though I could build websites with frameworks, something was missing. I dreaded JavaScript job interviews because I didn't have a solid grasp on fundamentals. 8 + 9 + Over the years, I've formed a mental model of JavaScript that gave me confidence. Here, I'm sharing a **very compressed** version of it. It's structured like a glossary, with each topic getting a few sentences. 10 + 11 + As you read through this post, try to mentally keep score about how *confident* you feel about each topic. I won't judge you if quite a few of them are a miss! At the end of this post, there is something that might help in that case. 12 + 13 + --- 14 + 15 + * **Value**: The concept of a value is a bit abstract. It's a "thing". A value to JavaScript is what a number is to math, or what a point is to geometry. When your program runs, its world is full of values. Numbers like `1`, `2`, and `420` are values, but so are some other things, like this sentence: `"Cows go moo"`. Not *everything* is a value though. A number is a value, but an `if` statement is not. We'll look at a few different kinds of values below. 16 + - **Type of Value**: There are a few different "types" of values. For example, *numbers* like `420`, *strings* like `"Cows go moo"`, *objects*, and a few other types. You can learn a type of some value by putting `typeof` before it. For example, `console.log(typeof 2)` prints `"number"`. 17 + - **Primitive Values**: Some value types are "primitive". They include numbers, strings, and a few other types. One peculiar thing about primitive values is that you can't create more of them, or change them in any way. For example, every time you write `2`, you get the *same* value `2`. You can't "create" another `2` in your program, or make the `2` *value* "become" `3`. This is also true for strings. 18 + - **`null` and `undefined`**: These are two special values. They're special because there's a lot of things you can't do with them -- they often cause errors. Usually, `null` represents that some value is missing intentionally, and `undefined` represents that a value is missing unintentionally. However, when to use either is left to the programmer. They exist because sometimes it's better for an operation to fail than to proceed with a missing value. 19 + 20 + * **Equality**: Like "value", equality is a fundamental concept in JavaScript. We say two values are equal when they're... actually, I'd never say that. If two values are equal, it means they *are* the same value. Not two different values, but one! For example, `"Cows go moo" === "Cows go moo"` and `2 === 2` because `2` *is* `2`. Note we use *three* equal signs to represent this concept of equality in JavaScript. 21 + - **Strict Equality**: Same as above. 22 + - **Referential Equality**: Same as above. 23 + - **Loose Equality**: Oof, this one is different! Loose equality is when we use *two* equal signs (`==`). Things may be considered *loosely equal* even if they refer to *different* values that look similar (such as `2` and `"2"`). It was added to JavaScript early on for convenience and has caused endless confusion ever since. This concept is not fundamental, but is a common source of mistakes. You can learn how it works on a rainy day, but many people try to avoid it. 24 + 25 + * **Literal**: A literal is when you refer to a value by *literally* writing it down in your program. For example, `2` is a *number literal*, and `"Banana"` is a *string literal*. 26 + 27 + * **Variable**: A variable lets you refer to some value using a name. For example, `let message = "Cows go moo"`. Now you can write `message` instead of repeating the same sentence every time in your code. You may later change `message` to point to another value, like `message = "I am the walrus"`. Note this doesn't change *the value itself*, but only where the `message` points to, like a "wire". It pointed to `"Cows go moo"`, and now it points to `"I am the walrus"`. 28 + - **Scope**: It would suck if there could only be one `message` variable in the whole program. Instead, when you define a variable, it becomes available in a *part* of your program. That part is called a "scope". There are rules about how scope works, but usually you can search for the closest `{` and `}` braces around where you define the variable. That "block" of code is its scope. 29 + - **Assignment**: When we write `message = "I am the walrus"`, we change the `message` variable to point to `"I am the walrus"` value. This is called an assignment, writing, or setting the variable. 30 + - **`let` vs `const` vs `var`**: Usually you want `let`. If you want to forbid assignment to this variable, you can use `const`. (Some codebases and coworkers are pedantic and force you to use `const` when there is only one assignment.) Avoid `var` if you can because its scoping rules are confusing. 31 + * **Object**: An object is a special kind of value in JavaScript. The cool thing about objects is that they can have connections to other values. For example, a `{flavor: "vanilla"}` object has a `flavor` property that points to the `"vanilla"` value. Think of an object as "your own" value with "wires" from it. 32 + - **Property**: A property is like a "wire" sticking from an object and pointing to some value. It might remind you of a variable: it has a name (like `flavor`) and points to a value (like `"vanilla"`). But unlike a variable, a property "lives" *in* the object itself rather than in some place in your code (scope). A property is considered a part of the object -- but the value it points to is not. 33 + - **Object Literal**: An object literal is a way to create an object value by *literally* writing it down in your program, like `{}` or `{flavor: "vanilla"}`. Inside `{}`, we can have multiple `property: value` pairs separated by commas. This lets us set up where the property "wires" point to from our object. 34 + - **Object Identity**: We mentioned earlier that `2` is *equal* to `2` (in other words, `2 === 2`) because whenever we write `2`, we "summon" the same value. But whenever we write `{}`, we will always get a *different* value! So `{}` is *not equal* to another `{}`. Try this in console: `{} === {}` (the result is false). When the computer meets `2` in our code, it always gives us the same `2` value. However, object literals are different: when a computer meets `{}`, it *creates a new object, which is always a new value*. So what is object identity? It's yet another term for equality, or same-ness of values. When we say “`a` and `b` have the same identity", we mean “`a` and `b` point to the *same* value" (`a === b`). When we say “`a` and `b` have different identities", we mean “`a` and `b` point to *different* values" (`a !== b`). 35 + - **Dot Notation**: When you want to read a property from an object or assign to it, you can use the dot (`.`) notation. For example, if a variable `iceCream` points to an object whose property `flavor` points to `"chocolate"`, writing `iceCream.flavor` will give you `"chocolate"`. 36 + - **Bracket Notation**: Sometimes you don't know the name of the property you want to read in advance. For example, maybe sometimes you want to read `iceCream.flavor` and sometimes you want to read `iceCream.taste`. The bracket (`[]`) notation lets you read the property when *its name itself* is a variable. For example, let's say that `let ourProperty = 'flavor'`. Then `iceCream[ourProperty]` will give us `"chocolate"`. Curiously, we can use it when creating objects too: `{ [ourProperty]: "vanilla" }`. 37 + - **Mutation**: We say an object is *mutated* when somebody changes its property to point to a different value. For example, if we declare `let iceCream = {flavor: "vanilla"}`, we can later *mutate* it with `iceCream.flavor = "chocolate"`. Note that even if we used `const` to declare `iceCream`, we could still mutate `iceCream.flavor`. This is because `const` would only prevent assignments to the `iceCream` *variable itself*, but we mutated a *property* (`flavor`) of the object it pointed to. Some people swore off using `const` altogether because they find this too misleading. 38 + - **Array**: An array is an object that represents a list of stuff. When you write an *array literal* like `["banana", "chocolate", "vanilla"]`, you essentially create an object whose property called `0` points to the `"banana"` string value, property called `1` points to the `"chocolate"` value, and property called `2` points to the `"vanilla"` value. It would be annoying to write `{0: ..., 1: ..., 2: ...}` which is why arrays are useful. There are also some built-in ways to operate on arrays, like `map`, `filter`, and `reduce`. Don't despair if `reduce` seems confusing -- it's confusing to everyone. 39 + - **Prototype**: What happens if we read a property that doesn't exist? For example, `iceCream.taste` (but our property is called `flavor`). The simple answer is we'll get the special `undefined` value. The more nuanced answer is that most objects in JavaScript have a "prototype". You can think of a prototype as a "hidden" property on every object that determines "where to look next". So if there's no `taste` property on `iceCream`, JavaScript will look for a `taste` property on its prototype, then on *that* object's prototype, and so on, and will only give us `undefined` if it reaches the end of this "prototype chain" without finding `.taste`. You will rarely interact with this mechanism directly, but it explains why our `iceCream` object has a `toString` method that we never defined -- it comes from the prototype. 40 + 41 + * **Function**: A function is a special value with one purpose: it represents *some code in your program*. Functions are handy if you don't want to write the same code many times. "Calling" a function like `sayHi()` tells the computer to run the code inside it and then go back to where it was in the program. There are many ways to define a function in JavaScript, with slight differences in what they do. 42 + - **Arguments (or Parameters)**: Arguments let you pass some information to your function from the place you call it: `sayHi("Amelie")`. Inside the function, they act similar to variables. They're called either "arguments" or "parameters" depending on which side you're reading (function definition or function call). However, this distinction in terminology is pedantic, and in practice these two terms are used interchangeably. 43 + - **Function Expression**: Previously, we set a variable to a *string value*, like `let message = "I am the walrus"`. It turns out that we can also set a variable to a *function*, like `let sayHi = function() { }`. The thing after `=` here is called a *function expression*. It gives us a special value (a function) that represents our piece of code, so we can call it later if we want to. 44 + - **Function Declaration**: It gets tiring to write something like `let sayHi = function() { }` every time, so we can use a shorter form instead: `function sayHi() { }`. This is called a *function declaration*. Instead of specifying the variable name on the left, we put it after the `function` keyword. These two styles are mostly interchangeable. 45 + - **Function Hoisting**: Normally, you can only use a variable after its declaration with `let` or `const` has run. This can be annoying with functions because they may need to call each other, and it's hard to track which function is used by which others and needs to be defined first. As a convenience, when (and only when!) you use the *function declaration* syntax, the order of their definitions doesn't matter because they get "hoisted". This is a fancy way of saying that conceptually, they all automatically get moved to the top of the scope. By the time you call them, they're all defined. 46 + - **`this`**: Probably the most misunderstood JavaScript concept, `this` is like a special argument to a function. You don't pass it to a function yourself. Instead, JavaScript itself passes it, depending on *how you call* the function. For example, calls using the dot `.` notation -- like `iceCream.eat()` -- will get a special `this` value from whatever is before the `.` (in our example, `iceCream`). The value of `this` inside a function depends on how the function is *called*, not where it's defined. Helpers like `.bind`, `.call`, and `.apply` let you have for more control over the value of `this`. 47 + - **Arrow Functions**: Arrow functions are similar to function expressions. You declare them like this: `let sayHi = () => { }`. They're concise and are often used for one-liners. Arrow functions are *more limited* than regular functions -- for example, they have no concept of `this` whatsoever. When you write `this` inside of an arrow function, it uses `this` of the closest "regular" function above. This is similar to what would happen if you used an argument or a variable that only exists in a function above. Practically, this means that people use arrow functions when they want to "see" the same `this` inside of them as in the code surrounding them. 48 + - **Function Binding**: Usually, *binding* a function `f` to a particular `this` value and arguments means creating a *new* function that calls `f` with those predefined values. JavaScript has a built-in helper to do it called `.bind`, but you could also do it by hand. Binding was a popular way to make nested functions "see" the same value of `this` as the outer functions. But now this use case is handled by arrow functions, so binding is not used as often. 49 + - **Call Stack**: Calling a function is like entering a room. Every time we call a function, the variables inside of it are initialized all over again. So each function call is like *constructing* a new "room" with its code and entering it. Our function's variables "live" in that room. When we return from the function, that "room" disappears with all its variables. You can visualize these rooms as a vertical stack of rooms -- a *call stack*. When we exit a function, we go back to the function "below" it on the call stack. 50 + - **Recursion**: Recursion means that a function calls itself from within itself. This is useful for when you want to repeat the thing you just did in your function *again*, but for different arguments. For example, if you're writing a search engine that crawls the web, your `collectLinks(url)` function might first collect the links from a page, and then *call itself* for every *link* until it visits all pages. The pitfall with recursion is that it's easy to write code that never finishes because a function keeps calling itself forever. If this happens, JavaScript will stop it with an error called "stack overflow". It's called this way because it means we have too many function calls stacked in our call stack, and it has literally overflown. 51 + - **Higher-Order Function**: A higher-order function is a function that deals with other functions by taking them as arguments or returning them. This might seem weird at first, but we should remember that functions are values so we can pass them around -- like we do with numbers, strings, or objects. This style can be overused, but it's very expressive in moderation. 52 + - **Callback**: A callback is not really a JavaScript term. It's more of a pattern. It's when you pass a function as an argument to another function, expecting it to *call your function back later*. You're expecting a "call back". For example, `setTimeout` takes a *callback* function and... calls you back after a timeout. But there's nothing special about callback functions. They're regular functions, and when we say "callback" we only talk about our expectations. 53 + - **Closure**: Normally, when you exit a function, all its variables "disappear". This is because nothing needs them anymore. But what if you declare a function *inside* a function? Then the inner function could still be called later, and read the variables of the *outer* function. In practice, this is very useful! But for this to work, the outer function's variables need to "stick around" somewhere. So in this case, JavaScript takes care of "keeping the variables alive" instead of "forgetting" them as it would usually do. This is called a "closure". While closures are often considered a misunderstood JavaScript aspect, you probably use them many times a day without realizing it! 54 + 55 + --- 56 + 57 + JavaScript is made of these concepts, and more. I felt very anxious about my knowledge of JavaScript until I could build a correct mental model, and I'd like to help the next generation of developers bridge this gap sooner. 58 + 59 + If you want to join me for a deeper dive in each of these topics, I have something for you. **[Just JavaScript](https://justjavascript.com/) is my distilled mental model of how JavaScript works, and it's going to feature visual illustrations by the amazing [Maggie Appleton](https://illustrated.dev/)**. Unlike this post, it goes at a slower pace so you can follow along on every detail. 60 + 61 + *Just JavaScript* is in a very early stage so it's only available as a series of emails with zero polish or editing. If this project sounds interesting, you can [sign up](https://justjavascript.com/) to receive free drafts by email. I will be grateful for your feedback. Thank you!
public/why-do-hooks-rely-on-call-order/hooks-hn1.png

This is a binary file and will not be displayed.

public/why-do-hooks-rely-on-call-order/hooks-hn2.png

This is a binary file and will not be displayed.

+601
public/why-do-hooks-rely-on-call-order/index.md
··· 1 + --- 2 + title: Why Do React Hooks Rely on Call Order? 3 + date: '2018-12-13' 4 + spoiler: Lessons learned from mixins, render props, HOCs, and classes. 5 + cta: 'react' 6 + --- 7 + 8 + At React Conf 2018, the React team presented the [Hooks proposal](https://reactjs.org/docs/hooks-intro.html). 9 + 10 + If you’d like to understand what Hooks are and what problems they solve, check out [our talks](https://www.youtube.com/watch?v=dpw9EHDh2bM) introducing them and [my follow-up article](https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889) addressing common misconceptions. 11 + 12 + Chances are you won’t like Hooks at first: 13 + 14 + ![Negative HN comment](./hooks-hn1.png) 15 + 16 + They’re like a music record that grows on you only after a few good listens: 17 + 18 + ![Positive HN comment from the same person four days later](./hooks-hn2.png) 19 + 20 + When you read the docs, don’t miss [the most important page](https://reactjs.org/docs/hooks-custom.html) about building your own Hooks! Too many people get fixated on some part of our messaging they disagree with (e.g. that learning classes is difficult) and miss the bigger picture behind Hooks. And the bigger picture is that **Hooks are like *functional mixins* that let you create and compose your own abstractions.** 21 + 22 + Hooks [are influenced by some prior art](https://reactjs.org/docs/hooks-faq.html#what-is-the-prior-art-for-hooks) but I haven’t seen anything *quite* like them until Sebastian shared his idea with the team. Unfortunately, it’s easy to overlook the connection between the specific API choices and the valuable properties unlocked by this design. With this post I hope to help more people understand the rationale for the most controversial aspect of Hooks proposal. 23 + 24 + **The rest of this post assumes you know the `useState()` Hook API and how to write a custom Hook. If you don’t, check out the earlier links. Also, keep in mind Hooks are experimental and you don’t have to learn them right now!** 25 + 26 + (Disclaimer: this is a personal post and doesn’t necessarily reflect the opinions of the React team. It’s large, the topic is complex, and I may have made mistakes somewhere.) 27 + 28 + --- 29 + 30 + The first and probably the biggest shock when you learn about Hooks is that they rely on *persistent call index between re-renders*. This has some [implications](https://reactjs.org/docs/hooks-rules.html). 31 + 32 + This decision is obviously controversial. This is why, [against our principles](https://www.reddit.com/r/reactjs/comments/9xs2r6/sebmarkbages_response_to_hooks_rfc_feedback/e9wh4um/), we only published this proposal after we felt the documentation and talks describe it well enough for people to give it a fair chance. 33 + 34 + **If you’re concerned about some aspects of the Hooks API design, I encourage you to read Sebastian’s [full response](https://github.com/reactjs/rfcs/pull/68#issuecomment-439314884) to the 1,000+ comment RFC discussion.** It is thorough but also quite dense. I could probably turn every paragraph of this comment into its own blog post. (In fact, I already [did](/how-does-setstate-know-what-to-do/) that once!) 35 + 36 + There is one specific part that I’d like to focus on today. As you may recall, each Hook can be used in a component more than once. For example, we can declare [multiple state variables](https://reactjs.org/docs/hooks-state.html#tip-using-multiple-state-variables) by calling `useState()` repeatedly: 37 + 38 + ```jsx{2,3,4} 39 + function Form() { 40 + const [name, setName] = useState('Mary'); // State variable 1 41 + const [surname, setSurname] = useState('Poppins'); // State variable 2 42 + const [width, setWidth] = useState(window.innerWidth); // State variable 3 43 + 44 + useEffect(() => { 45 + const handleResize = () => setWidth(window.innerWidth); 46 + window.addEventListener('resize', handleResize); 47 + return () => window.removeEventListener('resize', handleResize); 48 + }); 49 + 50 + function handleNameChange(e) { 51 + setName(e.target.value); 52 + } 53 + 54 + function handleSurnameChange(e) { 55 + setSurname(e.target.value); 56 + } 57 + 58 + return ( 59 + <> 60 + <input value={name} onChange={handleNameChange} /> 61 + <input value={surname} onChange={handleSurnameChange} /> 62 + <p>Hello, {name} {surname}</p> 63 + <p>Window width: {width}</p> 64 + </> 65 + ); 66 + } 67 + ``` 68 + 69 + Note that we use array destructuring syntax to name `useState()` state variables but these names are not passed to React. Instead, in this example **React treats `name` as “the first state variable”, `surname` as “the second state variable”, and so on**. Their *call index* is what gives them a stable identity between re-renders. This mental model is well-described [in this article](https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e). 70 + 71 + On a surface level, relying on the call index just *feels wrong*. A gut feeling is a useful signal but it can be misleading — especially if we haven’t fully internalized the problem we’re solving. **In this post, I’ll take a few commonly suggested alternative designs for Hooks and show where they break down.** 72 + 73 + --- 74 + 75 + This post won’t be exhaustive. Depending on how granular you’re counting, we’ve seen from a dozen to *hundreds* of different alternative proposals. We’ve also been [thinking](https://github.com/reactjs/react-future) about alternative component APIs for the last five years. 76 + 77 + Blog posts like this are tricky because even if you cover a hundred alternatives, somebody can tweak one and say: “Ha, you didn’t think of *that*!” 78 + 79 + In practice, different alternative proposals tend to overlap in their downsides. Rather than enumerate *all* the suggested APIs (which would take me months), I’ll demonstrate the most common flaws with specific examples. Categorizing other possible APIs by these problems could be an exercise to the reader. 🧐 80 + 81 + *That is not to say that Hooks are flawless.* But once you get familiar with the flaws of other solutions, you might find that the Hooks design makes some sense. 82 + 83 + --- 84 + 85 + ### Flaw #1: Can’t Extract a Custom Hook 86 + 87 + Surprisingly, many alternative proposals don’t allow [custom Hooks](https://reactjs.org/docs/hooks-custom.html) at all. Perhaps we didn’t emphasize custom Hooks enough in the “motivation” docs. It’s difficult to do until the primitives are well-understood. So it’s a chicken-and-egg problem. But custom Hooks are largely the point of the proposal. 88 + 89 + For example, an alternative banned multiple `useState()` calls in a component. You’d keep state in one object. That works for classes, right? 90 + 91 + ```jsx 92 + function Form() { 93 + const [state, setState] = useState({ 94 + name: 'Mary', 95 + surname: 'Poppins', 96 + width: window.innerWidth, 97 + }); 98 + // ... 99 + } 100 + ``` 101 + 102 + To be clear, Hooks *do* allow this style. You don’t *have to* split your state into a bunch of state variables (see our [recommendations](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables) in the FAQ). 103 + 104 + But the point of supporting multiple `useState()` calls is so that you can *extract* parts of stateful logic (state + effects) out of your components into custom Hooks which can *also* independently use local state and effects: 105 + 106 + ```jsx{6-7} 107 + function Form() { 108 + // Declare some state variables directly in component body 109 + const [name, setName] = useState('Mary'); 110 + const [surname, setSurname] = useState('Poppins'); 111 + 112 + // We moved some state and effects into a custom Hook 113 + const width = useWindowWidth(); 114 + // ... 115 + } 116 + 117 + function useWindowWidth() { 118 + // Declare some state and effects in a custom Hook 119 + const [width, setWidth] = useState(window.innerWidth); 120 + useEffect(() => { 121 + // ... 122 + }); 123 + return width; 124 + } 125 + ``` 126 + 127 + If you only allow one `useState()` call per component, you lose the ability of custom Hooks to introduce local state. Which is the point of custom Hooks. 128 + 129 + ### Flaw #2: Name Clashes 130 + 131 + One common suggestion is to let `useState()` accept a key argument (e.g. a string) that uniquely identifies a particular state variable within a component. 132 + 133 + There are a few variations on this idea, but they roughly look like this: 134 + 135 + ```jsx 136 + // ⚠️ This is NOT the React Hooks API 137 + function Form() { 138 + // We pass some kind of state key to useState() 139 + const [name, setName] = useState('name'); 140 + const [surname, setSurname] = useState('surname'); 141 + const [width, setWidth] = useState('width'); 142 + // ... 143 + ``` 144 + 145 + This tries to avoid reliance on the call index (yay explicit keys!) but introduces another problem — name clashes. 146 + 147 + Granted, you probably won’t be tempted to call `useState('name')` twice in the same component except by mistake. This can happen accidentally but we could argue that about any bug. However, it’s quite likely that when you work on a *custom Hook*, you’ll want to add or remove state variables and effects. 148 + 149 + With this proposal, any time you add a new state variable inside a custom Hook, you risk breaking any components that use it (directly or transitively) because *they might already use the same name* for their own state variables. 150 + 151 + This is an example of an API that’s not [optimized for change](/optimized-for-change/). The current code might always look “elegant”, but it is very fragile to changes in requirements. We should [learn](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html#mixins-cause-name-clashes) from our mistakes. 152 + 153 + The actual Hooks proposal solves this by relying on the call order: even if two Hooks use a `name` state variable, they would be isolated from each other. Every `useState()` call gets its own “memory cell”. 154 + 155 + There are still a few other ways we could work around this flaw but they also have their own issues. Let’s explore this problem space more closely. 156 + 157 + ### Flaw #3: Can’t Call the Same Hook Twice 158 + 159 + Another variation of the “keyed” `useState` proposal is to use something like Symbols. Those can’t clash, right? 160 + 161 + ```jsx 162 + // ⚠️ This is NOT the React Hooks API 163 + const nameKey = Symbol(); 164 + const surnameKey = Symbol(); 165 + const widthKey = Symbol(); 166 + 167 + function Form() { 168 + // We pass some kind of state key to useState() 169 + const [name, setName] = useState(nameKey); 170 + const [surname, setSurname] = useState(surnameKey); 171 + const [width, setWidth] = useState(widthKey); 172 + // ... 173 + ``` 174 + 175 + This proposal seems to work for extracting the `useWindowWidth()` Hook: 176 + 177 + ```jsx{4,11-17} 178 + // ⚠️ This is NOT the React Hooks API 179 + function Form() { 180 + // ... 181 + const width = useWindowWidth(); 182 + // ... 183 + } 184 + 185 + /********************* 186 + * useWindowWidth.js * 187 + ********************/ 188 + const widthKey = Symbol(); 189 + 190 + function useWindowWidth() { 191 + const [width, setWidth] = useState(widthKey); 192 + // ... 193 + return width; 194 + } 195 + ``` 196 + 197 + But if we attempt to extract input handling, it would fail: 198 + 199 + ```jsx{4,5,19-29} 200 + // ⚠️ This is NOT the React Hooks API 201 + function Form() { 202 + // ... 203 + const name = useFormInput(); 204 + const surname = useFormInput(); 205 + // ... 206 + return ( 207 + <> 208 + <input {...name} /> 209 + <input {...surname} /> 210 + {/* ... */} 211 + </> 212 + ) 213 + } 214 + 215 + /******************* 216 + * useFormInput.js * 217 + ******************/ 218 + const valueKey = Symbol(); 219 + 220 + function useFormInput() { 221 + const [value, setValue] = useState(valueKey); 222 + return { 223 + value, 224 + onChange(e) { 225 + setValue(e.target.value); 226 + }, 227 + }; 228 + } 229 + ``` 230 + 231 + (I’ll admit this `useFormInput()` Hook isn’t particularly useful but you could imagine it handling things like validation and dirty state flag a la [Formik](https://github.com/jaredpalmer/formik).) 232 + 233 + Can you spot the bug? 234 + 235 + We’re calling `useFormInput()` twice but our `useFormInput()` always calls `useState()` with the same key. So effectively we’re doing something like: 236 + 237 + ```jsx 238 + const [name, setName] = useState(valueKey); 239 + const [surname, setSurname] = useState(valueKey); 240 + ``` 241 + 242 + And this is how we get a clash again. 243 + 244 + The actual Hooks proposal doesn’t have this problem because **each _call_ to `useState()` gets its own isolated state.** Relying on a persistent call index frees us from worrying about name clashes. 245 + 246 + ### Flaw #4: The Diamond Problem 247 + 248 + This is technically the same flaw as the previous one but it’s worth mentioning for its notoriety. It’s even [described on Wikipedia](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem). (Apparently, it’s sometimes called “the deadly diamond of death” — cool beans.) 249 + 250 + Our own mixin system [suffered from it](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html#mixins-cause-name-clashes). 251 + 252 + Two custom Hooks like `useWindowWidth()` and `useNetworkStatus()` might want to use the same custom Hook like `useSubscription()` under the hood: 253 + 254 + ```jsx{12,23-27,32-42} 255 + function StatusMessage() { 256 + const width = useWindowWidth(); 257 + const isOnline = useNetworkStatus(); 258 + return ( 259 + <> 260 + <p>Window width is {width}</p> 261 + <p>You are {isOnline ? 'online' : 'offline'}</p> 262 + </> 263 + ); 264 + } 265 + 266 + function useSubscription(subscribe, unsubscribe, getValue) { 267 + const [state, setState] = useState(getValue()); 268 + useEffect(() => { 269 + const handleChange = () => setState(getValue()); 270 + subscribe(handleChange); 271 + return () => unsubscribe(handleChange); 272 + }); 273 + return state; 274 + } 275 + 276 + function useWindowWidth() { 277 + const width = useSubscription( 278 + handler => window.addEventListener('resize', handler), 279 + handler => window.removeEventListener('resize', handler), 280 + () => window.innerWidth 281 + ); 282 + return width; 283 + } 284 + 285 + function useNetworkStatus() { 286 + const isOnline = useSubscription( 287 + handler => { 288 + window.addEventListener('online', handler); 289 + window.addEventListener('offline', handler); 290 + }, 291 + handler => { 292 + window.removeEventListener('online', handler); 293 + window.removeEventListener('offline', handler); 294 + }, 295 + () => navigator.onLine 296 + ); 297 + return isOnline; 298 + } 299 + ``` 300 + 301 + This is a completely valid use case. **It should be safe for a custom Hook author to start or stop using another custom Hook without worrying whether it is “already used” somewhere in the chain.** In fact, *you can never know* the whole chain unless you audit every component using your Hook on every change. 302 + 303 + (As a counterexample, the legacy React `createClass()` mixins did not let you do this. Sometimes you’d have two mixins that both do exactly what you need but are mutually incompatible due to extending the same “base” mixin.) 304 + 305 + This is our “diamond”: 💎 306 + 307 + ``` 308 + / useWindowWidth() \ / useState() 🔴 Clash 309 + Status useSubscription() 310 + \ useNetworkStatus() / \ useEffect() 🔴 Clash 311 + ``` 312 + 313 + Reliance on the persistent call order naturally resolves it: 314 + 315 + ``` 316 + / useState() ✅ #1. State 317 + / useWindowWidth() -> useSubscription() 318 + / \ useEffect() ✅ #2. Effect 319 + Status 320 + \ / useState() ✅ #3. State 321 + \ useNetworkStatus() -> useSubscription() 322 + \ useEffect() ✅ #4. Effect 323 + ``` 324 + 325 + Function calls don’t have a “diamond” problem because they form a tree. 🎄 326 + 327 + ### Flaw #5: Copy Paste Breaks Things 328 + 329 + Maybe we could salvage the keyed state proposal by introducing some sort of namespacing. There are a few different ways to do it. 330 + 331 + One way could be to isolate state keys with closures. This would require you to “instantiate” custom Hooks and add a function wrapper around each of them: 332 + 333 + ```jsx{5,6} 334 + /******************* 335 + * useFormInput.js * 336 + ******************/ 337 + function createUseFormInput() { 338 + // Unique per instantiation 339 + const valueKey = Symbol(); 340 + 341 + return function useFormInput() { 342 + const [value, setValue] = useState(valueKey); 343 + return { 344 + value, 345 + onChange(e) { 346 + setValue(e.target.value); 347 + }, 348 + }; 349 + } 350 + } 351 + ``` 352 + 353 + This approach is rather heavy-handed. One of the design goals of Hooks is to avoid the deeply nested functional style that is prevalent with higher-order components and render props. Here, we have to “instantiate” *any* custom Hook before its use — and use the resulting function *exactly once* in the body of a component. This isn’t much simpler than calling Hooks unconditionally. 354 + 355 + Additionally, you have to repeat every custom Hook used in a component twice. Once in the top level scope (or inside a function scope if we’re writing a custom Hook), and once at the actual call site. This means you have to jump between the rendering and top-level declarations even for small changes: 356 + 357 + ```jsx{2,3,7,8} 358 + // ⚠️ This is NOT the React Hooks API 359 + const useNameFormInput = createUseFormInput(); 360 + const useSurnameFormInput = createUseFormInput(); 361 + 362 + function Form() { 363 + // ... 364 + const name = useNameFormInput(); 365 + const surname = useNameFormInput(); 366 + // ... 367 + } 368 + ``` 369 + 370 + You also need to be very precise with their names. You would always have “two levels” of names — factories like `createUseFormInput` and the instantiated Hooks like `useNameFormInput` and `useSurnameFormInput`. 371 + 372 + If you call the same custom Hook “instance” twice you’d get a state clash. In fact, the code above has this mistake — have you noticed? It should be: 373 + 374 + ```jsx 375 + const name = useNameFormInput(); 376 + const surname = useSurnameFormInput(); // Not useNameFormInput! 377 + ``` 378 + 379 + These problems are not insurmountable but I would argue that they add *more* friction than following the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html). 380 + 381 + Importantly, they break the expectations of copy-paste. Extracting a custom Hook without an extra closure wrapper *still works* with this approach but only until you call it twice. (Which is when it creates a conflict.) It’s unfortunate when an API seems to work but then forces you to Wrap All the Things™️ once you realize there is a conflict somewhere deep down the chain. 382 + 383 + ### Flaw #6: We Still Need a Linter 384 + 385 + There is another way to avoid conflicts with keyed state. If you know about it, you were probably really annoyed I still haven’t acknowledged it! Sorry. 386 + 387 + The idea is that we could *compose* keys every time we write a custom Hook. Something like this: 388 + 389 + ```jsx{4,5,16,17} 390 + // ⚠️ This is NOT the React Hooks API 391 + function Form() { 392 + // ... 393 + const name = useFormInput('name'); 394 + const surname = useFormInput('surname'); 395 + // ... 396 + return ( 397 + <> 398 + <input {...name} /> 399 + <input {...surname} /> 400 + {/* ... */} 401 + </> 402 + ) 403 + } 404 + 405 + function useFormInput(formInputKey) { 406 + const [value, setValue] = useState('useFormInput(' + formInputKey + ').value'); 407 + return { 408 + value, 409 + onChange(e) { 410 + setValue(e.target.value); 411 + }, 412 + }; 413 + } 414 + ``` 415 + 416 + Out of different alternatives, I dislike this approach the least. I don’t think it’s worth it though. 417 + 418 + Code passing non-unique or badly composed keys would *accidentally work* until a Hook is called multiple times or clashes with another Hook. Worse, if it’s meant to be conditional (we’re trying to “fix” the unconditional call requirement, right?), we might not even encounter the clashes until later. 419 + 420 + Remembering to pass keys through all layers of custom Hooks seems fragile enough that we’d want to lint for that. They would add extra work at runtime (don’t forget they’d need to serve *as keys*), and each of them is a paper cut for bundle size. **But if we have to lint anyway, what problem did we solve?** 421 + 422 + This might make sense if conditionally declaring state and effects was very desirable. But in practice I find it confusing. In fact, I don’t recall anyone ever asking to conditionally define `this.state` or `componentDidMount` either. 423 + 424 + What does this code mean exactly? 425 + 426 + ```jsx{3,4} 427 + // ⚠️ This is NOT the React Hooks API 428 + function Counter(props) { 429 + if (props.isActive) { 430 + const [count, setCount] = useState('count'); 431 + return ( 432 + <p onClick={() => setCount(count + 1)}> 433 + {count} 434 + </p>; 435 + ); 436 + } 437 + return null; 438 + } 439 + ``` 440 + 441 + Is `count` preserved when `props.isActive` is `false`? Or does it get reset because `useState('count')` wasn’t called? 442 + 443 + If conditional state gets preserved, what about an effect? 444 + 445 + ```jsx{5-8} 446 + // ⚠️ This is NOT the React Hooks API 447 + function Counter(props) { 448 + if (props.isActive) { 449 + const [count, setCount] = useState('count'); 450 + useEffect(() => { 451 + const id = setInterval(() => setCount(c => c + 1), 1000); 452 + return () => clearInterval(id); 453 + }, []); 454 + return ( 455 + <p onClick={() => setCount(count + 1)}> 456 + {count} 457 + </p>; 458 + ); 459 + } 460 + return null; 461 + } 462 + ``` 463 + 464 + It definitely can’t run *before* `props.isActive` is `true` for the first time. But once it becomes `true`, does it ever stop running? Does the interval reset when `props.isActive` flips to `false`? If it does, it’s confusing that effect behaves differently from state (which we said wouldn’t reset). If the effect keeps running, it’s confusing that `if` outside the effect doesn’t actually make the effect conditional. Didn’t we say we wanted conditional effects? 465 + 466 + If the state *does* get reset when we don’t “use” it during a render, what happens if multiple `if` branches contain `useState('count')` but only one runs at any given time? Is that valid code? If our mental model is a “map with keys”, why do things “disappear” from it? Would the developer expect an early `return` from a component to reset all state after it? If we truly wanted to reset the state, we could make it explicit by extracting a component: 467 + 468 + ```jsx 469 + function Counter(props) { 470 + if (props.isActive) { 471 + // Clearly has its own state 472 + return <TickingCounter />; 473 + } 474 + return null; 475 + } 476 + ``` 477 + 478 + That would probably become the “best practice” to avoid these confusing questions anyway. So whichever way you choose to answer them, I think the semantics of conditionally *declaring* state and effects itself end up weird enough that you might want to lint against it. 479 + 480 + If we have to lint anyway, the requirement to correctly compose keys becomes “dead weight”. It doesn’t buy us anything we actually *want* to do. However, dropping that requirement (and going back to the original proposal) *does* buy us something. It makes copy-pasting component code into a custom Hook safe without namespacing it, reduces bundle size paper cuts from keys and unlocks a slightly more efficient implementation (no need for Map lookups). 481 + 482 + Small things add up. 483 + 484 + ### Flaw #7: Can’t Pass Values Between Hooks 485 + 486 + One of the best features of Hooks is that you can pass values between them. 487 + 488 + Here is a hypothetical example of a message recipient picker that shows whether the currently chosen friend is online: 489 + 490 + ```jsx{8,9} 491 + const friendList = [ 492 + { id: 1, name: 'Phoebe' }, 493 + { id: 2, name: 'Rachel' }, 494 + { id: 3, name: 'Ross' }, 495 + ]; 496 + 497 + function ChatRecipientPicker() { 498 + const [recipientID, setRecipientID] = useState(1); 499 + const isRecipientOnline = useFriendStatus(recipientID); 500 + 501 + return ( 502 + <> 503 + <Circle color={isRecipientOnline ? 'green' : 'red'} /> 504 + <select 505 + value={recipientID} 506 + onChange={e => setRecipientID(Number(e.target.value))} 507 + > 508 + {friendList.map(friend => ( 509 + <option key={friend.id} value={friend.id}> 510 + {friend.name} 511 + </option> 512 + ))} 513 + </select> 514 + </> 515 + ); 516 + } 517 + 518 + function useFriendStatus(friendID) { 519 + const [isOnline, setIsOnline] = useState(null); 520 + const handleStatusChange = (status) => setIsOnline(status.isOnline); 521 + useEffect(() => { 522 + ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); 523 + return () => { 524 + ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); 525 + }; 526 + }); 527 + return isOnline; 528 + } 529 + ``` 530 + 531 + When you change the recipient, our `useFriendStatus()` Hook would unsubscribe from the previous friend’s status, and subscribe to the next one. 532 + 533 + This works because we can pass the return value of the `useState()` Hook to the `useFriendStatus()` Hook: 534 + 535 + ```jsx{2} 536 + const [recipientID, setRecipientID] = useState(1); 537 + const isRecipientOnline = useFriendStatus(recipientID); 538 + ``` 539 + 540 + Passing values between Hooks is very powerful. For example, [React Spring](https://medium.com/@drcmda/hooks-in-react-spring-a-tutorial-c6c436ad7ee4) lets you create a trailing animation of several values “following” each other: 541 + 542 + ```jsx 543 + const [{ pos1 }, set] = useSpring({ pos1: [0, 0], config: fast }); 544 + const [{ pos2 }] = useSpring({ pos2: pos1, config: slow }); 545 + const [{ pos3 }] = useSpring({ pos3: pos2, config: slow }); 546 + ``` 547 + 548 + (Here’s a [demo](https://codesandbox.io/s/ppxnl191zx).) 549 + 550 + Proposals that put Hook initialization into default argument values or that write Hooks in a decorator form make it difficult to express this kind of logic. 551 + 552 + If calling Hooks doesn’t happen in the function body, you can no longer easily pass values between them, transform those values without creating many layers of components, or add `useMemo()` to memoize an intermediate computation. You also can’t easily reference these values in effects because they can’t capture them in a closure. There are ways to work around these issues with some convention but they require you to mentally “match up” inputs and outputs. This is tricky and violates React’s otherwise direct style. 553 + 554 + Passing values between Hooks is at the heart of our proposal. Render props pattern was the closest you could get to it without Hooks, but you couldn’t get full benefits without something like [Component Component](https://ui.reach.tech/component-component) which has a lot of syntactic noise due to a “false hierarchy”. Hooks flatten that hierarchy to passing values — and function calls is the simplest way to do that. 555 + 556 + ### Flaw #8: Too Much Ceremony 557 + 558 + There are many proposals that fall under this umbrella. Most attempt to avoid the perceived dependency of Hooks on React. There is a wide variety of ways to do it: by making built-in Hooks available on `this`, making them an extra argument you have to pass through everything, and so on. 559 + 560 + I think [Sebastian’s answer](https://github.com/reactjs/rfcs/pull/68#issuecomment-439314884) addresses this way better than I could describe so I encourage you to check out its first section (“Injection Model”). 561 + 562 + I’ll just say there is a reason programmers tend to prefer `try` / `catch` for error handling to passing error codes through every function. It’s the same reason why we prefer ES Modules with `import` (or CommonJS `require`) to AMD’s “explicit” definitions where `require` is passed to us. 563 + 564 + ```jsx 565 + // Anyone miss AMD? 566 + define(['require', 'dependency1', 'dependency2'], function (require) { 567 + var dependency1 = require('dependency1'), 568 + var dependency2 = require('dependency2'); 569 + return function () {}; 570 + }); 571 + ``` 572 + 573 + Yes, AMD may be more “honest” to the fact that modules aren’t actually synchronously loaded in a browser environment. But once you learn about that, writing the `define` sandwich becomes a mindless chore. 574 + 575 + `try` / `catch`, `require`, and React Context API are pragmatic examples of how we want to have some “ambient” handler available to us instead of explicitly threading it through every level — even if in general we value explicitness. I think the same is true for Hooks. 576 + 577 + This is similar to how, when we define components, we just grab `Component` from `React`. Maybe our code would be more decoupled from React if we exported a factory for every component instead: 578 + 579 + ```jsx 580 + function createModal(React) { 581 + return class Modal extends React.Component { 582 + // ... 583 + }; 584 + } 585 + ``` 586 + 587 + But in practice this ends up being just an annoying indirection. When we actually want to stub React with something else, we can always do that at the module system level instead. 588 + 589 + The same applies to Hooks. Still, as [Sebastian’s answer](https://github.com/reactjs/rfcs/pull/68#issuecomment-439314884) mentions, it is *technically possible* to “redirect” Hooks exported from `react` to a different implementation. ([One of my previous posts](/how-does-setstate-know-what-to-do/) mentions how.) 590 + 591 + Another way to impose more ceremony is by making Hooks [monadic](https://paulgray.net/an-alternative-design-for-hooks/) or adding a first-class concept like `React.createHook()`. Aside from the runtime overhead, any solution that adds wrappers loses a huge benefit of using plain functions: *they are as easy to debug as it gets*. 592 + 593 + Plain functions let you step in and out with a debugger without any library code in the middle, and see exactly how values flow inside your component body. Indirections make this difficult. Solutions similar in spirit to either higher-order components (“decorator” Hooks) or render props (e.g. `adopt` proposal or `yield`ing from generators) suffer from the same problem. Indirections also complicate static typing. 594 + 595 + --- 596 + 597 + As I mentioned earlier, this post doesn’t aim to be exhaustive. There are other interesting problems with different proposals. Some of them are more obscure (e.g. related to concurrency or advanced compilation techniques) and might be a topic for another blog post in the future. 598 + 599 + Hooks aren’t perfect either, but it’s the best tradeoff we could find for solving these problems. There are things we [still need to fix](https://github.com/reactjs/rfcs/pull/68#issuecomment-440780509), and there exist things that are more awkward with Hooks than classes. That is also a topic for another blog post. 600 + 601 + Whether I covered your favorite alternative proposal or not, I hope this writeup helped shed some light on our thinking process and the criteria we consider when choosing an API. As you can see, a lot of it (such as making sure that copy-pasting, moving code, adding and removing dependencies works as expected) has to do with [optimizing for change](/optimized-for-change/). I hope that React users will appreciate these aspects.
+154
public/why-do-react-elements-have-typeof-property/index.md
··· 1 + --- 2 + title: Why Do React Elements Have a $$typeof Property? 3 + date: '2018-12-03' 4 + spoiler: It has something to do with security. 5 + cta: 'react' 6 + --- 7 + 8 + You might think you’re writing JSX: 9 + 10 + ```jsx 11 + <marquee bgcolor="#ffa7c4">hi</marquee> 12 + ``` 13 + 14 + But really, you’re calling a function: 15 + 16 + ```jsx 17 + React.createElement( 18 + /* type */ 'marquee', 19 + /* props */ { bgcolor: '#ffa7c4' }, 20 + /* children */ 'hi' 21 + ) 22 + ``` 23 + 24 + And that function gives you back an object. We call this object a React *element*. It tells React what to render next. Your components return a tree of them. 25 + 26 + ```jsx{9} 27 + { 28 + type: 'marquee', 29 + props: { 30 + bgcolor: '#ffa7c4', 31 + children: 'hi', 32 + }, 33 + key: null, 34 + ref: null, 35 + $$typeof: Symbol.for('react.element'), // 🧐 Who dis 36 + } 37 + ``` 38 + 39 + If you used React you might be familiar with `type`, `props`, `key`, and `ref` fields. **But what is `$$typeof`? And why does it have a `Symbol()` as a value?** 40 + 41 + That’s another one of those things that you don’t *need* to know to use React, but that will make you feel good when you do. There’s also some tips about security in this post that you might want to know. Maybe one day you’ll write your own UI library and all of this will come in handy. I certainly hope so. 42 + 43 + --- 44 + 45 + Before client-side UI libraries became common and added basic protection, it was common for app code to construct HTML and insert it into the DOM: 46 + 47 + ```jsx 48 + const messageEl = document.getElementById('message'); 49 + messageEl.innerHTML = '<p>' + message.text + '</p>'; 50 + ``` 51 + 52 + That works fine, except when your `message.text` is something like `'<img src onerror="stealYourPassword()">'`. **You don’t want things written by strangers to appear verbatim in your app’s rendered HTML.** 53 + 54 + (Fun fact: if you only do client-side rendering, a `<script>` tag here wouldn’t let you run JavaScript. But [don’t let this](https://gomakethings.com/preventing-cross-site-scripting-attacks-when-using-innerhtml-in-vanilla-javascript/) lull you into a false sense of security.) 55 + 56 + To protect against such attacks, you can use safe APIs like `document.createTextNode()` or `textContent` that only deal with text. You can also preemptively “escape” inputs by replacing potentially dangerous characters like `<`, `>` and others in any user-provided text. 57 + 58 + Still, the cost of a mistake is high and it’s a hassle to remember it every time you interpolate a user-written string into your output. **This is why modern libraries like React escape text content for strings by default:** 59 + 60 + ```jsx 61 + <p> 62 + {message.text} 63 + </p> 64 + ``` 65 + 66 + If `message.text` is a malicious string with an `<img>` or another tag, it won’t turn into a real `<img>` tag. React will escape the content and *then* insert it into the DOM. So instead of seeing the `<img>` tag you’ll just see its markup. 67 + 68 + To render arbitrary HTML inside a React element, you have to write `dangerouslySetInnerHTML={{ __html: message.text }}`. **The fact that it’s clumsy to write is a *feature*.** It’s meant to be highly visible so that you can catch it in code reviews and codebase audits. 69 + 70 + --- 71 + 72 + **Does it mean React is entirely safe from injection attacks? No.** HTML and DOM offer [plenty of attack surface](https://github.com/facebook/react/issues/3473#issuecomment-90594748) that is too difficult or slow for React or other UI libraries to mitigate against. Most of the remaining attack vectors involve attributes. For example, if you render `<a href={user.website}>`, beware of the user whose website is `'javascript: stealYourPassword()'`. Spreading user input like `<div {...userData}>` is rare but also dangerous. 73 + 74 + React [could](https://github.com/facebook/react/issues/10506) provide more protection over time but in many cases these are consequences of server issues that [should](https://github.com/facebook/react/issues/3473#issuecomment-91327040) be fixed there anyway. 75 + 76 + Still, escaping text content is a reasonable first line of defence that catches a lot of potential attacks. Isn’t it nice to know that code like this is safe? 77 + 78 + ```jsx 79 + // Escaped automatically 80 + <p> 81 + {message.text} 82 + </p> 83 + ``` 84 + 85 + **Well, that wasn’t always true either.** And that’s where `$$typeof` comes in. 86 + 87 + --- 88 + 89 + React elements are plain objects by design: 90 + 91 + ```jsx 92 + { 93 + type: 'marquee', 94 + props: { 95 + bgcolor: '#ffa7c4', 96 + children: 'hi', 97 + }, 98 + key: null, 99 + ref: null, 100 + $$typeof: Symbol.for('react.element'), 101 + } 102 + ``` 103 + 104 + While normally you create them with `React.createElement()`, it is not required. There are valid use cases for React to support plain element objects written like I just did above. Of course, you probably wouldn’t *want* to write them like this — but this [can be](https://github.com/facebook/react/pull/3583#issuecomment-90296667) useful for an optimizing compiler, passing UI elements between workers, or for decoupling JSX from the React package. 105 + 106 + However, **if your server has a hole that lets the user store an arbitrary JSON object** while the client code expects a string, this could become a problem: 107 + 108 + ```jsx{2-10,15} 109 + // Server could have a hole that lets user store JSON 110 + let expectedTextButGotJSON = { 111 + type: 'div', 112 + props: { 113 + dangerouslySetInnerHTML: { 114 + __html: '/* put your exploit here */' 115 + }, 116 + }, 117 + // ... 118 + }; 119 + let message = { text: expectedTextButGotJSON }; 120 + 121 + // Dangerous in React 0.13 122 + <p> 123 + {message.text} 124 + </p> 125 + ``` 126 + 127 + In that case, React 0.13 would be [vulnerable](http://danlec.com/blog/xss-via-a-spoofed-react-element) to an XSS attack. To clarify, again, **this attack depends on an existing server hole**. Still, React could do a better job of protecting people against it. And starting with React 0.14, it does. 128 + 129 + The fix in React 0.14 was to [tag every React element with a Symbol](https://github.com/facebook/react/pull/4832): 130 + 131 + ```jsx{9} 132 + { 133 + type: 'marquee', 134 + props: { 135 + bgcolor: '#ffa7c4', 136 + children: 'hi', 137 + }, 138 + key: null, 139 + ref: null, 140 + $$typeof: Symbol.for('react.element'), 141 + } 142 + ``` 143 + 144 + This works because you can’t just put `Symbol`s in JSON. **So even if the server has a security hole and returns JSON instead of text, that JSON can’t include `Symbol.for('react.element')`.** React will check `element.$$typeof`, and will refuse to process the element if it’s missing or invalid. 145 + 146 + The nice thing about using `Symbol.for()` specifically is that **Symbols are global between environments like iframes and workers.** So this fix doesn’t prevent passing trusted elements between different parts of the app even in more exotic conditions. Similarly, even if there are multiple copies of React on the page, they can still “agree” on the valid `$$typeof` value. 147 + 148 + --- 149 + 150 + What about the browsers that [don’t support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Browser_compatibility) Symbols? 151 + 152 + Alas, they don’t get this extra protection. React still includes the `$$typeof` field on the element for consistency, but it’s [set to a number](https://github.com/facebook/react/blob/8482cbe22d1a421b73db602e1f470c632b09f693/packages/shared/ReactSymbols.js#L14-L16) — `0xeac7`. 153 + 154 + Why this number specifically? `0xeac7` kinda looks like “React”.
+193
public/why-do-we-write-super-props/index.md
··· 1 + --- 2 + title: Why Do We Write super(props)? 3 + date: '2018-11-30' 4 + spoiler: There’s a twist at the end. 5 + --- 6 + 7 + 8 + I heard [Hooks](https://reactjs.org/docs/hooks-intro.html) are the new hotness. Ironically, I want to start this blog by describing fun facts about *class* components. How about that! 9 + 10 + **These gotchas are *not* important for using React productively. But you might find them amusing if you like to dig deeper into how things work.** 11 + 12 + Here’s the first one. 13 + 14 + --- 15 + 16 + I wrote `super(props)` more times in my life than I’d like to know: 17 + 18 + ```jsx{3} 19 + class Checkbox extends React.Component { 20 + constructor(props) { 21 + super(props); 22 + this.state = { isOn: true }; 23 + } 24 + // ... 25 + } 26 + ``` 27 + 28 + Of course, the [class fields proposal](https://github.com/tc39/proposal-class-fields) lets us skip the ceremony: 29 + 30 + ```jsx 31 + class Checkbox extends React.Component { 32 + state = { isOn: true }; 33 + // ... 34 + } 35 + ``` 36 + 37 + A syntax like this was [planned](https://reactjs.org/blog/2015/01/27/react-v0.13.0-beta-1.html#es7-property-initializers) when React 0.13 added support for plain classes in 2015. Defining `constructor` and calling `super(props)` was always intended to be a temporary solution until class fields provide an ergonomic alternative. 38 + 39 + But let’s get back to this example using only ES2015 features: 40 + 41 + ```jsx{3} 42 + class Checkbox extends React.Component { 43 + constructor(props) { 44 + super(props); 45 + this.state = { isOn: true }; 46 + } 47 + // ... 48 + } 49 + ``` 50 + 51 + **Why do we call `super`? Can we *not* call it? If we have to call it, what happens if we don’t pass `props`? Are there any other arguments?** Let’s find out. 52 + 53 + --- 54 + 55 + In JavaScript, `super` refers to the parent class constructor. (In our example, it points to the `React.Component` implementation.) 56 + 57 + Importantly, you can’t use `this` in a constructor until *after* you’ve called the parent constructor. JavaScript won’t let you: 58 + 59 + ```jsx 60 + class Checkbox extends React.Component { 61 + constructor(props) { 62 + // 🔴 Can’t use `this` yet 63 + super(props); 64 + // ✅ Now it’s okay though 65 + this.state = { isOn: true }; 66 + } 67 + // ... 68 + } 69 + ``` 70 + 71 + There’s a good reason for why JavaScript enforces that parent constructor runs before you touch `this`. Consider a class hierarchy: 72 + 73 + ```jsx 74 + class Person { 75 + constructor(name) { 76 + this.name = name; 77 + } 78 + } 79 + 80 + class PolitePerson extends Person { 81 + constructor(name) { 82 + this.greetColleagues(); // 🔴 This is disallowed, read below why 83 + super(name); 84 + } 85 + greetColleagues() { 86 + alert('Good morning folks!'); 87 + } 88 + } 89 + ``` 90 + 91 + Imagine using `this` before `super` call *was* allowed. A month later, we might change `greetColleagues` to include the person’s name in the message: 92 + 93 + ```jsx 94 + greetColleagues() { 95 + alert('Good morning folks!'); 96 + alert('My name is ' + this.name + ', nice to meet you!'); 97 + } 98 + ``` 99 + 100 + But we forgot that `this.greetColleagues()` is called before the `super()` call had a chance to set up `this.name`. So `this.name` isn’t even defined yet! As you can see, code like this can be very difficult to think about. 101 + 102 + To avoid such pitfalls, **JavaScript enforces that if you want to use `this` in a constructor, you *have to* call `super` first.** Let the parent do its thing! And this limitation applies to React components defined as classes too: 103 + 104 + ```jsx 105 + constructor(props) { 106 + super(props); 107 + // ✅ Okay to use `this` now 108 + this.state = { isOn: true }; 109 + } 110 + ``` 111 + 112 + This leaves us with another question: why pass `props`? 113 + 114 + --- 115 + 116 + You might think that passing `props` down to `super` is necessary so that the base `React.Component` constructor can initialize `this.props`: 117 + 118 + ```jsx 119 + // Inside React 120 + class Component { 121 + constructor(props) { 122 + this.props = props; 123 + // ... 124 + } 125 + } 126 + ``` 127 + 128 + And that’s not far from truth — indeed, that’s [what it does](https://github.com/facebook/react/blob/1d25aa5787d4e19704c049c3cfa985d3b5190e0d/packages/react/src/ReactBaseClasses.js#L22). 129 + 130 + But somehow, even if you call `super()` without the `props` argument, you’ll still be able to access `this.props` in the `render` and other methods. (If you don’t believe me, try it yourself!) 131 + 132 + How does *that* work? It turns out that **React also assigns `props` on the instance right after calling *your* constructor:** 133 + 134 + ```jsx 135 + // Inside React 136 + const instance = new YourComponent(props); 137 + instance.props = props; 138 + ``` 139 + 140 + So even if you forget to pass `props` to `super()`, React would still set them right afterwards. There is a reason for that. 141 + 142 + When React added support for classes, it didn’t just add support for ES6 classes alone. The goal was to support as wide range of class abstractions as possible. It was [not clear](https://reactjs.org/blog/2015/01/27/react-v0.13.0-beta-1.html#other-languages) how relatively successful would ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript, or other solutions be for defining components. So React was intentionally unopinionated about whether calling `super()` is required — even though ES6 classes are. 143 + 144 + So does this mean you can just write `super()` instead of `super(props)`? 145 + 146 + **Probably not because it’s still confusing.** Sure, React would later assign `this.props` *after* your constructor has run. But `this.props` would still be undefined *between* the `super` call and the end of your constructor: 147 + 148 + ```jsx{14} 149 + // Inside React 150 + class Component { 151 + constructor(props) { 152 + this.props = props; 153 + // ... 154 + } 155 + } 156 + 157 + // Inside your code 158 + class Button extends React.Component { 159 + constructor(props) { 160 + super(); // 😬 We forgot to pass props 161 + console.log(props); // ✅ {} 162 + console.log(this.props); // 😬 undefined 163 + } 164 + // ... 165 + } 166 + ``` 167 + 168 + It can be even more challenging to debug if this happens in some method that’s called *from* the constructor. **And that’s why I recommend always passing down `super(props)`, even though it isn’t strictly necessary:** 169 + 170 + ```jsx 171 + class Button extends React.Component { 172 + constructor(props) { 173 + super(props); // ✅ We passed props 174 + console.log(props); // ✅ {} 175 + console.log(this.props); // ✅ {} 176 + } 177 + // ... 178 + } 179 + ``` 180 + 181 + This ensures `this.props` is set even before the constructor exits. 182 + 183 + ----- 184 + 185 + There’s one last bit that longtime React users might be curious about. 186 + 187 + You might have noticed that when you use the Context API in classes (either with the legacy `contextTypes` or the modern `contextType` API added in React 16.6), `context` is passed as a second argument to the constructor. 188 + 189 + So why don’t we write `super(props, context)` instead? We could, but context is used less often so this pitfall just doesn’t come up as much. 190 + 191 + **With the class fields proposal this whole pitfall mostly disappears anyway.** Without an explicit constructor, all arguments are passed down automatically. This is what allows an expression like `state = {}` to include references to `this.props` or `this.context` if necessary. 192 + 193 + With Hooks, we don’t even have `super` or `this`. But that’s a topic for another day.
+238
public/why-isnt-x-a-hook/index.md
··· 1 + --- 2 + title: Why Isn’t X a Hook? 3 + date: '2019-01-26' 4 + spoiler: Just because we can, doesn’t mean we should. 5 + cta: 'react' 6 + --- 7 + 8 + Since the first alpha version of [React Hooks](https://reactjs.org/hooks) was released, there is a question that keeps coming up in discussions: “Why isn’t *\<some other API\>* a Hook?” 9 + 10 + To remind you, here’s a few things that *are* Hooks: 11 + 12 + * [`useState()`](https://reactjs.org/docs/hooks-reference.html#usestate) lets you declare a state variable. 13 + * [`useEffect()`](https://reactjs.org/docs/hooks-reference.html#useeffect) lets you declare a side effect. 14 + * [`useContext()`](https://reactjs.org/docs/hooks-reference.html#usecontext) lets you read some context. 15 + 16 + But there are some other APIs, like `React.memo()` and `<Context.Provider>`, that are *not* Hooks. Commonly proposed Hook versions of them would be *noncompositional* or *antimodular*. This article will help you understand why. 17 + 18 + **Note: this post is a deep dive for folks who are interested in API discussions. You don’t need to think about any of this to be productive with React!** 19 + 20 + --- 21 + 22 + There are two important properties that we want React APIs to preserve: 23 + 24 + 1. **Composition:** [Custom Hooks](https://reactjs.org/docs/hooks-custom.html) are largely the reason we’re excited about the Hooks API. We expect people to build their own Hooks very often, and we need to make sure Hooks written by different people [don't conflict](/why-do-hooks-rely-on-call-order/#flaw-4-the-diamond-problem). (Aren’t we all spoiled by how components compose cleanly and don’t break each other?) 25 + 26 + 2. **Debugging:** We want the bugs to be [easy to find](/the-bug-o-notation/) as the application grows. One of React's best features is that if you see something wrong rendered, you can walk up the tree until you find which component's prop or state caused the mistake. 27 + 28 + These two constraints put together can tell us what can or *cannot* be a Hook. Let’s try a few examples. 29 + 30 + --- 31 + 32 + ## A Real Hook: `useState()` 33 + 34 + ### Composition 35 + 36 + Multiple custom Hooks each calling `useState()` don’t conflict: 37 + 38 + ```jsx 39 + function useMyCustomHook1() { 40 + const [value, setValue] = useState(0); 41 + // What happens here, stays here. 42 + } 43 + 44 + function useMyCustomHook2() { 45 + const [value, setValue] = useState(0); 46 + // What happens here, stays here. 47 + } 48 + 49 + function MyComponent() { 50 + useMyCustomHook1(); 51 + useMyCustomHook2(); 52 + // ... 53 + } 54 + ``` 55 + 56 + Adding a new unconditional `useState()` call is always safe. You don’t need to know anything about other Hooks used by a component to declare a new state variable. You also can’t break other state variables by updating one of them. 57 + 58 + **Verdict:** ✅ `useState()` doesn’t make custom Hooks fragile. 59 + 60 + ### Debugging 61 + 62 + Hooks are useful because you can pass values *between* Hooks: 63 + 64 + ```jsx{4,12,14} 65 + function useWindowWidth() { 66 + const [width, setWidth] = useState(window.innerWidth); 67 + // ... 68 + return width; 69 + } 70 + 71 + function useTheme(isMobile) { 72 + // ... 73 + } 74 + 75 + function Comment() { 76 + const width = useWindowWidth(); 77 + const isMobile = width < MOBILE_VIEWPORT; 78 + const theme = useTheme(isMobile); 79 + return ( 80 + <section className={theme.comment}> 81 + {/* ... */} 82 + </section> 83 + ); 84 + } 85 + ``` 86 + 87 + But what if we make a mistake? What’s the debugging story? 88 + 89 + Let's say the CSS class we get from `theme.comment` is wrong. How would we debug this? We can set a breakpoint or a few logs in the body of our component. 90 + 91 + Maybe we’d see that `theme` is wrong but `width` and `isMobile` are correct. That would tell us the problem is inside `useTheme()`. Or perhaps we'd see that `width` itself is wrong. That would tell us to look into `useWindowWidth()`. 92 + 93 + **A single look at the intermediate values tells us which of the Hooks at the top level contains the bug.** We don't need to look at *all* of their implementations. 94 + 95 + Then we can “zoom in” on the one that has a bug, and repeat. 96 + 97 + This becomes more important if the depth of custom Hook nesting increases. Imagine we have 3 levels of custom Hook nesting, each level using 3 different custom Hooks inside. The [difference](/the-bug-o-notation/) between looking for a bug in **3 places** versus potentially checking **3 + 3×3 + 3×3×3 = 39 places** is enormous. Luckily, `useState()` can't magically “influence” other Hooks or components. A buggy value returned by it leaves a trail behind it, just like any variable. 🐛 98 + 99 + **Verdict:** ✅ `useState()` doesn’t obscure the cause-effect relationship in our code. We can follow the breadcrumbs directly to the bug. 100 + 101 + --- 102 + 103 + ## Not a Hook: `useBailout()` 104 + 105 + As an optimization, components using Hooks can bail out of re-rendering. 106 + 107 + One way to do it is to put a [`React.memo()`](https://reactjs.org/blog/2018/10/23/react-v-16-6.html#reactmemo) wrapper around the whole component. It bails out of re-rendering if props are shallowly equal to what we had during the last render. This makes it similar to `PureComponent` in classes. 108 + 109 + `React.memo()` takes a component and returns a component: 110 + 111 + ```jsx{4} 112 + function Button(props) { 113 + // ... 114 + } 115 + export default React.memo(Button); 116 + ``` 117 + 118 + **But why isn’t it just a Hook?** 119 + 120 + Whether you call it `useShouldComponentUpdate()`, `usePure()`, `useSkipRender()`, or `useBailout()`, the proposal tends to look something like this: 121 + 122 + ```jsx 123 + function Button({ color }) { 124 + // ⚠️ Not a real API 125 + useBailout(prevColor => prevColor !== color, color); 126 + 127 + return ( 128 + <button className={'button-' + color}> 129 + OK 130 + </button> 131 + ) 132 + } 133 + ``` 134 + 135 + There are a few more variations (e.g. a simple `usePure()` marker) but in broad strokes they have the same flaws. 136 + 137 + ### Composition 138 + 139 + Let’s say we try to put `useBailout()` in two custom Hooks: 140 + 141 + ```jsx{4,5,19,20} 142 + function useFriendStatus(friendID) { 143 + const [isOnline, setIsOnline] = useState(null); 144 + 145 + // ⚠️ Not a real API 146 + useBailout(prevIsOnline => prevIsOnline !== isOnline, isOnline); 147 + 148 + useEffect(() => { 149 + const handleStatusChange = status => setIsOnline(status.isOnline); 150 + ChatAPI.subscribe(friendID, handleStatusChange); 151 + return () => ChatAPI.unsubscribe(friendID, handleStatusChange); 152 + }); 153 + 154 + return isOnline; 155 + } 156 + 157 + function useWindowWidth() { 158 + const [width, setWidth] = useState(window.innerWidth); 159 + 160 + // ⚠️ Not a real API 161 + useBailout(prevWidth => prevWidth !== width, width); 162 + 163 + useEffect(() => { 164 + const handleResize = () => setWidth(window.innerWidth); 165 + window.addEventListener('resize', handleResize); 166 + return () => window.removeEventListener('resize', handleResize); 167 + }); 168 + 169 + return width; 170 + } 171 + ``` 172 + 173 + Now what happens if you use them both in the same component? 174 + 175 + 176 + ```jsx{2,3} 177 + function ChatThread({ friendID, isTyping }) { 178 + const width = useWindowWidth(); 179 + const isOnline = useFriendStatus(friendID); 180 + return ( 181 + <ChatLayout width={width}> 182 + <FriendStatus isOnline={isOnline} /> 183 + {isTyping && 'Typing...'} 184 + </ChatLayout> 185 + ); 186 + } 187 + ``` 188 + 189 + When does it re-render? 190 + 191 + If every `useBailout()` call has the power to skip an update, then updates from `useWindowWidth()` would be blocked by `useFriendStatus()`, and vice versa. **These Hooks would break each other.** 192 + 193 + However, if `useBailout()` was only respected when *all* calls to it inside a single component “agree” to block an update, our `ChatThread` would fail to update on changes to the `isTyping` prop. 194 + 195 + Even worse, with these semantics **any newly added Hooks to `ChatThread` would break if they don’t *also* call `useBailout()`**. Otherwise, they can’t “vote against” the bailout inside `useWindowWidth()` and `useFriendStatus()`. 196 + 197 + **Verdict:** 🔴 `useBailout()` breaks composition. Adding it to a Hook breaks state updates in other Hooks. We want the APIs to be [antifragile](/optimized-for-change/), and this behavior is pretty much the opposite. 198 + 199 + ### Debugging 200 + 201 + How does a Hook like `useBailout()` affect debugging? 202 + 203 + We’ll use the same example: 204 + 205 + ```jsx 206 + function ChatThread({ friendID, isTyping }) { 207 + const width = useWindowWidth(); 208 + const isOnline = useFriendStatus(friendID); 209 + return ( 210 + <ChatLayout width={width}> 211 + <FriendStatus isOnline={isOnline} /> 212 + {isTyping && 'Typing...'} 213 + </ChatLayout> 214 + ); 215 + } 216 + ``` 217 + 218 + Let’s say the `Typing...` label doesn’t appear when we expect, even though somewhere many layers above the prop is changing. How do we debug it? 219 + 220 + **Normally, in React you can confidently answer this question by looking *up*.** If `ChatThread` doesn’t get a new `isTyping` value, we can open the component that renders `<ChatThread isTyping={myVar} />` and check `myVar`, and so on. At one of these levels, we’ll either find a buggy `shouldComponentUpdate()` bailout, or an incorrect `isTyping` value being passed down. One look at each component in the chain is usually enough to locate the source of the problem. 221 + 222 + However, if this `useBailout()` Hook was real, you would never know the reason an update was skipped until you checked *every single custom Hook* (deeply) used by our `ChatThread` and components in its owner chain. Since every parent component can *also* use custom Hooks, this [scales](/the-bug-o-notation/) terribly. 223 + 224 + It’s like if you were looking for a screwdriver in a chest of drawers, and each drawer contained a bunch of smaller chests of drawers, and you don’t know how deep the rabbit hole goes. 225 + 226 + **Verdict:** 🔴 Not only `useBailout()` Hook breaks composition, but it also vastly increases the number of debugging steps and cognitive load for finding a buggy bailout — in some cases, exponentially. 227 + 228 + --- 229 + 230 + We just looked at one real Hook, `useState()`, and a common suggestion that is intentionally *not* a Hook — `useBailout()`. We compared them through the prism of Composition and Debugging, and discussed why one of them works and the other one doesn’t. 231 + 232 + While there is no “Hook version” of `memo()` or `shouldComponentUpdate()`, React *does* provide a Hook called [`useMemo()`](https://reactjs.org/docs/hooks-reference.html#usememo). It serves a similar purpose, but its semantics are different enough to not run into the pitfalls described above. 233 + 234 + `useBailout()` is just one example of something that doesn’t work well as a Hook. But there are a few others — for example, `useProvider()`, `useCatch()`, or `useSuspense()`. 235 + 236 + Can you see why? 237 + 238 + *(Whispers: Composition... Debugging...)*
+807
public/writing-resilient-components/index.md
··· 1 + --- 2 + title: Writing Resilient Components 3 + date: '2019-03-16' 4 + spoiler: Four principles to set you on the right path. 5 + --- 6 + 7 + When people start learning React, they often ask for a style guide. While it’s a good idea to have some consistent rules applied across a project, a lot of them are arbitrary — and so React doesn’t have a strong opinion about them. 8 + 9 + You can use different type systems, prefer function declarations or arrow functions, sort your props in alphabetical order or in an order you find pleasing. 10 + 11 + This flexibility allows [integrating React](https://reactjs.org/docs/add-react-to-a-website.html) into projects with existing conventions. But it also invites endless debates. 12 + 13 + **There _are_ important design principles that every component should strive to follow. But I don’t think style guides capture those principles well. We’ll talk about style guides first, and then [look at the principles that really _are_ useful](#writing-resilient-components).** 14 + 15 + --- 16 + 17 + ## Don’t Get Distracted by Imaginary Problems 18 + 19 + Before we talk about component design principles, I want to say a few words about style guides. This isn’t a popular opinion but someone needs to say it! 20 + 21 + In the JavaScript community, there are a few strict opinionated style guides enforced by a linter. My personal observation is that they tend to create more friction than they’re worth. I can’t count how many times somebody showed me some absolutely valid code and said “React complains about this”, but it was their lint config complaining! This leads to three issues: 22 + 23 + * People get used to seeing the linter as an **overzealous noisy gatekeeper** rather than a helpful tool. Useful warnings are drowned out by a sea of style nits. As a result, people don’t scan the linter messages while debugging, and miss helpful tips. Additionally, people who are less used to writing JavaScript (for example, designers) have a harder time working with the code. 24 + 25 + * People don’t learn to **differentiate between valid and invalid uses** of a certain pattern. For example, there is a popular rule that forbids calling `setState` inside `componentDidMount`. But if it was always “bad”, React simply wouldn’t allow it! There is a legitimate use case for it, and that is to measure the DOM node layout — e.g. to position a tooltip. I’ve seen people “work around” this rule by adding a `setTimeout` which completely misses the point. 26 + 27 + * Eventually, people adopt the “enforcer mindset” and get opinionated about things that **don’t bring a meaningful difference** but are easy to scan for in the code. “You used a function declaration, but *our* project uses arrow functions.” Whenever I have a strong feeling about enforcing a rule like this, looking deeper reveals that I invested emotional effort into this rule — and struggle to let it go. It lulls me into a false sense of accomplishment without improving my code. 28 + 29 + Am I saying that we should stop linting? Not at all! 30 + 31 + **With a good config, a linter is a great tool to catch bugs before they happen.** It’s focusing on the *style* too much that turns it into a distraction. 32 + 33 + --- 34 + 35 + ## Marie Kondo Your Lint Config 36 + 37 + Here’s what I suggest you to do on Monday. Gather your team for half an hour, go through every lint rule enabled in your project’s config, and ask yourself: *“Has this rule ever helped us catch a bug?”* If not, *turn it off.* (You can also start from a clean slate with [`eslint-config-react-app`](https://www.npmjs.com/package/eslint-config-react-app) which has no styling rules.) 38 + 39 + At the very least, your team should have a process for removing rules that cause friction. Don’t assume that whatever you or something somebody else added to your lint config a year ago is a “best practice”. Question it and look for answers. Don’t let anyone tell you you’re not smart enough to pick your lint rules. 40 + 41 + **But what about formatting?** Use [Prettier](https://prettier.io/) and forget about the “style nits”. You don’t need a tool to shout at you for putting an extra space if another tool can fix it for you. Use the linter to find *bugs*, not enforcing the *a e s t h e t i c s*. 42 + 43 + Of course, there are aspects of the coding style that aren’t directly related to formatting but can still be annoying when inconsistent across the project. 44 + 45 + However, many of them are too subtle to catch with a lint rule anyway. This is why it’s important to **build trust** between the team members, and to share useful learnings in the form of a wiki page or a short design guide. 46 + 47 + Not everything is worth automating! The insights gained from *actually reading* the rationale in such a guide can be more valuable than following the “rules”. 48 + 49 + **But if following a strict style guide is a distraction, what’s actually important?** 50 + 51 + That’s the topic of this post. 52 + 53 + --- 54 + 55 + ## Writing Resilient Components 56 + 57 + No amount of indentation or sorting imports alphabetically can fix a broken design. So instead of focusing on how some code *looks*, I will focus on how it *works*. There’s a few component design principles that I find very helpful: 58 + 59 + 1. **[Don’t stop the data flow](#principle-1-dont-stop-the-data-flow)** 60 + 2. **[Always be ready to render](#principle-2-always-be-ready-to-render)** 61 + 3. **[No component is a singleton](#principle-3-no-component-is-a-singleton)** 62 + 4. **[Keep the local state isolated](#principle-4-keep-the-local-state-isolated)** 63 + 64 + Even if you don’t use React, you’ll likely discover the same principles by trial and error for any UI component model with unidirectional data flow. 65 + 66 + --- 67 + 68 + ## Principle 1: Don’t Stop the Data Flow 69 + 70 + ### Don’t Stop the Data Flow in Rendering 71 + 72 + When somebody uses your component, they expect that they can pass different props to it over time, and that the component will reflect those changes: 73 + 74 + ```jsx 75 + // isOk might be driven by state and can change at any time 76 + <Button color={isOk ? 'blue' : 'red'} /> 77 + ``` 78 + 79 + In general, this is how React works by default. If you use a `color` prop inside a `Button` component, you’ll see the value provided from above for that render: 80 + 81 + ```jsx 82 + function Button({ color, children }) { 83 + return ( 84 + // ✅ `color` is always fresh! 85 + <button className={'Button-' + color}> 86 + {children} 87 + </button> 88 + ); 89 + } 90 + ``` 91 + 92 + However, a common mistake when learning React is to copy props into state: 93 + 94 + ```jsx{3,6} 95 + class Button extends React.Component { 96 + state = { 97 + color: this.props.color 98 + }; 99 + render() { 100 + const { color } = this.state; // 🔴 `color` is stale! 101 + return ( 102 + <button className={'Button-' + color}> 103 + {this.props.children} 104 + </button> 105 + ); 106 + } 107 + } 108 + ``` 109 + 110 + This might seem more intuitive at first if you used classes outside of React. **However, by copying a prop into state you’re ignoring all updates to it.** 111 + 112 + ```jsx 113 + // 🔴 No longer works for updates with the above implementation 114 + <Button color={isOk ? 'blue' : 'red'} /> 115 + ``` 116 + 117 + In the rare case that this behavior *is* intentional, make sure to call that prop `initialColor` or `defaultColor` to clarify that changes to it are ignored. 118 + 119 + But usually you’ll want to **read the props directly in your component** and avoid copying props (or anything computed from the props) into state: 120 + 121 + ```jsx 122 + function Button({ color, children }) { 123 + return ( 124 + // ✅ `color` is always fresh! 125 + <button className={'Button-' + color}> 126 + {children} 127 + </button> 128 + ); 129 + } 130 + ``` 131 + 132 + ---- 133 + 134 + Computed values are another reason people sometimes attempt to copy props into state. For example, imagine that we determined the *button text* color based on an expensive computation with background `color` as an argument: 135 + 136 + ```jsx{3,9} 137 + class Button extends React.Component { 138 + state = { 139 + textColor: slowlyCalculateTextColor(this.props.color) 140 + }; 141 + render() { 142 + return ( 143 + <button className={ 144 + 'Button-' + this.props.color + 145 + ' Button-text-' + this.state.textColor // 🔴 Stale on `color` prop updates 146 + }> 147 + {this.props.children} 148 + </button> 149 + ); 150 + } 151 + } 152 + ``` 153 + 154 + This component is buggy because it doesn’t recalculate `this.state.textColor` on the `color` prop change. The easiest fix would be to move the `textColor` calculation into the `render` method, and make this a `PureComponent`: 155 + 156 + ```jsx{1,3} 157 + class Button extends React.PureComponent { 158 + render() { 159 + const textColor = slowlyCalculateTextColor(this.props.color); 160 + return ( 161 + <button className={ 162 + 'Button-' + this.props.color + 163 + ' Button-text-' + textColor // ✅ Always fresh 164 + }> 165 + {this.props.children} 166 + </button> 167 + ); 168 + } 169 + } 170 + ``` 171 + 172 + Problem solved! Now if props change, we'll recalculate `textColor`, but we avoid the expensive computation on the same props. 173 + 174 + However, we might want to optimize it further. What if it’s the `children` prop that changed? It seems unfortunate to recalculate the `textColor` in that case. Our second attempt might be to invoke the calculation in `componentDidUpdate`: 175 + 176 + ```jsx{5-12} 177 + class Button extends React.Component { 178 + state = { 179 + textColor: slowlyCalculateTextColor(this.props.color) 180 + }; 181 + componentDidUpdate(prevProps) { 182 + if (prevProps.color !== this.props.color) { 183 + // 😔 Extra re-render for every update 184 + this.setState({ 185 + textColor: slowlyCalculateTextColor(this.props.color), 186 + }); 187 + } 188 + } 189 + render() { 190 + return ( 191 + <button className={ 192 + 'Button-' + this.props.color + 193 + ' Button-text-' + this.state.textColor // ✅ Fresh on final render 194 + }> 195 + {this.props.children} 196 + </button> 197 + ); 198 + } 199 + } 200 + ``` 201 + 202 + However, this would mean our component does a second re-render after every change. That’s not ideal either if we’re trying to optimize it. 203 + 204 + You could use the legacy `componentWillReceiveProps` lifecycle for this. However, people often put side effects there too. That, in turn, often causes problems for the upcoming Concurrent Rendering [features like Time Slicing and Suspense](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html). And the “safer” `getDerivedStateFromProps` method is clunky. 205 + 206 + Let’s step back for a second. Effectively, we want [*memoization*](https://en.wikipedia.org/wiki/Memoization). We have some inputs, and we don’t want to recalculate the output unless the inputs change. 207 + 208 + With a class, you could use a [helper](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization) for memoization. However, Hooks take this a step further, giving you a built-in way to memoize expensive computations: 209 + 210 + ```jsx{2-5} 211 + function Button({ color, children }) { 212 + const textColor = useMemo( 213 + () => slowlyCalculateTextColor(color), 214 + [color] // ✅ Don’t recalculate until `color` changes 215 + ); 216 + return ( 217 + <button className={'Button-' + color + ' Button-text-' + textColor}> 218 + {children} 219 + </button> 220 + ); 221 + } 222 + ``` 223 + 224 + That’s all the code you need! 225 + 226 + In a class component, you can use a helper like [`memoize-one`](https://github.com/alexreardon/memoize-one) for that. In a function component, `useMemo` Hook gives you similar functionality. 227 + 228 + Now we see that **even optimizing expensive computations isn’t a good reason to copy props into state.** Our rendering result should respect changes to props. 229 + 230 + --- 231 + 232 + ### Don’t Stop the Data Flow in Side Effects 233 + 234 + So far, we’ve talked about how to keep the rendering result consistent with prop changes. Avoiding copying props into state is a part of that. However, it is important that **side effects (e.g. data fetching) are also a part of the data flow**. 235 + 236 + Consider this React component: 237 + 238 + ```jsx{5-7} 239 + class SearchResults extends React.Component { 240 + state = { 241 + data: null 242 + }; 243 + componentDidMount() { 244 + this.fetchResults(); 245 + } 246 + fetchResults() { 247 + const url = this.getFetchUrl(); 248 + // Do the fetching... 249 + } 250 + getFetchUrl() { 251 + return 'http://myapi/results?query' + this.props.query; 252 + } 253 + render() { 254 + // ... 255 + } 256 + } 257 + ``` 258 + 259 + A lot of React components are like this — but if we look a bit closer, we'll notice a bug. The `fetchResults` method uses the `query` prop for data fetching: 260 + 261 + ```jsx{2} 262 + getFetchUrl() { 263 + return 'http://myapi/results?query' + this.props.query; 264 + } 265 + ``` 266 + 267 + But what if the `query` prop changes? In our component, nothing will happen. **This means our component’s side effects don’t respect changes to its props.** This is a very common source of bugs in React applications. 268 + 269 + In order to fix our component, we need to: 270 + 271 + * Look at `componentDidMount` and every method called from it. 272 + - In our example, that’s `fetchResults` and `getFetchUrl`. 273 + * Write down all props and state used by those methods. 274 + - In our example, that’s `this.props.query`. 275 + * Make sure that whenever those props change, we re-run the side effect. 276 + - We can do this by adding the `componentDidUpdate` method. 277 + 278 + ```jsx{8-12,18} 279 + class SearchResults extends React.Component { 280 + state = { 281 + data: null 282 + }; 283 + componentDidMount() { 284 + this.fetchResults(); 285 + } 286 + componentDidUpdate(prevProps) { 287 + if (prevProps.query !== this.props.query) { // ✅ Refetch on change 288 + this.fetchResults(); 289 + } 290 + } 291 + fetchResults() { 292 + const url = this.getFetchUrl(); 293 + // Do the fetching... 294 + } 295 + getFetchUrl() { 296 + return 'http://myapi/results?query' + this.props.query; // ✅ Updates are handled 297 + } 298 + render() { 299 + // ... 300 + } 301 + } 302 + ``` 303 + 304 + Now our code respects all changes to props, even for side effects. 305 + 306 + However, it’s challenging to remember not to break it again. For example, we might add `currentPage` to the local state, and use it in `getFetchUrl`: 307 + 308 + ```jsx{4,21} 309 + class SearchResults extends React.Component { 310 + state = { 311 + data: null, 312 + currentPage: 0, 313 + }; 314 + componentDidMount() { 315 + this.fetchResults(); 316 + } 317 + componentDidUpdate(prevProps) { 318 + if (prevProps.query !== this.props.query) { 319 + this.fetchResults(); 320 + } 321 + } 322 + fetchResults() { 323 + const url = this.getFetchUrl(); 324 + // Do the fetching... 325 + } 326 + getFetchUrl() { 327 + return ( 328 + 'http://myapi/results?query' + this.props.query + 329 + '&page=' + this.state.currentPage // 🔴 Updates are ignored 330 + ); 331 + } 332 + render() { 333 + // ... 334 + } 335 + } 336 + ``` 337 + 338 + Alas, our code is again buggy because our side effect doesn’t respect changes to `currentPage`. 339 + 340 + **Props and state are a part of the React data flow. Both rendering and side effects should reflect changes in that data flow, not ignore them!** 341 + 342 + To fix our code, we can repeat the steps above: 343 + 344 + * Look at `componentDidMount` and every method called from it. 345 + - In our example, that’s `fetchResults` and `getFetchUrl`. 346 + * Write down all props and state used by those methods. 347 + - In our example, that’s `this.props.query` **and `this.state.currentPage`**. 348 + * Make sure that whenever those props change, we re-run the side effect. 349 + - We can do this by changing the `componentDidUpdate` method. 350 + 351 + Let’s fix our component to handle updates to the `currentPage` state: 352 + 353 + ```jsx{11,24} 354 + class SearchResults extends React.Component { 355 + state = { 356 + data: null, 357 + currentPage: 0, 358 + }; 359 + componentDidMount() { 360 + this.fetchResults(); 361 + } 362 + componentDidUpdate(prevProps, prevState) { 363 + if ( 364 + prevState.currentPage !== this.state.currentPage || // ✅ Refetch on change 365 + prevProps.query !== this.props.query 366 + ) { 367 + this.fetchResults(); 368 + } 369 + } 370 + fetchResults() { 371 + const url = this.getFetchUrl(); 372 + // Do the fetching... 373 + } 374 + getFetchUrl() { 375 + return ( 376 + 'http://myapi/results?query' + this.props.query + 377 + '&page=' + this.state.currentPage // ✅ Updates are handled 378 + ); 379 + } 380 + render() { 381 + // ... 382 + } 383 + } 384 + ``` 385 + 386 + **Wouldn’t it be nice if we could somehow automatically catch these mistakes?** Isn’t that something a linter could help us with? 387 + 388 + --- 389 + 390 + Unfortunately, automatically checking a class component for consistency is too difficult. Any method can call any other method. Statically analyzing calls from `componentDidMount` and `componentDidUpdate` is fraught with false positives. 391 + 392 + However, one *could* design an API that *can* be statically analyzed for consistency. The [React `useEffect` Hook](/a-complete-guide-to-useeffect/) is an example of such API: 393 + 394 + ```jsx{13-14,19} 395 + function SearchResults({ query }) { 396 + const [data, setData] = useState(null); 397 + const [currentPage, setCurrentPage] = useState(0); 398 + 399 + useEffect(() => { 400 + function fetchResults() { 401 + const url = getFetchUrl(); 402 + // Do the fetching... 403 + } 404 + 405 + function getFetchUrl() { 406 + return ( 407 + 'http://myapi/results?query' + query + 408 + '&page=' + currentPage 409 + ); 410 + } 411 + 412 + fetchResults(); 413 + }, [currentPage, query]); // ✅ Refetch on change 414 + 415 + // ... 416 + } 417 + ``` 418 + 419 + We put the logic *inside* of the effect, and that makes it easier to see *which values from the React data flow* it depends on. These values are called “dependencies”, and in our example they are `[currentPage, query]`. 420 + 421 + Note how this array of “effect dependencies” isn’t really a new concept. In a class, we had to search for these “dependencies” through all the method calls. The `useEffect` API just makes the same concept explicit. 422 + 423 + This, in turn, lets us validate them automatically: 424 + 425 + ![Demo of exhaustive-deps lint rule](./useeffect.gif) 426 + 427 + *(This is a demo of the new recommended `exhaustive-deps` lint rule which is a part of `eslint-plugin-react-hooks`. It will soon be included in Create React App.)* 428 + 429 + **Note that it is important to respect all prop and state updates for effects regardless of whether you’re writing component as a class or a function.** 430 + 431 + With the class API, you have to think about consistency yourself, and verify that changes to every relevant prop or state are handled by `componentDidUpdate`. Otherwise, your component is not resilient to prop and state changes. This is not even a React-specific problem. It applies to any UI library that lets you handle “creation” and “updates” separately. 432 + 433 + **The `useEffect` API flips the default by encouraging consistency.** This [might feel unfamiliar at first](/a-complete-guide-to-useeffect/), but as a result your component becomes more resilient to changes in the logic. And since the “dependencies” are now explicit, we can *verify* the effect is consistent using a lint rule. We’re using a linter to catch bugs! 434 + 435 + --- 436 + 437 + ### Don’t Stop the Data Flow in Optimizations 438 + 439 + There's one more case where you might accidentally ignore changes to props. This mistake can occur when you’re manually optimizing your components. 440 + 441 + Note that optimization approaches that use shallow equality like `PureComponent` and `React.memo` with the default comparison are safe. 442 + 443 + **However, if you try to “optimize” a component by writing your own comparison, you may mistakenly forget to compare function props:** 444 + 445 + ```jsx{2-5,7} 446 + class Button extends React.Component { 447 + shouldComponentUpdate(prevProps) { 448 + // 🔴 Doesn't compare this.props.onClick 449 + return this.props.color !== prevProps.color; 450 + } 451 + render() { 452 + const onClick = this.props.onClick; // 🔴 Doesn't reflect updates 453 + const textColor = slowlyCalculateTextColor(this.props.color); 454 + return ( 455 + <button 456 + onClick={onClick} 457 + className={'Button-' + this.props.color + ' Button-text-' + textColor}> 458 + {this.props.children} 459 + </button> 460 + ); 461 + } 462 + } 463 + ``` 464 + 465 + It is easy to miss this mistake at first because with classes, you’d usually pass a *method* down, and so it would have the same identity anyway: 466 + 467 + ```jsx{2-4,9-11} 468 + class MyForm extends React.Component { 469 + handleClick = () => { // ✅ Always the same function 470 + // Do something 471 + } 472 + render() { 473 + return ( 474 + <> 475 + <h1>Hello!</h1> 476 + <Button color='green' onClick={this.handleClick}> 477 + Press me 478 + </Button> 479 + </> 480 + ) 481 + } 482 + } 483 + ``` 484 + 485 + So our optimization doesn’t break *immediately*. However, it will keep “seeing” the old `onClick` value if it changes over time but other props don’t: 486 + 487 + ```jsx{6,13-15} 488 + class MyForm extends React.Component { 489 + state = { 490 + isEnabled: true 491 + }; 492 + handleClick = () => { 493 + this.setState({ isEnabled: false }); 494 + // Do something 495 + } 496 + render() { 497 + return ( 498 + <> 499 + <h1>Hello!</h1> 500 + <Button color='green' onClick={ 501 + // 🔴 Button ignores updates to the onClick prop 502 + this.state.isEnabled ? this.handleClick : null 503 + }> 504 + Press me 505 + </Button> 506 + </> 507 + ) 508 + } 509 + } 510 + ``` 511 + 512 + In this example, clicking the button should disable it — but this doesn’t happen because the `Button` component ignores any updates to the `onClick` prop. 513 + 514 + This could get even more confusing if the function identity itself depends on something that could change over time, like `draft.content` in this example: 515 + 516 + ```jsx{6-7} 517 + drafts.map(draft => 518 + <Button 519 + color='blue' 520 + key={draft.id} 521 + onClick={ 522 + // 🔴 Button ignores updates to the onClick prop 523 + this.handlePublish.bind(this, draft.content) 524 + }> 525 + Publish 526 + </Button> 527 + ) 528 + ``` 529 + 530 + While `draft.content` could change over time, our `Button` component ignored change to the `onClick` prop so it continues to see the “first version” of the `onClick` bound method with the original `draft.content`. 531 + 532 + **So how do we avoid this problem?** 533 + 534 + I recommend to avoid manually implementing `shouldComponentUpdate` and to avoid specifying a custom comparison to `React.memo()`. The default shallow comparison in `React.memo` will respect changing function identity: 535 + 536 + ```jsx{11} 537 + function Button({ onClick, color, children }) { 538 + const textColor = slowlyCalculateTextColor(color); 539 + return ( 540 + <button 541 + onClick={onClick} 542 + className={'Button-' + color + ' Button-text-' + textColor}> 543 + {children} 544 + </button> 545 + ); 546 + } 547 + export default React.memo(Button); // ✅ Uses shallow comparison 548 + ``` 549 + 550 + In a class, `PureComponent` has the same behavior. 551 + 552 + This ensures that passing a different function as a prop will always work. 553 + 554 + If you insist on a custom comparison, **make sure that you don’t skip functions:** 555 + 556 + ```jsx{5} 557 + shouldComponentUpdate(prevProps) { 558 + // ✅ Compares this.props.onClick 559 + return ( 560 + this.props.color !== prevProps.color || 561 + this.props.onClick !== prevProps.onClick 562 + ); 563 + } 564 + ``` 565 + 566 + As I mentioned earlier, it’s easy to miss this problem in a class component because method identities are often stable (but not always — and that’s where the bugs become difficult to debug). With Hooks, the situation is a bit different: 567 + 568 + 1. Functions are different *on every render* so you discover this problem [right away](https://github.com/facebook/react/issues/14972#issuecomment-468280039). 569 + 2. With `useCallback` and `useContext`, you can [avoid passing functions deep down altogether](https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down). This lets you optimize rendering without worrying about functions. 570 + 571 + --- 572 + 573 + To sum up this section, **don’t stop the data flow!** 574 + 575 + Whenever you use props and state, consider what should happen if they change. In most cases, a component shouldn’t treat the initial render and updates differently. That makes it resilient to changes in the logic. 576 + 577 + With classes, it’s easy to forget about updates when using props and state inside the lifecycle methods. Hooks nudge you to do the right thing — but it takes some mental adjustment if you’re not used to already doing it. 578 + 579 + --- 580 + 581 + ## Principle 2: Always Be Ready to Render 582 + 583 + React components let you write rendering code without worrying too much about time. You describe how the UI *should* look at any given moment, and React makes it happen. Take advantage of that model! 584 + 585 + Don’t try to introduce unnecessary timing assumptions into your component behavior. **Your component should be ready to re-render at any time.** 586 + 587 + How can one violate this principle? React doesn’t make it very easy — but you can do it by using the legacy `componentWillReceiveProps` lifecycle method: 588 + 589 + ```jsx{5-8} 590 + class TextInput extends React.Component { 591 + state = { 592 + value: '' 593 + }; 594 + // 🔴 Resets local state on every parent render 595 + componentWillReceiveProps(nextProps) { 596 + this.setState({ value: nextProps.value }); 597 + } 598 + handleChange = (e) => { 599 + this.setState({ value: e.target.value }); 600 + }; 601 + render() { 602 + return ( 603 + <input 604 + value={this.state.value} 605 + onChange={this.handleChange} 606 + /> 607 + ); 608 + } 609 + } 610 + ``` 611 + 612 + In this example, we keep `value` in the local state, but we *also* receive `value` from props. Whenever we “receive new props”, we reset the `value` in state. 613 + 614 + **The problem with this pattern is that it entirely relies on accidental timing.** 615 + 616 + Maybe today this component’s parent updates rarely, and so our `TextInput` only “receives props” when something important happens, like saving a form. 617 + 618 + But tomorrow you might add some animation to the parent of `TextInput`. If its parent re-renders more often, it will keep [“blowing away”](https://codesandbox.io/s/m3w9zn1z8x) the child state! You can read more about this problem in [“You Probably Don’t Need Derived State”](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html). 619 + 620 + **So how can we fix this?** 621 + 622 + First of all, we need to fix our mental model. We need to stop thinking of “receiving props” as something different from just “rendering”. A re-render caused by a parent shouldn’t behave differently from a re-render caused by our own local state change. **Components should be resilient to rendering less or more often because otherwise they’re too coupled to their particular parents.** 623 + 624 + *([This demo](https://codesandbox.io/s/m3w9zn1z8x) shows how re-rendering can break fragile components.)* 625 + 626 + While there are a few [different](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions) [solutions](https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops) for when you *truly* want to derive state from props, usually you should use either a fully controlled component: 627 + 628 + ```jsx 629 + // Option 1: Fully controlled component. 630 + function TextInput({ value, onChange }) { 631 + return ( 632 + <input 633 + value={value} 634 + onChange={onChange} 635 + /> 636 + ); 637 + } 638 + ``` 639 + 640 + Or you can use an uncontrolled component with a key to reset it: 641 + 642 + ```jsx 643 + // Option 2: Fully uncontrolled component. 644 + function TextInput() { 645 + const [value, setValue] = useState(''); 646 + return ( 647 + <input 648 + value={value} 649 + onChange={e => setValue(e.target.value)} 650 + /> 651 + ); 652 + } 653 + 654 + // We can reset its internal state later by changing the key: 655 + <TextInput key={formId} /> 656 + ``` 657 + 658 + The takeaway from this section is that your component shouldn’t break just because it or its parent re-renders more often. The React API design makes it easy if you avoid the legacy `componentWillReceiveProps` lifecycle method. 659 + 660 + To stress-test your component, you can temporarily add this code to its parent: 661 + 662 + ```jsx{2} 663 + componentDidMount() { 664 + // Don't forget to remove this immediately! 665 + setInterval(() => this.forceUpdate(), 100); 666 + } 667 + ``` 668 + 669 + **Don’t leave this code in** — it’s just a quick way to check what happens when a parent re-renders more often than you expected. It shouldn’t break the child! 670 + 671 + --- 672 + 673 + You might be thinking: “I’ll keep resetting state when the props change, but will prevent unnecessary re-renders with `PureComponent`”. 674 + 675 + This code should work, right? 676 + 677 + ```jsx{1-2} 678 + // 🤔 Should prevent unnecessary re-renders... right? 679 + class TextInput extends React.PureComponent { 680 + state = { 681 + value: '' 682 + }; 683 + // 🔴 Resets local state on every parent render 684 + componentWillReceiveProps(nextProps) { 685 + this.setState({ value: nextProps.value }); 686 + } 687 + handleChange = (e) => { 688 + this.setState({ value: e.target.value }); 689 + }; 690 + render() { 691 + return ( 692 + <input 693 + value={this.state.value} 694 + onChange={this.handleChange} 695 + /> 696 + ); 697 + } 698 + } 699 + ``` 700 + 701 + At first, it might seem like this component solves the problem of “blowing away” the state on parent re-render. After all, if the props are the same, we just skip the update — and so `componentWillReceiveProps` doesn’t get called. 702 + 703 + However, this gives us a false sense of security. **This component is still not resilient to _actual_ prop changes.** For example, if we added *another* often-changing prop, like an animated `style`, we would still “lose” the internal state: 704 + 705 + ```jsx{2} 706 + <TextInput 707 + style={{opacity: someValueFromState}} 708 + value={ 709 + // 🔴 componentWillReceiveProps in TextInput 710 + // resets to this value on every animation tick. 711 + value 712 + } 713 + /> 714 + ``` 715 + 716 + So this approach is still flawed. We can see that various optimizations like `PureComponent`, `shouldComponentUpdate`, and `React.memo` shouldn’t be used for controlling *behavior*. Only use them to improve *performance* where it helps. If removing an optimization _breaks_ a component, it was too fragile to begin with. 717 + 718 + The solution here is the same as we described earlier. Don’t treat “receiving props” as a special event. Avoid “syncing” props and state. In most cases, every value should either be fully controlled (through props), or fully uncontrolled (in local state). Avoid derived state [when you can](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions). **And always be ready to render!** 719 + 720 + --- 721 + 722 + ## Principle 3: No Component Is a Singleton 723 + 724 + Sometimes we assume a certain component is only ever displayed once. Such as a navigation bar. This might be true for some time. However, this assumption often causes design problems that only surface much later. 725 + 726 + For example, maybe you need to implement an animation *between* two `Page` components on a route change — the previous `Page` and the next `Page`. Both of them need to be mounted during the animation. However, you might discover that each of those components assumes it’s the only `Page` on the screen. 727 + 728 + It’s easy to check for these problems. Just for fun, try to render your app twice: 729 + 730 + ```jsx{3,4} 731 + ReactDOM.render( 732 + <> 733 + <MyApp /> 734 + <MyApp /> 735 + </>, 736 + document.getElementById('root') 737 + ); 738 + ``` 739 + 740 + Click around. (You might need to tweak some CSS for this experiment.) 741 + 742 + **Does your app still behave as expected?** Or do you see strange crashes and errors? It’s a good idea to do this stress test on complex components once in a while, and ensure that multiple copies of them don’t conflict with one another. 743 + 744 + An example of a problematic pattern I’ve written myself a few times is performing global state “cleanup” in `componentWillUnmount`: 745 + 746 + ```jsx{2-3} 747 + componentWillUnmount() { 748 + // Resets something in Redux store 749 + this.props.resetForm(); 750 + } 751 + ``` 752 + 753 + Of course, if there are two such components on the page, unmounting one of them can break the other one. Resetting “global” state on *mount* is no better: 754 + 755 + ```jsx{2-3} 756 + componentDidMount() { 757 + // Resets something in Redux store 758 + this.props.resetForm(); 759 + } 760 + ``` 761 + 762 + In that case *mounting* a second form will break the first one. 763 + 764 + These patterns are good indicators of where our components are fragile. ***Showing* or *hiding* a tree shouldn’t break components outside of that tree.** 765 + 766 + Whether you plan to render this component twice or not, solving these issues pays off in the longer term. It leads you to a more resilient design. 767 + 768 + --- 769 + 770 + ## Principle 4: Keep the Local State Isolated 771 + 772 + Consider a social media `Post` component. It has a list of `Comment` threads (that can be expanded) and a `NewComment` input. 773 + 774 + React components may have local state. But what state is truly local? Is the post content itself local state or not? What about the list of comments? Or the record of which comment threads are expanded? Or the value of the comment input? 775 + 776 + If you’re used to putting everything into a “state manager”, answering this question can be challenging. So here’s a simple way to decide. 777 + 778 + **If you’re not sure whether some state is local, ask yourself: “If this component was rendered twice, should this interaction reflect in the other copy?” Whenever the answer is “no”, you found some local state.** 779 + 780 + For example, imagine we rendered the same `Post` twice. Let’s look at different things inside of it that can change. 781 + 782 + * *Post content.* We’d want editing the post in one tree to update it in another tree. Therefore, it probably **should not** be the local state of a `Post` component. (Instead, the post content could live in some cache like Apollo, Relay, or Redux.) 783 + 784 + * *List of comments.* This is similar to post content. We’d want adding a new comment in one tree to be reflected in the other tree too. So ideally we would use some kind of a cache for it, and it **should not** be a local state of our `Post`. 785 + 786 + * *Which comments are expanded.* It would be weird if expanding a comment in one tree would also expand it in another tree. In this case we’re interacting with a particular `Comment` *UI representation* rather than an abstract “comment entity”. Therefore, an “expanded” flag **should** be a local state of the `Comment`. 787 + 788 + * *The value of new comment input.* It would be odd if typing a comment in one input would also update an input in another tree. Unless inputs are clearly grouped together, usually people expect them to be independent. So the input value **should** be a local state of the `NewComment` component. 789 + 790 + I don’t suggest a dogmatic interpretation of these rules. Of course, in a simpler app you might want to use local state for everything, including those “caches”. I’m only talking about the ideal user experience [from the first principles](/the-elements-of-ui-engineering/). 791 + 792 + **Avoid making truly local state global.** This plays into our topic of “resilience”: there’s fewer surprising synchronization happening between components. As a bonus, this *also* fixes a large class of performance issues. “Over-rendering” is much less of an issue when your state is in the right place. 793 + 794 + --- 795 + 796 + ## Recap 797 + 798 + Let’s recap these principles one more time: 799 + 800 + 1. **[Don’t stop the data flow.](#principle-1-dont-stop-the-data-flow)** Props and state can change, and components should handle those changes whenever they happen. 801 + 2. **[Always be ready to render.](#principle-2-always-be-ready-to-render)** A component shouldn’t break because it’s rendered more or less often. 802 + 3. **[No component is a singleton.](#principle-3-no-component-is-a-singleton)** Even if a component is rendered just once, your design will improve if rendering twice doesn’t break it. 803 + 4. **[Keep the local state isolated.](#principle-4-keep-the-local-state-isolated)** Think about which state is local to a particular UI representation — and don’t hoist that state higher than necessary. 804 + 805 + **These principles help you write components that are [optimized for change](/optimized-for-change/). It’s easy to add, change them, and delete them.** 806 + 807 + And most importantly, once our components are resilient, we can come back to the pressing dilemma of whether or not props should be sorted by alphabet.
public/writing-resilient-components/useeffect.gif

This is a binary file and will not be displayed.

+10
tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + module.exports = { 3 + content: ["./app/**/*.{js,ts,jsx,tsx,mdx}"], 4 + theme: { 5 + extend: { 6 + colors: {}, 7 + }, 8 + }, 9 + plugins: [], 10 + };