@@ -460,7 +232,22 @@ function Image(props: any) {
return
;
}
-export const MDXComponents = {
+function annotateMDXComponents(
+ components: Record
+): Record {
+ return Object.entries(components).reduce((acc, [key, Component]) => {
+ acc[key] = (props) => ;
+ acc[key].displayName = `Annotated(${key})`; // Optional, for debugging
+ return acc;
+ }, {} as Record);
+}
+
+export const MDXComponentsToc = annotateMDXComponents({
+ a: Link,
+ code: InlineCode,
+});
+
+export const MDXComponents = annotateMDXComponents({
p: P,
strong: Strong,
blockquote: Blockquote,
@@ -529,11 +316,4 @@ export const MDXComponents = {
CodeStep,
YouTubeIframe,
ErrorDecoder,
-};
-
-for (let key in MDXComponents) {
- if (MDXComponents.hasOwnProperty(key)) {
- const MDXComponent: any = (MDXComponents as any)[key];
- MDXComponent.mdxName = key;
- }
-}
+});
diff --git a/src/components/MDX/PackageImport.tsx b/src/components/MDX/PackageImport.tsx
index 5e2da820e55..a4d5fa1405d 100644
--- a/src/components/MDX/PackageImport.tsx
+++ b/src/components/MDX/PackageImport.tsx
@@ -12,10 +12,10 @@ interface PackageImportProps {
export function PackageImport({children}: PackageImportProps) {
const terminal = Children.toArray(children).filter((child: any) => {
- return child.type?.mdxName !== 'pre';
+ return child.props?.['data-mdx-name'] !== 'pre';
});
const code = Children.toArray(children).map((child: any, i: number) => {
- if (child.type?.mdxName === 'pre') {
+ if (child.props?.['data-mdx-name'] === 'pre') {
return (
) => (
+
+);
+
+export const Strong = (strong: HTMLAttributes) => (
+
+);
+
+export const OL = (p: HTMLAttributes) => (
+
+);
+export const LI = (p: HTMLAttributes) => (
+
+);
+export const UL = (p: HTMLAttributes) => (
+
+);
+
+export const Divider = () => (
+
+);
diff --git a/src/components/MDX/Sandpack/CustomPreset.tsx b/src/components/MDX/Sandpack/CustomPreset.tsx
index 7d6e566d270..f95d3270ac7 100644
--- a/src/components/MDX/Sandpack/CustomPreset.tsx
+++ b/src/components/MDX/Sandpack/CustomPreset.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/Sandpack/SandpackRoot.tsx b/src/components/MDX/Sandpack/SandpackRoot.tsx
index 67f40d0b3b3..1084ea64747 100644
--- a/src/components/MDX/Sandpack/SandpackRoot.tsx
+++ b/src/components/MDX/Sandpack/SandpackRoot.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/Sandpack/createFileMap.ts b/src/components/MDX/Sandpack/createFileMap.ts
index 193b07be825..07bdcd377f4 100644
--- a/src/components/MDX/Sandpack/createFileMap.ts
+++ b/src/components/MDX/Sandpack/createFileMap.ts
@@ -12,19 +12,22 @@ export const SUPPORTED_FILES = [AppJSPath, StylesCSSPath];
export const createFileMap = (codeSnippets: any) => {
return codeSnippets.reduce(
(result: Record, codeSnippet: React.ReactElement) => {
- if (
- (codeSnippet.type as any).mdxName !== 'pre' &&
- codeSnippet.type !== 'pre'
- ) {
- return result;
- }
+ // TODO: actually fix this
const {props} = (
codeSnippet.props as PropsWithChildren<{
children: ReactElement<
- HTMLAttributes & {meta?: string}
+ HTMLAttributes & {
+ meta?: string;
+ 'data-mdx-name'?: string;
+ }
>;
}>
).children;
+
+ if (props?.['data-mdx-name'] !== 'code') {
+ return result;
+ }
+
let filePath; // path in the folder structure
let fileHidden = false; // if the file is available as a tab
let fileActive = false; // if the file tab is shown by default
diff --git a/src/components/MDX/Sandpack/index.tsx b/src/components/MDX/Sandpack/index.tsx
index 6755ba8de69..d90facfe80e 100644
--- a/src/components/MDX/Sandpack/index.tsx
+++ b/src/components/MDX/Sandpack/index.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/SandpackWithHTMLOutput.tsx b/src/components/MDX/SandpackWithHTMLOutput.tsx
index 51ce28dc149..041d7bf9bff 100644
--- a/src/components/MDX/SandpackWithHTMLOutput.tsx
+++ b/src/components/MDX/SandpackWithHTMLOutput.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import {Children, memo} from 'react';
import InlineCode from './InlineCode';
import Sandpack from './Sandpack';
diff --git a/src/components/MDX/TerminalBlock.tsx b/src/components/MDX/TerminalBlock.tsx
index 47529271619..73a10216712 100644
--- a/src/components/MDX/TerminalBlock.tsx
+++ b/src/components/MDX/TerminalBlock.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/TocContext.tsx b/src/components/MDX/TocContext.tsx
index 8aeead370e9..cc7080a8bcc 100644
--- a/src/components/MDX/TocContext.tsx
+++ b/src/components/MDX/TocContext.tsx
@@ -7,7 +7,7 @@ import type {ReactNode} from 'react';
export type TocItem = {
url: string;
- text: ReactNode;
+ node: ReactNode;
depth: number;
};
export type Toc = Array;
diff --git a/src/components/SafariScrollHandler.tsx b/src/components/SafariScrollHandler.tsx
new file mode 100644
index 00000000000..2cb3e4037a3
--- /dev/null
+++ b/src/components/SafariScrollHandler.tsx
@@ -0,0 +1,22 @@
+'use client';
+
+import {useEffect} from 'react';
+
+export function ScrollHandler() {
+ useEffect(() => {
+ // Taken from StackOverflow. Trying to detect both Safari desktop and mobile.
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+ if (isSafari) {
+ // This is kind of a lie.
+ // We still rely on the manual Next.js scrollRestoration logic.
+ // However, we *also* don't want Safari grey screen during the back swipe gesture.
+ // Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time.
+ history.scrollRestoration = 'auto';
+ } else {
+ // For other browsers, let Next.js set scrollRestoration to 'manual'.
+ // It seems to work better for Chrome and Firefox which don't animate the back swipe.
+ }
+ }, []);
+
+ return null;
+}
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index c7401487b75..3ff5c188131 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -4,7 +4,7 @@
import Head from 'next/head';
import Link from 'next/link';
-import Router from 'next/router';
+import {useRouter} from 'next/navigation';
import {lazy, useEffect} from 'react';
import * as React from 'react';
import {createPortal} from 'react-dom';
@@ -111,6 +111,7 @@ export function Search({
},
}: SearchProps) {
useDocSearchKeyboardEvents({isOpen, onOpen, onClose});
+ const router = useRouter();
return (
<>
@@ -127,7 +128,7 @@ export function Search({
onClose={onClose}
navigator={{
navigate({itemUrl}: any) {
- Router.push(itemUrl);
+ router.push(itemUrl);
},
}}
transformItems={(items: any[]) => {
diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx
deleted file mode 100644
index 628085744d7..00000000000
--- a/src/components/Seo.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (c) Facebook, Inc. and its affiliates.
- */
-
-import * as React from 'react';
-import Head from 'next/head';
-import {withRouter, Router} from 'next/router';
-import {siteConfig} from '../siteConfig';
-import {finishedTranslations} from 'utils/finishedTranslations';
-
-export interface SeoProps {
- title: string;
- titleForTitleTag: undefined | string;
- description?: string;
- image?: string;
- // jsonld?: JsonLDType | Array;
- children?: React.ReactNode;
- isHomePage: boolean;
- searchOrder?: number;
-}
-
-// If you are a maintainer of a language fork,
-// deployedTranslations has been moved to src/utils/finishedTranslations.ts.
-
-function getDomain(languageCode: string): string {
- const subdomain = languageCode === 'en' ? '' : languageCode + '.';
- return subdomain + 'react.dev';
-}
-
-export const Seo = withRouter(
- ({
- title,
- titleForTitleTag,
- image = '/images/og-default.png',
- router,
- children,
- isHomePage,
- searchOrder,
- }: SeoProps & {router: Router}) => {
- const siteDomain = getDomain(siteConfig.languageCode);
- const canonicalUrl = `https://${siteDomain}${
- router.asPath.split(/[\?\#]/)[0]
- }`;
- // Allow setting a different title for Google results
- const pageTitle =
- (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React');
- // Twitter's meta parser is not very good.
- const twitterTitle = pageTitle.replace(/[<>]/g, '');
- let description = isHomePage
- ? 'React is the library for web and native user interfaces. Build user interfaces out of individual pieces called components written in JavaScript. React is designed to let you seamlessly combine components written by independent people, teams, and organizations.'
- : 'The library for web and native user interfaces';
- return (
-
-
- {title != null && {pageTitle} }
- {isHomePage && (
- // Let Google figure out a good description for each page.
-
- )}
-
-
- {finishedTranslations.map((languageCode) => (
-
- ))}
-
-
-
- {title != null && (
-
- )}
- {description != null && (
-
- )}
-
-
-
-
- {title != null && (
-
- )}
- {description != null && (
-
- )}
-
-
- {searchOrder != null && (
-
- )}
-
-
-
-
-
-
-
-
- {children}
-
- );
- }
-);
diff --git a/src/components/ThemeScript.jsx b/src/components/ThemeScript.jsx
new file mode 100644
index 00000000000..66034557c27
--- /dev/null
+++ b/src/components/ThemeScript.jsx
@@ -0,0 +1,52 @@
+function ThemeInlineScript() {
+ function setTheme(newTheme) {
+ window.__theme = newTheme;
+ if (newTheme === 'dark') {
+ document.documentElement.classList.add('dark');
+ } else if (newTheme === 'light') {
+ document.documentElement.classList.remove('dark');
+ }
+ }
+
+ var preferredTheme;
+ try {
+ preferredTheme = localStorage.getItem('theme');
+ } catch (err) {}
+
+ window.__setPreferredTheme = function (newTheme) {
+ preferredTheme = newTheme;
+ setTheme(newTheme);
+ try {
+ localStorage.setItem('theme', newTheme);
+ } catch (err) {}
+ };
+
+ var initialTheme = preferredTheme;
+ var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ if (!initialTheme) {
+ initialTheme = darkQuery.matches ? 'dark' : 'light';
+ }
+ setTheme(initialTheme);
+
+ darkQuery.addEventListener('change', function (e) {
+ if (!preferredTheme) {
+ setTheme(e.matches ? 'dark' : 'light');
+ }
+ });
+
+ document.documentElement.classList.add(
+ window.navigator.platform.includes('Mac') ? 'platform-mac' : 'platform-win'
+ );
+}
+
+export function ThemeScript() {
+ return (
+
+ );
+}
diff --git a/src/components/UwuScript.jsx b/src/components/UwuScript.jsx
new file mode 100644
index 00000000000..d2c26585d5e
--- /dev/null
+++ b/src/components/UwuScript.jsx
@@ -0,0 +1,64 @@
+function UwuInlineScript() {
+ try {
+ let logShown = false;
+ function setUwu(isUwu) {
+ try {
+ if (isUwu) {
+ localStorage.setItem('uwu', true);
+ document.documentElement.classList.add('uwu');
+ if (!logShown) {
+ console.log('uwu mode! turn off with ?uwu=0');
+ console.log(
+ 'logo credit to @sawaratsuki1004 via https://github.com/SAWARATSUKI/ServiceLogos'
+ );
+ logShown = true;
+ }
+ } else {
+ localStorage.removeItem('uwu');
+ document.documentElement.classList.remove('uwu');
+ console.log('uwu mode off. turn on with ?uwu');
+ }
+ } catch (err) {}
+ }
+ window.__setUwu = setUwu;
+ function checkQueryParam() {
+ const params = new URLSearchParams(window.location.search);
+ const value = params.get('uwu');
+ switch (value) {
+ case '':
+ case 'true':
+ case '1':
+ return true;
+ case 'false':
+ case '0':
+ return false;
+ default:
+ return null;
+ }
+ }
+ function checkLocalStorage() {
+ try {
+ return localStorage.getItem('uwu') === 'true';
+ } catch (err) {
+ return false;
+ }
+ }
+ const uwuQueryParam = checkQueryParam();
+ if (uwuQueryParam != null) {
+ setUwu(uwuQueryParam);
+ } else if (checkLocalStorage()) {
+ document.documentElement.classList.add('uwu');
+ }
+ } catch (err) {}
+}
+
+export function UwuScript() {
+ return (
+
+ );
+}
diff --git a/src/content/community/docs-contributors.md b/src/content/community/docs-contributors.md
index 27b32a18f76..4f456784631 100644
--- a/src/content/community/docs-contributors.md
+++ b/src/content/community/docs-contributors.md
@@ -38,5 +38,6 @@ React documentation is written and maintained by the [React team](/community/tea
* [Rick Hanlon](https://twitter.com/rickhanlonii): site development
* [Harish Kumar](https://www.strek.in/): development and maintenance
* [Luna Ruan](https://twitter.com/lunaruan): sandbox improvements
+* [Jimmy Lai](https://twitter.com/feedthejim): site development
We'd also like to thank countless alpha testers and community members who gave us feedback along the way.
diff --git a/src/content/versions.md b/src/content/versions.md
index 8530f632476..8956e80e8d7 100644
--- a/src/content/versions.md
+++ b/src/content/versions.md
@@ -298,4 +298,4 @@ See the first blog post: [Why did we build React?](https://legacy.reactjs.org/bl
React was open sourced at Facebook Seattle in 2013:
-VIDEO
+VIDEO
diff --git a/src/hooks/usePendingRoute.ts b/src/hooks/usePendingRoute.ts
index 229a36e64c4..2e3f7c6f691 100644
--- a/src/hooks/usePendingRoute.ts
+++ b/src/hooks/usePendingRoute.ts
@@ -2,40 +2,9 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
-import {useRouter} from 'next/router';
-import {useState, useRef, useEffect} from 'react';
-
const usePendingRoute = () => {
- const {events} = useRouter();
- const [pendingRoute, setPendingRoute] = useState(null);
- const currentRoute = useRef(null);
- useEffect(() => {
- let routeTransitionTimer: any = null;
-
- const handleRouteChangeStart = (url: string) => {
- clearTimeout(routeTransitionTimer);
- routeTransitionTimer = setTimeout(() => {
- if (currentRoute.current !== url) {
- currentRoute.current = url;
- setPendingRoute(url);
- }
- }, 100);
- };
- const handleRouteChangeComplete = () => {
- setPendingRoute(null);
- clearTimeout(routeTransitionTimer);
- };
- events.on('routeChangeStart', handleRouteChangeStart);
- events.on('routeChangeComplete', handleRouteChangeComplete);
-
- return () => {
- events.off('routeChangeStart', handleRouteChangeStart);
- events.off('routeChangeComplete', handleRouteChangeComplete);
- clearTimeout(routeTransitionTimer);
- };
- }, [events]);
-
- return pendingRoute;
+ // TODO: @feedthejim - Implement usePendingRoute when App Router supports tapping into the transition state
+ return null;
};
export default usePendingRoute;
diff --git a/src/instrumentation.js b/src/instrumentation.js
new file mode 100644
index 00000000000..94c964cea2a
--- /dev/null
+++ b/src/instrumentation.js
@@ -0,0 +1,34 @@
+export function register() {
+ if (
+ process.env.NODE_ENV === 'development' &&
+ process.env.NEXT_RUNTIME === 'nodejs'
+ ) {
+ // watch for changes in the ./src/content directory
+ // and trigger an HMR update when a change is detected via a custom WebSocket setup
+ const chokidar = require('chokidar');
+ const path = require('path');
+ const ws = require('ws');
+
+ const wsServer = new ws.Server({
+ port: 3001,
+ });
+
+ function triggerRefresh() {
+ wsServer.clients.forEach((client) => {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(JSON.stringify({event: 'refresh'}));
+ }
+ });
+ }
+
+ // the process is in .next so we need to go up two level
+ const contentDir = path.resolve(__dirname, '../../src/content');
+ const watcher = chokidar.watch(contentDir, {
+ ignoreInitial: true,
+ });
+
+ watcher.on('all', () => {
+ triggerRefresh();
+ });
+ }
+}
diff --git a/src/pages/[[...markdownPath]].js b/src/pages/[[...markdownPath]].js
deleted file mode 100644
index bef4508df06..00000000000
--- a/src/pages/[[...markdownPath]].js
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (c) Facebook, Inc. and its affiliates.
- */
-
-import {Fragment, useMemo} from 'react';
-import {useRouter} from 'next/router';
-import {Page} from 'components/Layout/Page';
-import sidebarHome from '../sidebarHome.json';
-import sidebarLearn from '../sidebarLearn.json';
-import sidebarReference from '../sidebarReference.json';
-import sidebarCommunity from '../sidebarCommunity.json';
-import sidebarBlog from '../sidebarBlog.json';
-import {MDXComponents} from 'components/MDX/MDXComponents';
-import compileMDX from 'utils/compileMDX';
-import {generateRssFeed} from '../utils/rss';
-
-export default function Layout({content, toc, meta, languages}) {
- const parsedContent = useMemo(
- () => JSON.parse(content, reviveNodeOnClient),
- [content]
- );
- const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]);
- const section = useActiveSection();
- let routeTree;
- switch (section) {
- case 'home':
- case 'unknown':
- routeTree = sidebarHome;
- break;
- case 'learn':
- routeTree = sidebarLearn;
- break;
- case 'reference':
- routeTree = sidebarReference;
- break;
- case 'community':
- routeTree = sidebarCommunity;
- break;
- case 'blog':
- routeTree = sidebarBlog;
- break;
- }
- return (
-
- {parsedContent}
-
- );
-}
-
-function useActiveSection() {
- const {asPath} = useRouter();
- const cleanedPath = asPath.split(/[\?\#]/)[0];
- if (cleanedPath === '/') {
- return 'home';
- } else if (cleanedPath.startsWith('/reference')) {
- return 'reference';
- } else if (asPath.startsWith('/learn')) {
- return 'learn';
- } else if (asPath.startsWith('/community')) {
- return 'community';
- } else if (asPath.startsWith('/blog')) {
- return 'blog';
- } else {
- return 'unknown';
- }
-}
-
-// Deserialize a client React tree from JSON.
-function reviveNodeOnClient(parentPropertyName, val) {
- if (Array.isArray(val) && val[0] == '$r') {
- // Assume it's a React element.
- let Type = val[1];
- let key = val[2];
- if (key == null) {
- key = parentPropertyName; // Index within a parent.
- }
- let props = val[3];
- if (Type === 'wrapper') {
- Type = Fragment;
- props = {children: props.children};
- }
- if (Type in MDXComponents) {
- Type = MDXComponents[Type];
- }
- if (!Type) {
- console.error('Unknown type: ' + Type);
- Type = Fragment;
- }
- return ;
- } else {
- return val;
- }
-}
-
-// Put MDX output into JSON for client.
-export async function getStaticProps(context) {
- generateRssFeed();
- const fs = require('fs');
- const rootDir = process.cwd() + '/src/content/';
-
- // Read MDX from the file.
- let path = (context.params.markdownPath || []).join('/') || 'index';
- let mdx;
- try {
- mdx = fs.readFileSync(rootDir + path + '.md', 'utf8');
- } catch {
- mdx = fs.readFileSync(rootDir + path + '/index.md', 'utf8');
- }
-
- const {toc, content, meta, languages} = await compileMDX(mdx, path, {});
- return {
- props: {
- toc,
- content,
- meta,
- languages,
- },
- };
-}
-
-// Collect all MDX files for static generation.
-export async function getStaticPaths() {
- const {promisify} = require('util');
- const {resolve} = require('path');
- const fs = require('fs');
- const readdir = promisify(fs.readdir);
- const stat = promisify(fs.stat);
- const rootDir = process.cwd() + '/src/content';
-
- // Find all MD files recursively.
- async function getFiles(dir) {
- const subdirs = await readdir(dir);
- const files = await Promise.all(
- subdirs.map(async (subdir) => {
- const res = resolve(dir, subdir);
- return (await stat(res)).isDirectory()
- ? getFiles(res)
- : res.slice(rootDir.length + 1);
- })
- );
- return (
- files
- .flat()
- // ignores `errors/*.md`, they will be handled by `pages/errors/[errorCode].tsx`
- .filter((file) => file.endsWith('.md') && !file.startsWith('errors/'))
- );
- }
-
- // 'foo/bar/baz.md' -> ['foo', 'bar', 'baz']
- // 'foo/bar/qux/index.md' -> ['foo', 'bar', 'qux']
- function getSegments(file) {
- let segments = file.slice(0, -3).replace(/\\/g, '/').split('/');
- if (segments[segments.length - 1] === 'index') {
- segments.pop();
- }
- return segments;
- }
-
- const files = await getFiles(rootDir);
-
- const paths = files.map((file) => ({
- params: {
- markdownPath: getSegments(file),
- // ^^^ CAREFUL HERE.
- // If you rename markdownPath, update patches/next-remote-watch.patch too.
- // Otherwise you'll break Fast Refresh for all MD files.
- },
- }));
-
- return {
- paths: paths,
- fallback: false,
- };
-}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
deleted file mode 100644
index 5431f87cc9e..00000000000
--- a/src/pages/_app.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) Facebook, Inc. and its affiliates.
- */
-
-import {useEffect} from 'react';
-import {AppProps} from 'next/app';
-import {useRouter} from 'next/router';
-
-import '@docsearch/css';
-import '../styles/algolia.css';
-import '../styles/index.css';
-import '../styles/sandpack.css';
-
-if (typeof window !== 'undefined') {
- const terminationEvent = 'onpagehide' in window ? 'pagehide' : 'unload';
- window.addEventListener(terminationEvent, function () {
- // @ts-ignore
- gtag('event', 'timing', {
- event_label: 'JS Dependencies',
- event: 'unload',
- });
- });
-}
-
-export default function MyApp({Component, pageProps}: AppProps) {
- const router = useRouter();
-
- useEffect(() => {
- // Taken from StackOverflow. Trying to detect both Safari desktop and mobile.
- const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
- if (isSafari) {
- // This is kind of a lie.
- // We still rely on the manual Next.js scrollRestoration logic.
- // However, we *also* don't want Safari grey screen during the back swipe gesture.
- // Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time.
- history.scrollRestoration = 'auto';
- } else {
- // For other browsers, let Next.js set scrollRestoration to 'manual'.
- // It seems to work better for Chrome and Firefox which don't animate the back swipe.
- }
- }, []);
-
- useEffect(() => {
- const handleRouteChange = (url: string) => {
- const cleanedUrl = url.split(/[\?\#]/)[0];
- // @ts-ignore
- gtag('event', 'pageview', {
- event_label: cleanedUrl,
- });
- };
- router.events.on('routeChangeComplete', handleRouteChange);
- return () => {
- router.events.off('routeChangeComplete', handleRouteChange);
- };
- }, [router.events]);
-
- return ;
-}
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
deleted file mode 100644
index 6849df35d6a..00000000000
--- a/src/pages/_document.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (c) Facebook, Inc. and its affiliates.
- */
-
-import {Html, Head, Main, NextScript} from 'next/document';
-import {siteConfig} from '../siteConfig';
-
-const MyDocument = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default MyDocument;
diff --git a/src/pages/errors/[errorCode].tsx b/src/pages/errors/[errorCode].tsx
deleted file mode 100644
index de9eab5bb56..00000000000
--- a/src/pages/errors/[errorCode].tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import {Fragment, useMemo} from 'react';
-import {Page} from 'components/Layout/Page';
-import {MDXComponents} from 'components/MDX/MDXComponents';
-import sidebarLearn from 'sidebarLearn.json';
-import type {RouteItem} from 'components/Layout/getRouteMeta';
-import {GetStaticPaths, GetStaticProps, InferGetStaticPropsType} from 'next';
-import {ErrorDecoderContext} from 'components/ErrorDecoderContext';
-import compileMDX from 'utils/compileMDX';
-
-interface ErrorDecoderProps {
- errorCode: string | null;
- errorMessage: string | null;
- content: string;
- toc: string;
- meta: any;
-}
-
-export default function ErrorDecoderPage({
- errorMessage,
- errorCode,
- content,
-}: InferGetStaticPropsType) {
- const parsedContent = useMemo(
- () => JSON.parse(content, reviveNodeOnClient),
- [content]
- );
-
- return (
-
-
- {parsedContent}
- {/*
-
- We highly recommend using the development build locally when debugging
- your app since it tracks additional debug info and provides helpful
- warnings about potential problems in your apps, but if you encounter
- an exception while using the production build, this page will
- reassemble the original error message.
-
-
- */}
-
-
- );
-}
-
-// Deserialize a client React tree from JSON.
-function reviveNodeOnClient(parentPropertyName: unknown, val: any) {
- if (Array.isArray(val) && val[0] == '$r') {
- // Assume it's a React element.
- let Type = val[1];
- let key = val[2];
- if (key == null) {
- key = parentPropertyName; // Index within a parent.
- }
- let props = val[3];
- if (Type === 'wrapper') {
- Type = Fragment;
- props = {children: props.children};
- }
- if (Type in MDXComponents) {
- Type = MDXComponents[Type as keyof typeof MDXComponents];
- }
- if (!Type) {
- console.error('Unknown type: ' + Type);
- Type = Fragment;
- }
- return ;
- } else {
- return val;
- }
-}
-
-/**
- * Next.js Page Router doesn't have a way to cache specific data fetching request.
- * But since Next.js uses limited number of workers, keep "cachedErrorCodes" as a
- * module level memory cache can reduce the number of requests down to once per worker.
- *
- * TODO: use `next/unstable_cache` when migrating to Next.js App Router
- */
-let cachedErrorCodes: Record | null = null;
-
-export const getStaticProps: GetStaticProps = async ({
- params,
-}) => {
- const errorCodes: {[key: string]: string} = (cachedErrorCodes ||= await (
- await fetch(
- 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
- )
- ).json());
-
- const code = typeof params?.errorCode === 'string' ? params?.errorCode : null;
- if (code && !errorCodes[code]) {
- return {
- notFound: true,
- };
- }
-
- const fs = require('fs');
- const rootDir = process.cwd() + '/src/content/errors';
-
- // Read MDX from the file.
- let path = params?.errorCode || 'index';
- let mdx;
- try {
- mdx = fs.readFileSync(rootDir + '/' + path + '.md', 'utf8');
- } catch {
- // if [errorCode].md is not found, fallback to generic.md
- mdx = fs.readFileSync(rootDir + '/generic.md', 'utf8');
- }
-
- const {content, toc, meta} = await compileMDX(mdx, path, {code, errorCodes});
-
- return {
- props: {
- content,
- toc,
- meta,
- errorCode: code,
- errorMessage: code ? errorCodes[code] : null,
- },
- };
-};
-
-export const getStaticPaths: GetStaticPaths = async () => {
- /**
- * Fetch error codes from GitHub
- */
- const errorCodes = (cachedErrorCodes ||= await (
- await fetch(
- 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
- )
- ).json());
-
- const paths = Object.keys(errorCodes).map((code) => ({
- params: {
- errorCode: code,
- },
- }));
-
- return {
- paths,
- fallback: 'blocking',
- };
-};
diff --git a/src/pages/errors/index.tsx b/src/pages/errors/index.tsx
deleted file mode 100644
index d7742f03a30..00000000000
--- a/src/pages/errors/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import ErrorDecoderPage from './[errorCode]';
-export default ErrorDecoderPage;
-export {getStaticProps} from './[errorCode]';
diff --git a/src/styles/index.css b/src/styles/index.css
index 2811110920b..28d8473ac5b 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -19,8 +19,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -28,8 +27,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_W_MdIt.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_W_MdIt.woff2') format('woff2');
font-weight: 500;
font-style: italic;
font-display: swap;
@@ -37,8 +35,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_W_SBd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_W_SBd.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -46,8 +43,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_W_SBdIt.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_W_SBdIt.woff2') format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
@@ -55,8 +51,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -64,8 +59,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_W_BdIt.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_W_BdIt.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
@@ -73,8 +67,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_W_Rg.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_W_Rg.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -82,8 +75,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_W_It.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_W_It.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
@@ -91,8 +83,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -100,8 +91,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_W_MdIt.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_W_MdIt.woff2') format('woff2');
font-weight: 500;
font-style: italic;
font-display: swap;
@@ -109,8 +99,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -118,8 +107,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_W_BdIt.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_W_BdIt.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
@@ -129,8 +117,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Arbc_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Arbc_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -139,8 +126,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Arbc_W_SBd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Arbc_W_SBd.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -149,8 +135,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Arbc_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Arbc_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -159,8 +144,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Arbc_W_Rg.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Arbc_W_Rg.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -169,8 +153,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Arbc_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Arbc_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -179,8 +162,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Arbc_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Arbc_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -191,8 +173,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Cyrl_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Cyrl_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -201,8 +182,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Cyrl_W_SBd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Cyrl_W_SBd.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -211,8 +191,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Cyrl_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Cyrl_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -221,8 +200,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Cyrl_W_Rg.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Cyrl_W_Rg.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -231,8 +209,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Cyrl_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Cyrl_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -241,8 +218,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Cyrl_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Cyrl_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -253,8 +229,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Deva_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Deva_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -264,8 +239,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Deva_W_SBd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Deva_W_SBd.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -275,8 +249,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Deva_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Deva_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -286,8 +259,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Deva_W_Rg.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Deva_W_Rg.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -297,8 +269,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Deva_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Deva_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -308,8 +279,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Deva_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Deva_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -321,8 +291,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Viet_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Viet_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -331,8 +300,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Viet_W_SBd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Viet_W_SBd.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -341,8 +309,7 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('https://react.dev/fonts/Optimistic_Display_Viet_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Display_Viet_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -351,8 +318,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Viet_W_Rg.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Viet_W_Rg.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -361,8 +327,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Viet_W_Md.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Viet_W_Md.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -371,8 +336,7 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('https://react.dev/fonts/Optimistic_Text_Viet_W_Bd.woff2')
- format('woff2');
+ src: url('/fonts/Optimistic_Text_Viet_W_Bd.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
diff --git a/src/utils/compileMDX.ts b/src/utils/compileMDX.ts
deleted file mode 100644
index be770c29afb..00000000000
--- a/src/utils/compileMDX.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import {LanguageItem} from 'components/MDX/LanguagesContext';
-import {MDXComponents} from 'components/MDX/MDXComponents';
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~
-const DISK_CACHE_BREAKER = 10;
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-export default async function compileMDX(
- mdx: string,
- path: string | string[],
- params: {[key: string]: any}
-): Promise<{content: string; toc: string; meta: any}> {
- const fs = require('fs');
- const {
- prepareMDX,
- PREPARE_MDX_CACHE_BREAKER,
- } = require('../utils/prepareMDX');
- const mdxComponentNames = Object.keys(MDXComponents);
-
- // See if we have a cached output first.
- const {FileStore, stableHash} = require('metro-cache');
- const store = new FileStore({
- root: process.cwd() + '/node_modules/.cache/react-docs-mdx/',
- });
- const hash = Buffer.from(
- stableHash({
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // ~~~~ IMPORTANT: Everything that the code below may rely on.
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- mdx,
- ...params,
- mdxComponentNames,
- DISK_CACHE_BREAKER,
- PREPARE_MDX_CACHE_BREAKER,
- lockfile: fs.readFileSync(process.cwd() + '/yarn.lock', 'utf8'),
- })
- );
- const cached = await store.get(hash);
- if (cached) {
- console.log(
- 'Reading compiled MDX for /' + path + ' from ./node_modules/.cache/'
- );
- return cached;
- }
- if (process.env.NODE_ENV === 'production') {
- console.log(
- 'Cache miss for MDX for /' + path + ' from ./node_modules/.cache/'
- );
- }
-
- // If we don't add these fake imports, the MDX compiler
- // will insert a bunch of opaque components we can't introspect.
- // This will break the prepareMDX() call below.
- let mdxWithFakeImports =
- mdx +
- '\n\n' +
- mdxComponentNames
- .map((key) => 'import ' + key + ' from "' + key + '";\n')
- .join('\n');
-
- // Turn the MDX we just read into some JS we can execute.
- const {remarkPlugins} = require('../../plugins/markdownToHtml');
- const {compile: compileMdx} = await import('@mdx-js/mdx');
- const visit = (await import('unist-util-visit')).default;
- const jsxCode = await compileMdx(mdxWithFakeImports, {
- remarkPlugins: [
- ...remarkPlugins,
- (await import('remark-gfm')).default,
- (await import('remark-frontmatter')).default,
- ],
- rehypePlugins: [
- // Support stuff like ```js App.js {1-5} active by passing it through.
- function rehypeMetaAsAttributes() {
- return (tree) => {
- visit(tree, 'element', (node) => {
- if (
- // @ts-expect-error -- tagName is a valid property
- node.tagName === 'code' &&
- node.data &&
- node.data.meta
- ) {
- // @ts-expect-error -- properties is a valid property
- node.properties.meta = node.data.meta;
- }
- });
- };
- },
- ],
- });
- const {transform} = require('@babel/core');
- const jsCode = await transform(jsxCode, {
- plugins: ['@babel/plugin-transform-modules-commonjs'],
- presets: ['@babel/preset-react'],
- }).code;
-
- // Prepare environment for MDX.
- let fakeExports = {};
- const fakeRequire = (name: string) => {
- if (name === 'react/jsx-runtime') {
- return require('react/jsx-runtime');
- } else {
- // For each fake MDX import, give back the string component name.
- // It will get serialized later.
- return name;
- }
- };
- const evalJSCode = new Function('require', 'exports', jsCode);
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // THIS IS A BUILD-TIME EVAL. NEVER DO THIS WITH UNTRUSTED MDX (LIKE FROM CMS)!!!
- // In this case it's okay because anyone who can edit our MDX can also edit this file.
- evalJSCode(fakeRequire, fakeExports);
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- // @ts-expect-error -- default exports is existed after eval
- const reactTree = fakeExports.default({});
-
- // Pre-process MDX output and serialize it.
- let {toc, children} = prepareMDX(reactTree.props.children);
- if (path === 'index') {
- toc = [];
- }
-
- // Parse Frontmatter headers from MDX.
- const fm = require('gray-matter');
- const meta = fm(mdx).data;
-
- // Load the list of translated languages conditionally.
- let languages: Array | null = null;
- if (typeof path === 'string' && path.endsWith('/translations')) {
- languages = await (
- await fetch(
- 'https://raw.githubusercontent.com/reactjs/translations.react.dev/main/langs/langs.json'
- )
- ).json(); // { code: string; name: string; enName: string}[]
- }
-
- const output = {
- content: JSON.stringify(children, stringifyNodeOnServer),
- toc: JSON.stringify(toc, stringifyNodeOnServer),
- meta,
- languages,
- };
-
- // Serialize a server React tree node to JSON.
- function stringifyNodeOnServer(key: unknown, val: any) {
- if (
- val != null &&
- val.$$typeof === Symbol.for('react.transitional.element')
- ) {
- // Remove fake MDX props.
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const {mdxType, originalType, parentName, ...cleanProps} = val.props;
- return [
- '$r',
- typeof val.type === 'string' ? val.type : mdxType,
- val.key,
- cleanProps,
- ];
- } else {
- return val;
- }
- }
-
- // Cache it on the disk.
- await store.set(hash, output);
- return output;
-}
diff --git a/src/utils/generateMDX.tsx b/src/utils/generateMDX.tsx
new file mode 100644
index 00000000000..4d24e5f7ac1
--- /dev/null
+++ b/src/utils/generateMDX.tsx
@@ -0,0 +1,148 @@
+import fs from 'fs';
+import {FileStore, stableHash} from 'metro-cache';
+import grayMatter from 'gray-matter';
+import {compile, run} from '@mdx-js/mdx';
+import * as runtime from 'react/jsx-runtime';
+import {remarkPlugins} from '../../plugins/markdownToHtml';
+import remarkGfm from 'remark-gfm';
+import remarkFrontmatter from 'remark-frontmatter';
+import {MDXComponents, MDXComponentsToc} from '../components/MDX/MDXComponents';
+import {MaxWidthWrapperPlugin} from './mdx/MaxWidthWrapperPlugin';
+import {ExtractedTOC, TOCExtractorPlugin} from './mdx/TOCExtractorPlugin';
+import {MetaAttributesPlugin} from './mdx/MetaAttributesPlugin';
+
+const DISK_CACHE_BREAKER = 13;
+const CACHE_PATH = `${process.cwd()}/node_modules/.cache/react-docs-mdx/`;
+const LOCKFILE_PATH = `${process.cwd()}/yarn.lock`;
+
+type Params = {[key: string]: any};
+type MDXResult = {
+ content: React.ReactNode;
+ toc: ExtractedTOC[];
+ meta: any;
+};
+
+type CachedResult = {
+ code: string;
+ toc: ExtractedTOC[];
+ meta: any;
+};
+
+async function readFromCache(
+ store: FileStore,
+ hash: Buffer,
+ path: string | string[]
+): Promise {
+ try {
+ const cached = await store.get(hash);
+ if (cached) {
+ return JSON.parse(cached.toString());
+ }
+ } catch (error) {
+ console.warn(`Cache read failed for /${path}:`, error);
+ }
+ return null;
+}
+
+async function writeToCache(
+ store: FileStore,
+ hash: Buffer,
+ result: CachedResult,
+ path: string | string[]
+): Promise {
+ try {
+ await store.set(hash, Buffer.from(JSON.stringify(result)));
+ } catch (error) {
+ console.warn(`Cache write failed for /${path}:`, error);
+ }
+}
+
+export async function generateMDX(
+ mdx: string,
+ path: string | string[],
+ params: Params
+): Promise {
+ const store = new FileStore({root: CACHE_PATH});
+ const hash = Buffer.from(
+ stableHash({
+ ...params,
+ mdx,
+ mdxComponentNames: Object.keys(MDXComponents),
+ DISK_CACHE_BREAKER,
+ lockfile: fs.readFileSync(LOCKFILE_PATH, 'utf8'),
+ })
+ );
+
+ const cachedResult = await readFromCache(store, hash, path);
+ if (cachedResult) {
+ const {code, meta, toc} = cachedResult;
+ const {default: MDXContent} = await run(code, {
+ ...runtime,
+ baseUrl: import.meta.url,
+ });
+ const tocWithMDX = await getTransformedToc(toc);
+ return {
+ content: ,
+ toc: tocWithMDX,
+ meta,
+ };
+ }
+
+ const compiled = await compile(mdx, {
+ remarkPlugins: [
+ ...remarkPlugins,
+ remarkGfm,
+ remarkFrontmatter,
+ MaxWidthWrapperPlugin,
+ TOCExtractorPlugin,
+ ],
+ rehypePlugins: [MetaAttributesPlugin],
+ outputFormat: 'function-body',
+ });
+
+ const {data: meta} = grayMatter(mdx);
+ const toc = compiled.data.toc as ExtractedTOC[];
+ const result: CachedResult = {
+ code: String(compiled),
+ toc,
+ meta,
+ };
+
+ await writeToCache(store, hash, result, path);
+
+ const tocWithMDX = await getTransformedToc(toc);
+
+ const {default: MDXContent} = await run(result.code, {
+ ...runtime,
+ baseUrl: import.meta.url,
+ });
+
+ return {
+ content: ,
+ toc: tocWithMDX,
+ meta: result.meta,
+ };
+}
+
+async function getTransformedToc(toc: ExtractedTOC[]): Promise {
+ return await Promise.all(
+ toc.map(async (item) => {
+ if (typeof item.node !== 'string') {
+ return item;
+ }
+
+ const compiled = await compile(item.node, {
+ outputFormat: 'function-body',
+ });
+
+ const {default: MDXContent} = await run(compiled, {
+ ...runtime,
+ baseUrl: import.meta.url,
+ });
+
+ item.node = ;
+
+ return item;
+ })
+ );
+}
diff --git a/src/utils/generateMetadata.ts b/src/utils/generateMetadata.ts
new file mode 100644
index 00000000000..e45b10daba9
--- /dev/null
+++ b/src/utils/generateMetadata.ts
@@ -0,0 +1,75 @@
+import {Metadata} from 'next';
+import {siteConfig} from '../siteConfig';
+import {finishedTranslations} from 'utils/finishedTranslations';
+
+export interface SeoProps {
+ title: string;
+ titleForTitleTag?: string;
+ description?: string;
+ image?: string;
+ isHomePage: boolean;
+ searchOrder?: number;
+ path: string;
+}
+
+function getDomain(languageCode: string): string {
+ const subdomain = languageCode === 'en' ? '' : languageCode + '.';
+ return subdomain + 'react.dev';
+}
+export function generateMetadata({
+ title,
+ titleForTitleTag,
+ image,
+ isHomePage,
+ description: customDescription,
+ searchOrder,
+ path,
+}: SeoProps): Metadata {
+ const siteDomain = getDomain(siteConfig.languageCode);
+ const canonicalUrl = `https://${siteDomain}${path.split(/[\?\#]/)[0]}`;
+
+ const pageTitle =
+ (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React');
+ const twitterTitle = pageTitle.replace(/[<>]/g, '');
+
+ const description = isHomePage
+ ? 'React is the library for web and native user interfaces...'
+ : customDescription ?? 'The library for web and native user interfaces';
+
+ const alternateLanguages = {
+ 'x-default': canonicalUrl.replace(siteDomain, getDomain('en')),
+ ...Object.fromEntries(
+ finishedTranslations.map((languageCode) => [
+ languageCode,
+ canonicalUrl.replace(siteDomain, getDomain(languageCode)),
+ ])
+ ),
+ };
+
+ return {
+ title: pageTitle,
+ description: isHomePage ? description : undefined,
+ alternates: {
+ canonical: canonicalUrl,
+ languages: alternateLanguages,
+ },
+ openGraph: {
+ title: pageTitle,
+ description,
+ url: canonicalUrl,
+ images: [
+ {url: `https://${siteDomain}${image || '/images/og-default.png'}`},
+ ],
+ },
+ twitter: {
+ title: twitterTitle,
+ description,
+ images: [`https://${siteDomain}${image || '/images/og-default.png'}`],
+ },
+ other: {
+ ...(searchOrder != null && {
+ 'algolia-search-order': searchOrder.toString(),
+ }),
+ },
+ };
+}
diff --git a/src/utils/mdx/MaxWidthWrapperPlugin.ts b/src/utils/mdx/MaxWidthWrapperPlugin.ts
new file mode 100644
index 00000000000..22f1a022f3a
--- /dev/null
+++ b/src/utils/mdx/MaxWidthWrapperPlugin.ts
@@ -0,0 +1,48 @@
+import {Root, RootContent} from 'mdast';
+import {u} from 'unist-builder';
+
+export function MaxWidthWrapperPlugin() {
+ const fullWidthTypes = [
+ 'Sandpack',
+ 'FullWidth',
+ 'Illustration',
+ 'IllustrationBlock',
+ 'Challenges',
+ 'Recipes',
+ ];
+
+ return (tree: Root) => {
+ const newChildren: RootContent[] = [];
+ let wrapQueue: RootContent[] = [];
+
+ function flushWrapper() {
+ if (wrapQueue.length > 0) {
+ newChildren.push(
+ u('mdxJsxFlowElement', {
+ name: 'MaxWidth',
+ attributes: [],
+ children: wrapQueue,
+ } as any)
+ );
+ wrapQueue = [];
+ }
+ }
+
+ for (const node of tree.children) {
+ if (
+ // @ts-expect-error
+ fullWidthTypes.includes(node.name) &&
+ // @ts-expect-error: mdxJsxFlowElement
+ node.type === 'mdxJsxFlowElement'
+ ) {
+ flushWrapper();
+ newChildren.push(node);
+ } else {
+ wrapQueue.push(node);
+ }
+ }
+ flushWrapper();
+
+ tree.children = newChildren;
+ };
+}
diff --git a/src/utils/mdx/MetaAttributesPlugin.ts b/src/utils/mdx/MetaAttributesPlugin.ts
new file mode 100644
index 00000000000..f708fd36899
--- /dev/null
+++ b/src/utils/mdx/MetaAttributesPlugin.ts
@@ -0,0 +1,19 @@
+import {Root} from 'mdast';
+import visit from 'unist-util-visit';
+
+// Support stuff like ```js App.js {1-5} active by passing it through.
+export function MetaAttributesPlugin() {
+ return (tree: Root) => {
+ visit(tree, 'element', (node) => {
+ if (
+ // @ts-expect-error -- tagName is a valid property
+ node.tagName === 'code' &&
+ node.data &&
+ node.data.meta
+ ) {
+ // @ts-expect-error -- properties is a valid property
+ node.properties.meta = node.data.meta;
+ }
+ });
+ };
+}
diff --git a/src/utils/mdx/TOCExtractorPlugin.ts b/src/utils/mdx/TOCExtractorPlugin.ts
new file mode 100644
index 00000000000..dd8e4d64401
--- /dev/null
+++ b/src/utils/mdx/TOCExtractorPlugin.ts
@@ -0,0 +1,131 @@
+import visit from 'unist-util-visit';
+import {Node} from 'unist';
+
+interface HeadingNode extends Node {
+ type: 'heading';
+ depth: number;
+ children: Array<{
+ type: string;
+ value?: string;
+ }>;
+ data?: {
+ hProperties?: {
+ id?: string;
+ };
+ };
+}
+
+interface MDXJsxFlowElementNode extends Node {
+ type: 'mdxJsxFlowElement';
+ name: string;
+ attributes?: Array<{
+ name: string;
+ value?: string;
+ }>;
+}
+
+export interface ExtractedTOC {
+ url: string;
+ node: string | React.ReactNode;
+ depth: number;
+}
+
+interface PluginOptions {
+ maxDepth?: number;
+}
+
+export function TOCExtractorPlugin({maxDepth = 3}: PluginOptions = {}) {
+ return (tree: Node, file: any) => {
+ const toc: ExtractedTOC[] = [];
+
+ visit(tree, (node: Node) => {
+ // Standard markdown headings
+ if (node.type === 'heading') {
+ const headingNode = node as HeadingNode;
+ if (headingNode.depth > maxDepth) {
+ return;
+ }
+
+ const mdxSource = file.value
+ .slice(
+ // @ts-ignore
+ node.children[0].position!.start.offset,
+ // @ts-ignore
+ node.children[0].position!.end.offset
+ )
+ .trim();
+
+ const text = headingNode.children
+ .filter((child) => child.type === 'text' && child.value)
+ .map((child) => child.value!)
+ .join('');
+
+ const id =
+ headingNode.data?.hProperties?.id ||
+ text.toLowerCase().replace(/\s+/g, '-');
+
+ toc.push({
+ depth: headingNode.depth,
+ node: mdxSource,
+ url: `#${id}`,
+ });
+ }
+
+ // MDX custom components (e.g., )
+ else if (node.type === 'mdxJsxFlowElement') {
+ const mdxNode = node as MDXJsxFlowElementNode;
+ switch (mdxNode.name) {
+ case 'TeamMember': {
+ let name = 'Team Member';
+ let permalink = 'team-member';
+
+ if (Array.isArray(mdxNode.attributes)) {
+ for (const attr of mdxNode.attributes) {
+ if (attr.name === 'name' && attr.value) {
+ name = attr.value;
+ } else if (attr.name === 'permalink' && attr.value) {
+ permalink = attr.value;
+ }
+ }
+ }
+
+ toc.push({
+ url: `#${permalink}`,
+ depth: 3,
+ node: name,
+ });
+ break;
+ }
+
+ case 'Challenges':
+ toc.push({
+ url: '#challenges',
+ depth: 2,
+ node: 'Challenges',
+ });
+ break;
+ case 'Recap':
+ toc.push({
+ url: '#recap',
+ depth: 2,
+ node: 'Recap',
+ });
+ break;
+ default:
+ break;
+ }
+ }
+ });
+
+ // Insert "Overview" at the top if there's at least one heading
+ if (toc.length > 0) {
+ toc.unshift({
+ url: '#',
+ node: 'Overview',
+ depth: 2,
+ });
+ }
+
+ file.data.toc = toc;
+ };
+}
diff --git a/src/utils/prepareMDX.js b/src/utils/prepareMDX.js
deleted file mode 100644
index 20a22577dc4..00000000000
--- a/src/utils/prepareMDX.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) Facebook, Inc. and its affiliates.
- */
-
-import {Children} from 'react';
-
-// TODO: This logic could be in MDX plugins instead.
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-export const PREPARE_MDX_CACHE_BREAKER = 3;
-// !!! IMPORTANT !!! Bump this if you change any logic.
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-export function prepareMDX(rawChildren) {
- const toc = getTableOfContents(rawChildren, /* depth */ 10);
- const children = wrapChildrenInMaxWidthContainers(rawChildren);
- return {toc, children};
-}
-
-function wrapChildrenInMaxWidthContainers(children) {
- // Auto-wrap everything except a few types into
- // wrappers. Keep reusing the same
- // wrapper as long as we can until we meet
- // a full-width section which interrupts it.
- let fullWidthTypes = [
- 'Sandpack',
- 'FullWidth',
- 'Illustration',
- 'IllustrationBlock',
- 'Challenges',
- 'Recipes',
- ];
- let wrapQueue = [];
- let finalChildren = [];
- function flushWrapper(key) {
- if (wrapQueue.length > 0) {
- const Wrapper = 'MaxWidth';
- finalChildren.push({wrapQueue} );
- wrapQueue = [];
- }
- }
- function handleChild(child, key) {
- if (child == null) {
- return;
- }
- if (typeof child !== 'object') {
- wrapQueue.push(child);
- return;
- }
- if (fullWidthTypes.includes(child.type)) {
- flushWrapper(key);
- finalChildren.push(child);
- } else {
- wrapQueue.push(child);
- }
- }
- Children.forEach(children, handleChild);
- flushWrapper('last');
- return finalChildren;
-}
-
-function getTableOfContents(children, depth) {
- const anchors = [];
- extractHeaders(children, depth, anchors);
- if (anchors.length > 0) {
- anchors.unshift({
- url: '#',
- text: 'Overview',
- depth: 2,
- });
- }
- return anchors;
-}
-
-const headerTypes = new Set([
- 'h1',
- 'h2',
- 'h3',
- 'Challenges',
- 'Recap',
- 'TeamMember',
-]);
-function extractHeaders(children, depth, out) {
- for (const child of Children.toArray(children)) {
- if (child.type && headerTypes.has(child.type)) {
- let header;
- if (child.type === 'Challenges') {
- header = {
- url: '#challenges',
- depth: 2,
- text: 'Challenges',
- };
- } else if (child.type === 'Recap') {
- header = {
- url: '#recap',
- depth: 2,
- text: 'Recap',
- };
- } else if (child.type === 'TeamMember') {
- header = {
- url: '#' + child.props.permalink,
- depth: 3,
- text: child.props.name,
- };
- } else {
- header = {
- url: '#' + child.props.id,
- depth: (child.type && parseInt(child.type.replace('h', ''), 0)) ?? 0,
- text: child.props.children,
- };
- }
- out.push(header);
- } else if (child.children && depth > 0) {
- extractHeaders(child.children, depth - 1, out);
- }
- }
-}
diff --git a/src/utils/rss.js b/src/utils/rss.js
deleted file mode 100644
index 29e5511ea5b..00000000000
--- a/src/utils/rss.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) Facebook, Inc. and its affiliates.
- */
-const Feed = require('rss');
-const fs = require('fs');
-const path = require('path');
-const matter = require('gray-matter');
-
-const getAllFiles = function (dirPath, arrayOfFiles) {
- const files = fs.readdirSync(dirPath);
-
- arrayOfFiles = arrayOfFiles || [];
-
- files.forEach(function (file) {
- if (fs.statSync(dirPath + '/' + file).isDirectory()) {
- arrayOfFiles = getAllFiles(dirPath + '/' + file, arrayOfFiles);
- } else {
- arrayOfFiles.push(path.join(dirPath, '/', file));
- }
- });
-
- return arrayOfFiles;
-};
-
-exports.generateRssFeed = function () {
- const feed = new Feed({
- title: 'React Blog',
- description:
- 'This blog is the official source for the updates from the React team. Anything important, including release notes or deprecation notices, will be posted here first.',
- feed_url: 'https://react.dev/rss.xml',
- site_url: 'https://react.dev/',
- language: 'en',
- favicon: 'https://react.dev/favicon.ico',
- pubDate: new Date(),
- generator: 'react.dev rss module',
- });
-
- const dirPath = path.join(process.cwd(), 'src/content/blog');
- const filesByOldest = getAllFiles(dirPath);
- const files = filesByOldest.reverse();
-
- for (const filePath of files) {
- const id = path.basename(filePath);
- if (id !== 'index.md') {
- const content = fs.readFileSync(filePath, 'utf-8');
- const {data} = matter(content);
- const slug = filePath.split('/').slice(-4).join('/').replace('.md', '');
-
- if (data.title == null || data.title.trim() === '') {
- throw new Error(
- `${id}: Blog posts must include a title in the metadata, for RSS feeds`
- );
- }
- if (data.author == null || data.author.trim() === '') {
- throw new Error(
- `${id}: Blog posts must include an author in the metadata, for RSS feeds`
- );
- }
- if (data.date == null || data.date.trim() === '') {
- throw new Error(
- `${id}: Blog posts must include a date in the metadata, for RSS feeds`
- );
- }
- if (data.description == null || data.description.trim() === '') {
- throw new Error(
- `${id}: Blog posts must include a description in the metadata, for RSS feeds`
- );
- }
-
- feed.item({
- id,
- title: data.title,
- author: data.author || '',
- date: new Date(data.date),
- url: `https://react.dev/blog/${slug}`,
- description: data.description,
- });
- }
- }
-
- fs.writeFileSync('./public/rss.xml', feed.xml({indent: true}));
-};
diff --git a/tailwind.config.js b/tailwind.config.js
index f31a2451677..f709aba7d8f 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -9,6 +9,7 @@ module.exports = {
content: [
'./src/components/**/*.{js,ts,jsx,tsx}',
'./src/pages/**/*.{js,ts,jsx,tsx}',
+ './src/app/**/*.{js,ts,jsx,tsx}',
'./src/styles/**/*.{js,ts,jsx,tsx}',
],
darkMode: 'class',
diff --git a/tsconfig.json b/tsconfig.json
index 440b38510fa..983db114c27 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,16 +1,12 @@
{
"compilerOptions": {
"target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": true,
- "noImplicitReturns": true,
+ "noImplicitReturns": false,
"noImplicitThis": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
@@ -22,14 +18,20 @@
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "src",
- "incremental": true
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
},
"include": [
"next-env.d.ts",
"src/**/*.ts",
- "src/**/*.tsx"
+ "src/**/*.tsx",
+ ".next/types/**/*.ts",
+ "types.d.ts",
+ "src/components/UwuScript.jsx"
],
- "exclude": [
- "node_modules"
- ]
+ "exclude": ["node_modules"]
}
diff --git a/types.d.ts b/types.d.ts
new file mode 100644
index 00000000000..72e9cbfa1db
--- /dev/null
+++ b/types.d.ts
@@ -0,0 +1,8 @@
+declare module 'metro-cache' {
+ export class FileStore {
+ constructor(options: {root: string});
+ get(hash: Buffer): Promise;
+ set(hash: Buffer, value: Buffer): Promise;
+ }
+ export function stableHash(obj: any): string;
+}
diff --git a/yarn.lock b/yarn.lock
index a118cbeda5f..2e33a440cde 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1225,10 +1225,10 @@
unist-util-visit "^4.0.0"
vfile "^5.0.0"
-"@next/env@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/env/-/env-15.1.0.tgz#35b00a5f60ff10dc275182928c325d25c29379ae"
- integrity sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w==
+"@next/env@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-15.2.0-canary.33.tgz#5cd769cca64e09564e80817b6b6aeaba472ea9e9"
+ integrity sha512-y3EPM+JYKU8t2K+i6bc0QrotEZVGpqu9eVjprj4cfS8QZyZcL54s+W9aGB0TBuGavU9tQdZ50W186+toeMV+hw==
"@next/eslint-plugin-next@12.0.3":
version "12.0.3"
@@ -1237,45 +1237,45 @@
dependencies:
glob "7.1.7"
-"@next/swc-darwin-arm64@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.0.tgz#30cb89220e719244c9fa7391641e515a078ade46"
- integrity sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw==
-
-"@next/swc-darwin-x64@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.0.tgz#c24c4f5d1016dd161da32049305b0ddddfc80951"
- integrity sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q==
-
-"@next/swc-linux-arm64-gnu@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.0.tgz#08ed540ecdac74426a624cc7d736dc709244b004"
- integrity sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q==
-
-"@next/swc-linux-arm64-musl@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.0.tgz#dfddbd40087d018266aa92515ec5b3e251efa6dd"
- integrity sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg==
-
-"@next/swc-linux-x64-gnu@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.0.tgz#a7b5373a1b28c0acecbc826a3790139fc0d899e5"
- integrity sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw==
-
-"@next/swc-linux-x64-musl@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.0.tgz#b82a29903ee2f12d8b64163ddf208ac519869550"
- integrity sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg==
-
-"@next/swc-win32-arm64-msvc@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.0.tgz#98deae6cb1fccfb6a600e9faa6aa714402a9ab9a"
- integrity sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg==
-
-"@next/swc-win32-x64-msvc@15.1.0":
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.0.tgz#4b04a6a667c41fecdc63db57dd71ca7e84d0946b"
- integrity sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA==
+"@next/swc-darwin-arm64@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.0-canary.33.tgz#946b9fa766575baf7577ea21b70105d5fefc86ed"
+ integrity sha512-+fCdK2KmR6lWoCTk1fSd5pvbiLZHfZF+D/Xdz3xrXw+pbnBtXWLKQrPT0bCtDseMxD31qcOywq5mAApvI3EGpA==
+
+"@next/swc-darwin-x64@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.0-canary.33.tgz#8878b319cd3d3f90648097d5d76b54b1db02d4f0"
+ integrity sha512-GrrU+tSmeBRow+7bnn7i5M96g3tc28hPH5t5Y65qUXGmmrZwGZN1e1d+8QbXPdAGkvjEPcOkUNQuQVpp1qpYPA==
+
+"@next/swc-linux-arm64-gnu@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.0-canary.33.tgz#0bc4f61ccbb4e7424f9acb215f0d7e0674456f5e"
+ integrity sha512-8RnGxnUpASHoUf6aHUifmZom5b4Ow5nTdCib/CNYXZ6VLuL5ocvmr+DXs/SKzi9h8OHR7JkLwKXHCcF8WyscSg==
+
+"@next/swc-linux-arm64-musl@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.0-canary.33.tgz#15fa3982f382dc6c51ac17d47111b6525deb7f94"
+ integrity sha512-COyE0LzMuLBZSR+Z/TOGilyJPdwSU588Vt0+o8GoECkoDEnjyuO2s2nHa2kDAcEfUEPkhlo0tErU3mF+8AVOTQ==
+
+"@next/swc-linux-x64-gnu@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.0-canary.33.tgz#94d06dcb6f116c470d9d6a7ddda0a29ab23ce80b"
+ integrity sha512-3Y9lqJs+ftU9jgbLdCtvAvF8MNJsJYGMH7icb8QMs1+yOyHHbmwkZoElKdjwfUWzQ2sX28ywp73GWq4HbrsoUg==
+
+"@next/swc-linux-x64-musl@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.0-canary.33.tgz#daaa3610648afe7440f3672d11352ecd5109d2df"
+ integrity sha512-FS9iA+RkZlhdWGQEKtsplVBXIYZJUn5nsRB+1UY46b3uaL6dDypu13ODaSwYuAwXGgkrZBVF9AFO3y4biBnPlA==
+
+"@next/swc-win32-arm64-msvc@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.0-canary.33.tgz#e704113f42449f30ee0d81337ff9c27177cad75c"
+ integrity sha512-Ji9CtBbUx06qvvN/rPohJN2FEFGsUv26F50f2nMRYRwrq3POXDjloGOiRocrjU0ty/cUzCz71qTUfKdmv/ajmg==
+
+"@next/swc-win32-x64-msvc@15.2.0-canary.33":
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.0-canary.33.tgz#d719ea46ba61f3a97678dd0b290147488ea974ba"
+ integrity sha512-hjdbGnkwIZ8zN2vlS6lNsEJO37HRtcEGimzfkruBMsi/DwJBqkJvZbNC/XCJy3HFcU58igncqV52p1IPjmAJAw==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@@ -1656,6 +1656,13 @@
dependencies:
"@types/unist" "*"
+"@types/mdast@^4.0.4":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
+ integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
+ dependencies:
+ "@types/unist" "*"
+
"@types/mdurl@^1.0.0":
version "1.0.2"
resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz"
@@ -1724,6 +1731,11 @@
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+"@types/unist@^3.0.0":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
+ integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
+
"@typescript-eslint/eslint-plugin@^5.36.2":
version "5.36.2"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz"
@@ -2423,15 +2435,10 @@ camelcase-css@^2.0.1:
resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
-caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001579:
- version "1.0.30001636"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78"
- integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==
-
-caniuse-lite@^1.0.30001688:
- version "1.0.30001692"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9"
- integrity sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==
+caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001688:
+ version "1.0.30001695"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz"
+ integrity sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==
ccount@^1.0.0:
version "1.1.0"
@@ -2515,6 +2522,13 @@ chokidar@^3.4.0, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
+chokidar@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
+ integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
+ dependencies:
+ readdirp "^4.0.1"
+
ci-info@^3.2.0:
version "3.3.0"
resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz"
@@ -5798,12 +5812,12 @@ next-tick@^1.1.0:
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz"
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
-next@15.1.0:
- version "15.1.0"
- resolved "https://registry.yarnpkg.com/next/-/next-15.1.0.tgz#be847cf67ac94ae23b57f3ea6d10642f3fc1ad69"
- integrity sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw==
+next@^15.2.0-canary.33:
+ version "15.2.0-canary.33"
+ resolved "https://registry.yarnpkg.com/next/-/next-15.2.0-canary.33.tgz#a866edb669618a5a7aac5035895255eb0c9fb189"
+ integrity sha512-WF8QLeYkakuYwksdWY/F+Bi8tNJfIbiSYk9hCmldn9sNp1lU3lqI1hrW1ynbcMSaXC+qQEr7yol2OdvVZ4nZYQ==
dependencies:
- "@next/env" "15.1.0"
+ "@next/env" "15.2.0-canary.33"
"@swc/counter" "0.1.3"
"@swc/helpers" "0.5.15"
busboy "1.6.0"
@@ -5811,14 +5825,14 @@ next@15.1.0:
postcss "8.4.31"
styled-jsx "5.1.6"
optionalDependencies:
- "@next/swc-darwin-arm64" "15.1.0"
- "@next/swc-darwin-x64" "15.1.0"
- "@next/swc-linux-arm64-gnu" "15.1.0"
- "@next/swc-linux-arm64-musl" "15.1.0"
- "@next/swc-linux-x64-gnu" "15.1.0"
- "@next/swc-linux-x64-musl" "15.1.0"
- "@next/swc-win32-arm64-msvc" "15.1.0"
- "@next/swc-win32-x64-msvc" "15.1.0"
+ "@next/swc-darwin-arm64" "15.2.0-canary.33"
+ "@next/swc-darwin-x64" "15.2.0-canary.33"
+ "@next/swc-linux-arm64-gnu" "15.2.0-canary.33"
+ "@next/swc-linux-arm64-musl" "15.2.0-canary.33"
+ "@next/swc-linux-x64-gnu" "15.2.0-canary.33"
+ "@next/swc-linux-x64-musl" "15.2.0-canary.33"
+ "@next/swc-win32-arm64-msvc" "15.2.0-canary.33"
+ "@next/swc-win32-x64-msvc" "15.2.0-canary.33"
sharp "^0.33.5"
nice-try@^1.0.4:
@@ -6800,6 +6814,11 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
+readdirp@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55"
+ integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==
+
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
@@ -8087,6 +8106,13 @@ unist-builder@^3.0.0:
dependencies:
"@types/unist" "^2.0.0"
+unist-builder@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-4.0.0.tgz#817b326c015a6f9f5e92bb55b8e8bc5e578fe243"
+ integrity sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
unist-util-generated@^1.0.0:
version "1.1.6"
resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz"