From 7206614d0be6c7307fc0c92cf436df74a39bbbab Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 25 May 2022 20:24:53 -0500 Subject: [PATCH 1/5] Update contributing doc and PR template for examples (#37193) * Update contributing doc and PR template for examples * Apply suggestions from code review Co-authored-by: Steven --- .github/pull_request_template.md | 1 + contributing.md | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dd585c5365423..83e70b093047b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -22,3 +22,4 @@ Choose the right checklist for the change that you're making: ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` +- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) diff --git a/contributing.md b/contributing.md index 33251e0633e45..de47be28c1130 100644 --- a/contributing.md +++ b/contributing.md @@ -265,7 +265,15 @@ Below are the steps to add a new link: ## Adding examples -When you add an example to the [examples](examples) directory, don’t forget to add a `README.md` file with the following format: +When you add an example to the [examples](examples) directory, please follow these guidelines to ensure high quality examples: + +- TypeScript should be leveraged for new examples (no need for separate JavaScript and TypeScript examples) +- Examples should not add custom eslint configuration (we have specific templates for eslint) +- If API routes aren't used in an example, they should be omitted +- If an example exists for a certain library and you would like to showcase a specific feature of that library, the existing example should be updated (instead of adding a new example) +- Package manager specific config should not be added (e.g. `resolutions` in `package.json`) + +Also don’t forget to add a `README.md` file with the following format: - Replace `DIRECTORY_NAME` with the directory name you’re adding. - Fill in `Example Name` and `Description`. From 3f088592d70de448cc2257e5f05086c991992df5 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 26 May 2022 12:37:49 +0200 Subject: [PATCH 2/5] Split useServerResponse for initial responses (#37209) Small refactor that splits initial response from fetching a changed response for Flight. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) --- packages/next/client/app-index.tsx | 95 ++++++++++++++---------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index c16884831644c..e4dc772192151 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -5,7 +5,10 @@ import ReactDOMClient from 'react-dom/client' // @ts-ignore startTransition exists when using React 18 import React, { useState, startTransition } from 'react' import { RefreshContext } from './streaming/refresh' -import { createFromFetch } from 'next/dist/compiled/react-server-dom-webpack' +import { + createFromFetch, + createFromReadableStream, +} from 'next/dist/compiled/react-server-dom-webpack' /// @@ -36,7 +39,8 @@ const getCacheKey = () => { const encoder = new TextEncoder() let initialServerDataBuffer: string[] | undefined = undefined -let initialServerDataWriter: WritableStreamDefaultWriter | undefined = undefined +let initialServerDataWriter: ReadableStreamDefaultController | undefined = + undefined let initialServerDataLoaded = false let initialServerDataFlushed = false @@ -48,7 +52,7 @@ function nextServerDataCallback(seg: [number, string, string]) { throw new Error('Unexpected server data: missing bootstrap script.') if (initialServerDataWriter) { - initialServerDataWriter.write(encoder.encode(seg[2])) + initialServerDataWriter.enqueue(encoder.encode(seg[2])) } else { initialServerDataBuffer.push(seg[2]) } @@ -63,19 +67,19 @@ function nextServerDataCallback(seg: [number, string, string]) { // Hence, we use two variables `initialServerDataLoaded` and // `initialServerDataFlushed` to make sure the writer will be closed and // `initialServerDataBuffer` will be cleared in the right time. -function nextServerDataRegisterWriter(writer: WritableStreamDefaultWriter) { +function nextServerDataRegisterWriter(ctr: ReadableStreamDefaultController) { if (initialServerDataBuffer) { initialServerDataBuffer.forEach((val) => { - writer.write(encoder.encode(val)) + ctr.enqueue(encoder.encode(val)) }) if (initialServerDataLoaded && !initialServerDataFlushed) { - writer.close() + ctr.close() initialServerDataFlushed = true initialServerDataBuffer = undefined } } - initialServerDataWriter = writer + initialServerDataWriter = ctr } // When `DOMContentLoaded`, we can close all pending writers to finish hydration. @@ -104,54 +108,26 @@ function createResponseCache() { } const rscCache = createResponseCache() -function fetchFlight(href: string, props?: any) { - const url = new URL(href, location.origin) - const searchParams = url.searchParams - searchParams.append('__flight__', '1') - if (props) { - searchParams.append('__props__', JSON.stringify(props)) - } - return fetch(url.toString()) -} - -function useServerResponse(cacheKey: string, serialized?: string) { - let response = rscCache.get(cacheKey) +function useInitialServerResponse(cacheKey: string) { + const response = rscCache.get(cacheKey) if (response) return response - if (initialServerDataBuffer) { - const t = new TransformStream() - const writer = t.writable.getWriter() - response = createFromFetch(Promise.resolve({ body: t.readable })) - nextServerDataRegisterWriter(writer) - } else { - const fetchPromise = serialized - ? (() => { - const t = new TransformStream() - const writer = t.writable.getWriter() - writer.ready.then(() => { - writer.write(new TextEncoder().encode(serialized)) - }) - return Promise.resolve({ body: t.readable }) - })() - : fetchFlight(getCacheKey()) - response = createFromFetch(fetchPromise) - } + const readable = new ReadableStream({ + start(controller) { + nextServerDataRegisterWriter(controller) + }, + }) + const newResponse = createFromReadableStream(readable) - rscCache.set(cacheKey, response) - return response + rscCache.set(cacheKey, newResponse) + return newResponse } -const ServerRoot = ({ - cacheKey, - serialized, -}: { - cacheKey: string - serialized?: string -}) => { +const ServerRoot = ({ cacheKey }: { cacheKey: string }) => { React.useEffect(() => { rscCache.delete(cacheKey) }) - const response = useServerResponse(cacheKey, serialized) + const response = useInitialServerResponse(cacheKey) const root = response.readRoot() return root } @@ -171,9 +147,8 @@ function Root({ children }: React.PropsWithChildren<{}>): React.ReactElement { return children as React.ReactElement } -const RSCComponent = (props: any) => { +const RSCComponent = () => { const cacheKey = getCacheKey() - const { __flight_serialized__ } = props const [, dispatch] = useState({}) const rerender = () => dispatch({}) // If there is no cache, or there is serialized data already @@ -189,11 +164,31 @@ const RSCComponent = (props: any) => { return ( - + ) } +function fetchFlight(href: string, props?: any) { + const url = new URL(href, location.origin) + const searchParams = url.searchParams + searchParams.append('__flight__', '1') + if (props) { + searchParams.append('__props__', JSON.stringify(props)) + } + return fetch(url.toString()) +} + +function useServerResponse(cacheKey: string) { + let response = rscCache.get(cacheKey) + if (response) return response + + response = createFromFetch(fetchFlight(getCacheKey())) + + rscCache.set(cacheKey, response) + return response +} + const AppRouterContext = React.createContext({}) // TODO: move to client component when handling is implemented From a8636528931fd3d30f7c4107e34554e06f01ef2f Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 26 May 2022 16:35:09 +0200 Subject: [PATCH 3/5] Refactor fetchFlight (#37213) * Remove __props__ * Remove refreshCache function --- packages/next/client/app-index.tsx | 28 ++++------------------------ packages/next/server/app-render.tsx | 4 ---- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index e4dc772192151..c0b958017bc3b 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -3,8 +3,7 @@ import '../build/polyfills/polyfill-module' // @ts-ignore react-dom/client exists when using React 18 import ReactDOMClient from 'react-dom/client' // @ts-ignore startTransition exists when using React 18 -import React, { useState, startTransition } from 'react' -import { RefreshContext } from './streaming/refresh' +import React from 'react' import { createFromFetch, createFromReadableStream, @@ -149,33 +148,14 @@ function Root({ children }: React.PropsWithChildren<{}>): React.ReactElement { const RSCComponent = () => { const cacheKey = getCacheKey() - const [, dispatch] = useState({}) - const rerender = () => dispatch({}) - // If there is no cache, or there is serialized data already - function refreshCache(nextProps: any) { - startTransition(() => { - const currentCacheKey = getCacheKey() - const response = createFromFetch(fetchFlight(currentCacheKey, nextProps)) - - rscCache.set(currentCacheKey, response) - rerender() - }) - } - - return ( - - - - ) + return } -function fetchFlight(href: string, props?: any) { +function fetchFlight(href: string) { const url = new URL(href, location.origin) const searchParams = url.searchParams searchParams.append('__flight__', '1') - if (props) { - searchParams.append('__props__', JSON.stringify(props)) - } + return fetch(url.toString()) } diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 04e01c4d8d1c9..7dcbd2046a4aa 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -372,10 +372,6 @@ export async function renderToHTML( } ) - // const serverComponentProps = query.__props__ - // ? JSON.parse(query.__props__ as string) - // : undefined - const jsxStyleRegistry = createStyleRegistry() const styledJsxFlushEffect = () => { From 3c764fc14385b65d30ccf1aa790a8c25eefcd906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 26 May 2022 17:18:42 +0200 Subject: [PATCH 4/5] Update contributing doc and PR template for examples (#37215) Follow-up on https://github.com/vercel/next.js/pull/37193 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) --- contributing.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index de47be28c1130..4ac04d261749f 100644 --- a/contributing.md +++ b/contributing.md @@ -268,10 +268,12 @@ Below are the steps to add a new link: When you add an example to the [examples](examples) directory, please follow these guidelines to ensure high quality examples: - TypeScript should be leveraged for new examples (no need for separate JavaScript and TypeScript examples) -- Examples should not add custom eslint configuration (we have specific templates for eslint) +- Examples should not add custom ESLint configuration (we have specific templates for ESLint) - If API routes aren't used in an example, they should be omitted - If an example exists for a certain library and you would like to showcase a specific feature of that library, the existing example should be updated (instead of adding a new example) - Package manager specific config should not be added (e.g. `resolutions` in `package.json`) +- In `package.json` the version of `next` (and `eslint-config-next`) should be `latest` +- In `package.json` the dependency versions should be up-to-date Also don’t forget to add a `README.md` file with the following format: From 6a5bdb5d80c07884ea1615ec41f5ef164daeb6e9 Mon Sep 17 00:00:00 2001 From: Tom Marshall Date: Thu, 26 May 2022 16:29:16 +0100 Subject: [PATCH 5/5] Update `cms-kontent` example to action PR feedback (#37206) * Updates the `cms-kontent` example `Image` component to implement the suggested improvements from @styfle in #37188. * Simplifies the Kontent loader host checking. * Reduces the default image quality to from `100` to `75`. * Add `tslib` dependency to fix `Module not found: Can't resolve 'tslib'` error when importing `transformImageUrl` in the `Image` component. It looks like this might be a bug in v11 of the Kontent Delivery SDK, as it appears `tslib` needs to be included as dependency, rather than dev dependency. I missed this originally as the example runs fine in the Next repo as the root `yarn.lock` has `tslib`. It's only when the example is used via `yarn create next-app` that the issue occurs. We can likely remove this in future alongside an upgrade to the SDK package once this issue has been fixed and released there. https://github.com/vercel/next.js/pull/37188 --- examples/cms-kontent/components/image.js | 6 ++---- examples/cms-kontent/package.json | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/cms-kontent/components/image.js b/examples/cms-kontent/components/image.js index 60c4b11de7558..7b47e9176ad06 100644 --- a/examples/cms-kontent/components/image.js +++ b/examples/cms-kontent/components/image.js @@ -1,8 +1,6 @@ import NextImage from 'next/image' import { transformImageUrl } from '@kentico/kontent-delivery' -const KONTENT_ASSET_HOSTNAME_REGEX = /.kc-usercontent.com$/ - const getLoader = (src) => { return srcIsKontentAsset(src) ? kontentImageLoader : undefined } @@ -10,13 +8,13 @@ const getLoader = (src) => { const srcIsKontentAsset = (src) => { try { const { hostname } = new URL(src) - return KONTENT_ASSET_HOSTNAME_REGEX.test(hostname) + return hostname.endsWith('.kc-usercontent.com') } catch { return false } } -const kontentImageLoader = ({ src, width, quality = 100 }) => { +const kontentImageLoader = ({ src, width, quality = 75 }) => { return new transformImageUrl(src) .withWidth(width) .withQuality(quality) diff --git a/examples/cms-kontent/package.json b/examples/cms-kontent/package.json index 81ccbfbfb88d3..79665f418ac6a 100644 --- a/examples/cms-kontent/package.json +++ b/examples/cms-kontent/package.json @@ -19,6 +19,7 @@ "devDependencies": { "autoprefixer": "10.4.7", "postcss": "8.4.14", - "tailwindcss": "^3.0.15" + "tailwindcss": "^3.0.15", + "tslib": "2.4.0" } }