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..4ac04d261749f 100644 --- a/contributing.md +++ b/contributing.md @@ -265,7 +265,17 @@ 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`) +- 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: - Replace `DIRECTORY_NAME` with the directory name you’re adding. - Fill in `Example Name` and `Description`. 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" } } diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index c16884831644c..c0b958017bc3b 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -3,9 +3,11 @@ 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 { createFromFetch } from 'next/dist/compiled/react-server-dom-webpack' +import React from 'react' +import { + createFromFetch, + createFromReadableStream, +} from 'next/dist/compiled/react-server-dom-webpack' /// @@ -36,7 +38,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 +51,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 +66,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 +107,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,27 +146,27 @@ 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 - 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) { + const url = new URL(href, location.origin) + const searchParams = url.searchParams + searchParams.append('__flight__', '1') + + 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({}) 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 = () => {