From bb38630bc9c4aed40b72eaa171caf7e410a9e393 Mon Sep 17 00:00:00 2001 From: lauren Date: Mon, 7 Oct 2024 19:14:43 -0400 Subject: [PATCH 001/831] Add react-compiler-runtime instructions to compiler docs (#7213) For users of React < 19, there is a new react-compiler-runtime package that can be used to provide a "polyfill" for runtime APIs needed for compiled code. This PR adds docs for that. --- src/content/learn/react-compiler.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/content/learn/react-compiler.md b/src/content/learn/react-compiler.md index 2920e8643..5afaa4cf5 100644 --- a/src/content/learn/react-compiler.md +++ b/src/content/learn/react-compiler.md @@ -20,8 +20,6 @@ These docs are still a work in progress. More documentation is available in the React Compiler is a new experimental compiler that we've open sourced to get early feedback from the community. It still has rough edges and is not yet fully ready for production. - -React Compiler requires React 19 RC. If you are unable to upgrade to React 19, you may try a userspace implementation of the cache function as described in the [Working Group](https://github.com/reactwg/react-compiler/discussions/6). However, please note that this is not recommended and you should upgrade to React 19 when possible. React Compiler is a new experimental compiler that we've open sourced to get early feedback from the community. It is a build-time only tool that automatically optimizes your React app. It works with plain JavaScript, and understands the [Rules of React](/reference/rules), so you don't need to rewrite any code to use it. @@ -226,6 +224,29 @@ module.exports = function () { `babel-plugin-react-compiler` should run first before other Babel plugins as the compiler requires the input source information for sound analysis. +React Compiler works best with React 19 RC. If you are unable to upgrade, you can install the extra `react-compiler-runtime` package which will allow the compiled code to run on versions prior to 19. However, note that the minimum supported version is 17. + + +npm install react-compiler-runtime@experimental + + +You should also add the correct `target` to your compiler config, where `target` is the major version of React you are targeting: + +```js {3} +// babel.config.js +const ReactCompilerConfig = { + target: '18' // '17' | '18' | '19' +}; + +module.exports = function () { + return { + plugins: [ + ['babel-plugin-react-compiler', ReactCompilerConfig], + ], + }; +}; +``` + ### Vite {/*usage-with-vite*/} If you use Vite, you can add the plugin to vite-plugin-react: From 2b2d0f2309f49c82cf5bb88ea62fb2e44661c634 Mon Sep 17 00:00:00 2001 From: Jake Saterlay <41985955+JakeSaterlay@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:57:10 +0100 Subject: [PATCH 002/831] `useActionState` pending example (#6989) Co-authored-by: Sebastian "Sebbie" Silbermann --- src/content/reference/react/useActionState.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/content/reference/react/useActionState.md b/src/content/reference/react/useActionState.md index 6f5924e3d..52fba47e7 100644 --- a/src/content/reference/react/useActionState.md +++ b/src/content/reference/react/useActionState.md @@ -20,7 +20,7 @@ In earlier React Canary versions, this API was part of React DOM and called `use `useActionState` is a Hook that allows you to update state based on the result of a form action. ```js -const [state, formAction] = useActionState(fn, initialState, permalink?); +const [state, formAction, isPending] = useActionState(fn, initialState, permalink?); ``` @@ -35,7 +35,7 @@ const [state, formAction] = useActionState(fn, initialState, permalink?); {/* TODO T164397693: link to actions documentation once it exists */} -Call `useActionState` at the top level of your component to create component state that is updated [when a form action is invoked](/reference/react-dom/components/form). You pass `useActionState` an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state. The latest form state is also passed to the function that you provided. +Call `useActionState` at the top level of your component to create component state that is updated [when a form action is invoked](/reference/react-dom/components/form). You pass `useActionState` an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state and whether the Action is still pending. The latest form state is also passed to the function that you provided. ```js import { useActionState } from "react"; @@ -71,10 +71,11 @@ If used with a Server Action, `useActionState` allows the server's response from #### Returns {/*returns*/} -`useActionState` returns an array with exactly two values: +`useActionState` returns an array with the following values: 1. The current state. During the first render, it will match the `initialState` you have passed. After the action is invoked, it will match the value returned by the action. 2. A new action that you can pass as the `action` prop to your `form` component or `formAction` prop to any `button` component within the form. +3. The `isPending` flag that tells you whether there is a pending Transition. #### Caveats {/*caveats*/} @@ -104,10 +105,11 @@ function MyComponent() { } ``` -`useActionState` returns an array with exactly two items: +`useActionState` returns an array with the following items: 1. The current state of the form, which is initially set to the initial state you provided, and after the form is submitted is set to the return value of the action you provided. 2. A new action that you pass to `
` as its `action` prop. +3. A pending state that you can utilise whilst your action is processing. When the form is submitted, the action function that you provided will be called. Its return value will become the new current state of the form. @@ -133,13 +135,13 @@ import { useActionState, useState } from "react"; import { addToCart } from "./actions.js"; function AddToCartForm({itemID, itemTitle}) { - const [message, formAction] = useActionState(addToCart, null); + const [message, formAction, isPending] = useActionState(addToCart, null); return (

{itemTitle}

- {message} + {isPending ? "Loading..." : message}
); } @@ -162,6 +164,10 @@ export async function addToCart(prevState, queryData) { if (itemID === "1") { return "Added to cart"; } else { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 2000); + }); return "Couldn't add to cart: the item is sold out."; } } From 31262c823bd4585127dc3585e21c8d558affdc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Mon, 14 Oct 2024 21:37:54 +0900 Subject: [PATCH 003/831] docs(typo): fix typo bundle bundler bundling linted by textlint (#1080) * docs(typo): fix typo bundle bundler bundling linted by textlint * docs(typo): fix typo bundle bundler bundling linted by textlint --- ...what-we-have-been-working-on-march-2023.md | 2 +- src/content/blog/2023/05/03/react-canaries.md | 4 +-- ...t-we-have-been-working-on-february-2024.md | 2 +- src/content/learn/describing-the-ui.md | 2 +- src/content/reference/react/index.md | 2 +- src/content/reference/rsc/directives.md | 2 +- textlint/data/rules/translateGlossary.js | 27 +++++++++++++++++++ wiki/translate-glossary.md | 3 +++ 8 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md index f67760294..4ef33b6bc 100644 --- a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md +++ b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md @@ -21,7 +21,7 @@ React Labs 게시글에는 활발히 연구 개발 중인 프로젝트에 대한 React 서버 컴포넌트(React Server Components, 또는 RSC)는 React 팀에서 설계한 새로운 애플리케이션 아키텍처입니다. -우리는 먼저 [소개 발표](/blog/2020/12/21/data-fetching-with-react-server-components)와 [RFC](https://github.com/reactjs/rfcs/pull/188)에서 RSC에 대한 연구를 공유했습니다. 그 내용을 요약하면, 미리 실행하고 JavaScript bundle에서 제외할 수 있는 새로운 종류의 컴포넌트인 서버 컴포넌트를 소개하고 있습니다. 서버 컴포넌트는 빌드 중에 실행되어 파일 시스템에서 읽거나 정적 콘텐츠를 가져올 수 있습니다. 또한 서버에서 실행할 수 있어 API를 빌드할 필요 없이 데이터 계층에 접근할 수 있습니다. props를 통해 서버 컴포넌트에서 상호작용하는 브라우저의 클라이언트 컴포넌트로 데이터를 전달할 수 있습니다. +우리는 먼저 [소개 발표](/blog/2020/12/21/data-fetching-with-react-server-components)와 [RFC](https://github.com/reactjs/rfcs/pull/188)에서 RSC에 대한 연구를 공유했습니다. 그 내용을 요약하면, 미리 실행하고 JavaScript 번들에서 제외할 수 있는 새로운 종류의 컴포넌트인 서버 컴포넌트를 소개하고 있습니다. 서버 컴포넌트는 빌드 중에 실행되어 파일 시스템에서 읽거나 정적 콘텐츠를 가져올 수 있습니다. 또한 서버에서 실행할 수 있어 API를 빌드할 필요 없이 데이터 계층에 접근할 수 있습니다. props를 통해 서버 컴포넌트에서 상호작용하는 브라우저의 클라이언트 컴포넌트로 데이터를 전달할 수 있습니다. RSC는 서버 중심의 멀티 페이지 애플리케이션의 간단한 "요청/응답" 멘탈 모델에 클라이언트 중심의 싱글 페이지 애플리케이션의 원활한 상호작용을 결합하여 양쪽의 장점을 모두 제공합니다. diff --git a/src/content/blog/2023/05/03/react-canaries.md b/src/content/blog/2023/05/03/react-canaries.md index d2edcb2a6..cb1666447 100644 --- a/src/content/blog/2023/05/03/react-canaries.md +++ b/src/content/blog/2023/05/03/react-canaries.md @@ -57,7 +57,7 @@ Canaries 채널을 통한 롤링 릴리즈를 통해 더욱 긴밀한 피드백 기술적으로는 [Experimental releases](/community/versioning-policy#canary-channel)를 사용*할 수* 있지만, 실험적 API는 안정화 과정에서 중대한 변경이 있을 수 있으므로(또는 심지어 완전히 제거될 수도 있으므로) 프로덕션 환경에서 사용하지 않는 것이 좋습니다. Canaries에도 실수가 있을 수 있지만(다른 릴리즈와 마찬가지로), 앞으로는 Canaries에 중대한 변경 사항이 있으면 블로그에 공지할 계획입니다. Canaries는 Meta가 내부적으로 실행하는 코드에 가장 가깝기 때문에 일반적으로 비교적 안정적일 것으로 기대할 수 있습니다. 하지만 버전을 고정*하고* 고정된 커밋 사이를 업데이트할 때는 GitHub 커밋 로그를 수동으로 스캔해야 합니다. -**프레임워크와 같이 엄선된 환경 밖에서 React를 사용하는 대부분 사람은 Stable 릴리즈를 계속 사용하기를 원할 것으로 예상합니다.** 하지만 프레임워크를 구축하는 경우 특정 커밋에 고정된 React의 Canary 버전을 bundle로 묶어 원하는 속도로 업데이트하는 것을 고려할 수 있습니다. 이 방법의 장점은 지난 몇 년 동안 React Native가 해왔던 방식과 유사하게, 완성된 개별 React 기능 및 버그 수정을 사용자에게 더 일찍, 자체 릴리즈 일정에 맞춰 제공할 수 있다는 것입니다. 단점은 어떤 React 커밋을 가져오는지 검토하고 릴리즈에 어떤 React 변경 사항이 포함되었는지 사용자에게 알리는 추가적인 책임을 져야 한다는 것입니다. +**프레임워크와 같이 엄선된 환경 밖에서 React를 사용하는 대부분 사람은 Stable 릴리즈를 계속 사용하기를 원할 것으로 예상합니다.** 하지만 프레임워크를 구축하는 경우 특정 커밋에 고정된 React의 Canary 버전을 번들로 묶어 원하는 속도로 업데이트하는 것을 고려할 수 있습니다. 이 방법의 장점은 지난 몇 년 동안 React Native가 해왔던 방식과 유사하게, 완성된 개별 React 기능 및 버그 수정을 사용자에게 더 일찍, 자체 릴리즈 일정에 맞춰 제공할 수 있다는 것입니다. 단점은 어떤 React 커밋을 가져오는지 검토하고 릴리즈에 어떤 React 변경 사항이 포함되었는지 사용자에게 알리는 추가적인 책임을 져야 한다는 것입니다. 프레임워크 작성자로서 이 접근 방식을 시도해 보고 싶다면, 저희에게 연락해 주세요. @@ -77,7 +77,7 @@ Canary 릴리즈는 특정 시점에 다음 안정된 React 릴리즈에 포함 지난 [3월에 발표했듯이](/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components) React Server 컴포넌트 컨벤션은 확정되었으며, 사용자 대면 API 계약과 관련된 중대한 변경 사항은 없을 것으로 예상됩니다. 그러나 서로 얽혀 있는 여러 프레임워크 전용 기능([에셋 로딩](/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#asset-loading)과 같은)에 대한 작업이 진행 중이며, 더 많은 변경 사항이 있을 것으로 예상되기 때문에 아직 안정된 버전의 React에서 React Server 컴포넌트에 대한 지원을 릴리즈할 수는 없습니다. -즉, React Server 컴포넌트가 프레임워크에 채택될 준비가 되었다는 뜻입니다. 그러나 다음 주요 React 릴리즈가 나올 때까지 프레임워크가 이를 채택할 수 있는 유일한 방법은 고정된 Canary 버전의 React를 출시하는 것입니다. (두 개의 React 복사본이 bundle로 제공되는 것을 피하고자, 이를 원하는 프레임워크는 프레임워크와 함께 제공하는 고정된 Canary에 `react` 및 `react-dom`의 해결 방법을 적용하고 사용자에게 이를 설명해야 합니다. 예를 들어, 이것이 Next.js App Router가 하는 일입니다.) +즉, React Server 컴포넌트가 프레임워크에 채택될 준비가 되었다는 뜻입니다. 그러나 다음 주요 React 릴리즈가 나올 때까지 프레임워크가 이를 채택할 수 있는 유일한 방법은 고정된 Canary 버전의 React를 출시하는 것입니다. (두 개의 React 복사본이 번들로 제공되는 것을 피하고자, 이를 원하는 프레임워크는 프레임워크와 함께 제공하는 고정된 Canary에 `react` 및 `react-dom`의 해결 방법을 적용하고 사용자에게 이를 설명해야 합니다. 예를 들어, 이것이 Next.js App Router가 하는 일입니다.) ## Stable 및 Canary 버전 모두에 대해 라이브러리 테스트하기 {/*testing-libraries-against-both-stable-and-canary-versions*/} diff --git a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md index f14149a8b..a71799eae 100644 --- a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md +++ b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md @@ -75,7 +75,7 @@ Canary는 React의 개발 방식을 변경하는 것입니다. 이전에는 기 React 서버 컴포넌트, 에셋 불러오기, 문서 메타데이터 및 액션 모두 React Canary에 도입되었으며, 이러한 기능에 대한 문서를 react.dev에 추가했습니다. -- **지시어**: [`"use client"`](/reference/rsc/use-client)와 [`"use server"`](/reference/rsc/use-server)는 풀스택 React 프레임워크를 위해 설계한 bundler 기능입니다. 이들은 두 환경 사이의 "분할점"을 나타냅니다. "use client"는 [Astro Islands](https://docs.astro.build/en/concepts/islands/#creating-an-island)처럼 bundler에 ` @@ -27,7 +27,7 @@ React의 ` @@ -38,16 +38,16 @@ React의 ` +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + bootstrapScripts: [assetMap['/main.js']] + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + bootstrapScripts: [assetMap['/main.js']] + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` ``` -클라이언트에선, 추가된 bootstrap 스크립트는 [`hydrateRoot`를 호출해 `document` 전체를 hydrate 해야합니다:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) +클라이언트에선, 추가된 bootstrap 스크립트는 [`hydrateRoot`를 호출해 `document` 전체를 hydrate 해야합니다](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document). ```js [[1, 4, ""]] import { hydrateRoot } from 'react-dom/client'; @@ -139,9 +139,9 @@ hydrateRoot(document, ); #### 빌드 결과물에서 CSS와 JS의 경로 읽어오기 {/*reading-css-and-js-asset-paths-from-the-build-output*/} -JS와 CSS같은 최종 에셋들에 대한 URL들은 종종 빌드 후에 해시됩니다. 예를 들어, `styles.css` 대신 `styles.123456.css`와 같은 형태로 끝날 수 있습니다. 에셋들의 파일명을 해시하는 것은 모든 빌드의 결과물이 각각 다른 파일명을 가지도록 보장합니다. 이는 정적 에셋들에 대한 장기 캐싱을 안전하게 활성화할 수 있도록 해줍니다: 즉, 특정 이름의 파일 내용은 절대 바뀌지 않는 다는 것을 보장합니다. +JS와 CSS같은 최종 에셋들에 대한 URL들은 종종 빌드 후에 해시됩니다. 예를 들어, `styles.css` 대신 `styles.123456.css`와 같은 형태로 끝날 수 있습니다. 에셋들의 파일명을 해시하는 것은 모든 빌드의 결과물이 각각 다른 파일명을 가지도록 보장합니다. 이는 정적 에셋들에 대한 장기 캐싱을 안전하게 활성화할 수 있도록 해줍니다. 즉, 특정 이름의 파일 내용은 절대 바뀌지 않는 다는 것을 보장합니다. -하지만, 빌드 후에 에셋들의 URL을 알 수 없다면, 소스 코드에 URL을 넣을 수 없습니다. 예를 들어, JSX에 `"/styles.css"`를 하드코딩하는 것은 작동하지 않습니다. 소스 코드에 URL을 넣지 않으려면, 루트 컴포넌트는 props로 전달된 맵에서 실제 파일명을 읽어야합니다: +하지만, 빌드 후에 에셋들의 URL을 알 수 없다면, 소스 코드에 URL을 넣을 수 없습니다. 예를 들어, JSX에 `"/styles.css"`를 하드코딩하는 것은 작동하지 않습니다. 소스 코드에 URL을 넣지 않으려면, 루트 컴포넌트는 props로 전달된 맵에서 실제 파일명을 읽어야합니다. ```js {1,6} export default function App({ assetMap }) { @@ -157,7 +157,7 @@ export default function App({ assetMap }) { } ``` -서버에선 ``을 렌더링하고, 에셋 URL들과 함께 `assetMap`을 전달합니다: +서버에선 ``을 렌더링하고, 에셋 URL들과 함께 `assetMap`을 전달합니다. ```js {1-5,8,9} // 빌드 도구로부터 이 JSON을 얻어야합니다. 예를 들어, 빌드 결과물에서 읽어올 수 있습니다. @@ -176,7 +176,7 @@ async function handler(request) { } ``` -서버가 ``를 렌더링한 이후엔, 클라이언트에서도 hydration 에러를 피하기 위해 `assetMap`과 함께 렌더링해야합니다. `assetMap`을 직렬화하고 클라이언트에 전달하기 위해 다음과 같이 할 수 있습니다: +서버가 ``를 렌더링한 이후엔, 클라이언트에서도 hydration 오류를 피하기 위해 `assetMap`과 함께 렌더링해야합니다. `assetMap`을 직렬화하고 클라이언트에 전달하기 위해 다음과 같이 할 수 있습니다. ```js {9-10} // 빌드 도구로부터 이 JSON을 얻어야합니다. @@ -197,7 +197,7 @@ async function handler(request) { } ``` -위의 예시에서, `bootstrapScriptContent` 옵션은 클라이언트에서 `window.assetMap` 전역 변수를 설정하는 인라인 ` + + ); +} diff --git a/src/components/DevContentRefresher.tsx b/src/components/DevContentRefresher.tsx new file mode 100644 index 000000000..31a0961ed --- /dev/null +++ b/src/components/DevContentRefresher.tsx @@ -0,0 +1,29 @@ +'use client'; + +import {useRouter} from 'next/navigation'; +import {useRef, useEffect} from 'react'; + +export function DevContentRefresher() { + const router = useRouter(); + const wsRef = useRef(null); + + useEffect(() => { + wsRef.current = new WebSocket('ws://localhost:3001'); + + wsRef.current.onmessage = (event) => { + const message = JSON.parse(event.data); + + if (message.event === 'refresh') { + console.log('Refreshing content...'); + // @ts-ignore + router.hmrRefresh(); // Triggers client-side refresh + } + }; + + return () => { + wsRef.current?.close(); + }; + }, [router]); + + return null; +} diff --git a/src/components/ErrorDecoderProvider.tsx b/src/components/ErrorDecoderProvider.tsx new file mode 100644 index 000000000..bad1ed2d0 --- /dev/null +++ b/src/components/ErrorDecoderProvider.tsx @@ -0,0 +1,19 @@ +'use client'; + +import {ErrorDecoderContext} from './ErrorDecoderContext'; + +export function ErrorDecoderProvider({ + children, + errorMessage, + errorCode, +}: { + children: React.ReactNode; + errorMessage: string | null; + errorCode: string | null; +}) { + return ( + + {children} + + ); +} diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index 34db728ce..16b974c10 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -3,12 +3,12 @@ */ import {useState} from 'react'; -import {useRouter} from 'next/router'; import cn from 'classnames'; +import {usePathname} from 'next/navigation'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { - const {asPath} = useRouter(); - const cleanedPath = asPath.split(/[\?\#]/)[0]; + const pathname = usePathname(); + const cleanedPath = pathname.split(/[\?\#]/)[0]; // Reset on route changes. return ; } diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index 24d379589..ea0c53e1c 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -1,16 +1,16 @@ +'use client'; + /* * Copyright (c) Facebook, Inc. and its affiliates. */ -import {Suspense} from 'react'; import * as React from 'react'; -import {useRouter} from 'next/router'; +import {Suspense} from 'react'; import {SidebarNav} from './SidebarNav'; import {Footer} from './Footer'; import {Toc} from './Toc'; -// import SocialBanner from '../SocialBanner'; import {DocsPageFooter} from 'components/DocsFooter'; -import {Seo} from 'components/Seo'; + import PageHeading from 'components/PageHeading'; import {getRouteMeta} from './getRouteMeta'; import {TocContext} from '../MDX/TocContext'; @@ -20,8 +20,8 @@ import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; -import Head from 'next/head'; +// Prefetch the code block component import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); interface PageProps { @@ -36,6 +36,7 @@ interface PageProps { }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; languages?: Languages | null; + pathname: string; } export function Page({ @@ -44,11 +45,11 @@ export function Page({ routeTree, meta, section, + pathname, languages = null, }: PageProps) { - const {asPath} = useRouter(); - const cleanedPath = asPath.split(/[\?\#]/)[0]; - const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta( + const cleanedPath = pathname.split(/[\?\#]/)[0]; + const {route, nextRoute, prevRoute, breadcrumbs} = getRouteMeta( cleanedPath, routeTree ); @@ -113,31 +114,17 @@ export function Page({ showSidebar = false; } - let searchOrder; - if (section === 'learn' || (section === 'blog' && !isBlogIndex)) { - searchOrder = order; - } - return ( <> - {(isHomePage || isBlogIndex) && ( - - - + // RSS Feed link is now handled by metadata in layout.tsx + )} - {/**/}
-
+
{content}
- {showToc && toc.length > 0 && } + {showToc && toc.length > 0 && }
diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 72003df74..f67b0ed2b 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -5,12 +5,12 @@ import {useRef, useLayoutEffect, Fragment} from 'react'; import cn from 'classnames'; -import {useRouter} from 'next/router'; import {SidebarLink} from './SidebarLink'; import {useCollapse} from 'react-collapsed'; import usePendingRoute from 'hooks/usePendingRoute'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {siteConfig} from 'siteConfig'; +import {usePathname} from 'next/navigation'; interface SidebarRouteTreeProps { isForceExpanded: boolean; @@ -77,7 +77,7 @@ export function SidebarRouteTree({ routeTree, level = 0, }: SidebarRouteTreeProps) { - const slug = useRouter().asPath.split(/[\?\#]/)[0]; + const slug = usePathname().split(/[\?\#]/)[0]; const pendingRoute = usePendingRoute(); const currentRoutes = routeTree.routes as RouteItem[]; return ( diff --git a/src/components/Layout/Toc.tsx b/src/components/Layout/Toc.tsx index 5308c602c..a8d269898 100644 --- a/src/components/Layout/Toc.tsx +++ b/src/components/Layout/Toc.tsx @@ -11,7 +11,11 @@ export function Toc({headings}: {headings: Toc}) { // TODO: We currently have a mismatch between the headings in the document // and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges). // Select the max TOC item we have here for now, but remove this after the fix. - const selectedIndex = Math.min(currentIndex, headings.length - 1); + const selectedIndex = + currentIndex !== undefined + ? Math.min(currentIndex, headings.length - 1) + : -1; + return (