From cdb0c47455aadcb0eeea4e250a669b4000b90e63 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 20 Jul 2022 17:26:38 -0400 Subject: [PATCH 01/79] Improve `next/image` error message when `src` prop is missing (#38847) Currently, when a user forgets the `src` prop on an image, an error is thrown. However that error doesn't include any of the the user's code in the stack trace, so its nearly impossible to figure out which image is the culprit if there are multiple images on the page. Since this error only throws on demand when a page is visited during `next dev`, we can instead delay the error so it no longer prints on the server but prints on mount instead. At that point, we'll have the `` element via ref and can print it to the web console with the stack trace. - Fixes #23742 --- packages/next/client/future/image.tsx | 240 ++++++++--------- packages/next/client/image.tsx | 247 +++++++++--------- .../base-path/test/index.test.js | 11 +- .../default/test/index.test.js | 11 +- .../image-future/base-path/test/index.test.js | 11 +- .../image-future/default/test/index.test.js | 11 +- 6 files changed, 277 insertions(+), 254 deletions(-) diff --git a/packages/next/client/future/image.tsx b/packages/next/client/future/image.tsx index 1c62dc641e0f3..088c00cc9f80a 100644 --- a/packages/next/client/future/image.tsx +++ b/packages/next/client/future/image.tsx @@ -357,10 +357,6 @@ export default function Image({ } src = typeof src === 'string' ? src : staticSrc - const widthInt = getInt(width) - const heightInt = getInt(height) - const qualityInt = getInt(quality) - let isLazy = !priority && (loading === 'lazy' || typeof loading === 'undefined') if (src.startsWith('data:') || src.startsWith('blob:')) { @@ -373,136 +369,139 @@ export default function Image({ } const [blurComplete, setBlurComplete] = useState(false) + let widthInt = getInt(width) + let heightInt = getInt(height) + const qualityInt = getInt(quality) if (process.env.NODE_ENV !== 'production') { if (!src) { - throw new Error( - `Image is missing required "src" property. Make sure you pass "src" in props to the \`next/image\` component. Received: ${JSON.stringify( - { width, height, quality } - )}` - ) - } - if (typeof widthInt === 'undefined') { - throw new Error( - `Image with src "${src}" is missing required "width" property.` - ) - } else if (isNaN(widthInt)) { - throw new Error( - `Image with src "${src}" has invalid "width" property. Expected a numeric value in pixels but received "${width}".` - ) - } - if (typeof heightInt === 'undefined') { - throw new Error( - `Image with src "${src}" is missing required "height" property.` - ) - } else if (isNaN(heightInt)) { - throw new Error( - `Image with src "${src}" has invalid "height" property. Expected a numeric value in pixels but received "${height}".` - ) - } - if (!VALID_LOADING_VALUES.includes(loading)) { - throw new Error( - `Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map( - String - ).join(',')}.` - ) - } - if (priority && loading === 'lazy') { - throw new Error( - `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` - ) - } - - if ('objectFit' in rest) { - throw new Error( - `Image with src "${src}" has unknown prop "objectFit". This style should be specified using the "style" attribute.` - ) - } - if ('objectPosition' in rest) { - throw new Error( - `Image with src "${src}" has unknown prop "objectPosition". This style should be specified using the "style" attribute.` - ) - } - - if (placeholder === 'blur') { - if ((widthInt || 0) * (heightInt || 0) < 1600) { - warnOnce( - `Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance.` + // React doesn't show the stack trace and there's + // no `src` to help identify which image, so we + // instead console.error(ref) during mount. + unoptimized = true + } else { + if (typeof widthInt === 'undefined') { + throw new Error( + `Image with src "${src}" is missing required "width" property.` + ) + } else if (isNaN(widthInt)) { + throw new Error( + `Image with src "${src}" has invalid "width" property. Expected a numeric value in pixels but received "${width}".` + ) + } + if (typeof heightInt === 'undefined') { + throw new Error( + `Image with src "${src}" is missing required "height" property.` + ) + } else if (isNaN(heightInt)) { + throw new Error( + `Image with src "${src}" has invalid "height" property. Expected a numeric value in pixels but received "${height}".` + ) + } + if (!VALID_LOADING_VALUES.includes(loading)) { + throw new Error( + `Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map( + String + ).join(',')}.` + ) + } + if (priority && loading === 'lazy') { + throw new Error( + `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` ) } - if (!blurDataURL) { - const VALID_BLUR_EXT = ['jpeg', 'png', 'webp', 'avif'] // should match next-image-loader + if ('objectFit' in rest) { throw new Error( - `Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property. - Possible solutions: - - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image - - Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join( - ',' - )} - - Remove the "placeholder" property, effectively no blur effect - Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url` + `Image with src "${src}" has unknown prop "objectFit". This style should be specified using the "style" attribute.` + ) + } + if ('objectPosition' in rest) { + throw new Error( + `Image with src "${src}" has unknown prop "objectPosition". This style should be specified using the "style" attribute.` ) } - } - if ('ref' in rest) { - warnOnce( - `Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.` - ) - } - if (!unoptimized && loader !== defaultLoader) { - const urlStr = loader({ - config, - src, - width: widthInt || 400, - quality: qualityInt || 75, - }) - let url: URL | undefined - try { - url = new URL(urlStr) - } catch (err) {} - if (urlStr === src || (url && url.pathname === src && !url.search)) { + if (placeholder === 'blur') { + if ((widthInt || 0) * (heightInt || 0) < 1600) { + warnOnce( + `Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance.` + ) + } + if (!blurDataURL) { + const VALID_BLUR_EXT = ['jpeg', 'png', 'webp', 'avif'] // should match next-image-loader + + throw new Error( + `Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property. + Possible solutions: + - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image + - Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join( + ',' + )} + - Remove the "placeholder" property, effectively no blur effect + Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url` + ) + } + } + if ('ref' in rest) { warnOnce( - `Image with src "${src}" has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead.` + - `\nRead more: https://nextjs.org/docs/messages/next-image-missing-loader-width` + `Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.` ) } - } - if ( - typeof window !== 'undefined' && - !perfObserver && - window.PerformanceObserver - ) { - perfObserver = new PerformanceObserver((entryList) => { - for (const entry of entryList.getEntries()) { - // @ts-ignore - missing "LargestContentfulPaint" class with "element" prop - const imgSrc = entry?.element?.src || '' - const lcpImage = allImgs.get(imgSrc) - if ( - lcpImage && - !lcpImage.priority && - lcpImage.placeholder !== 'blur' && - !lcpImage.src.startsWith('data:') && - !lcpImage.src.startsWith('blob:') - ) { - // https://web.dev/lcp/#measure-lcp-in-javascript - warnOnce( - `Image with src "${lcpImage.src}" was detected as the Largest Contentful Paint (LCP). Please add the "priority" property if this image is above the fold.` + - `\nRead more: https://nextjs.org/docs/api-reference/next/image#priority` - ) - } + if (!unoptimized && loader !== defaultLoader) { + const urlStr = loader({ + config, + src, + width: widthInt || 400, + quality: qualityInt || 75, + }) + let url: URL | undefined + try { + url = new URL(urlStr) + } catch (err) {} + if (urlStr === src || (url && url.pathname === src && !url.search)) { + warnOnce( + `Image with src "${src}" has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead.` + + `\nRead more: https://nextjs.org/docs/messages/next-image-missing-loader-width` + ) } - }) - try { - perfObserver.observe({ - type: 'largest-contentful-paint', - buffered: true, + } + + if ( + typeof window !== 'undefined' && + !perfObserver && + window.PerformanceObserver + ) { + perfObserver = new PerformanceObserver((entryList) => { + for (const entry of entryList.getEntries()) { + // @ts-ignore - missing "LargestContentfulPaint" class with "element" prop + const imgSrc = entry?.element?.src || '' + const lcpImage = allImgs.get(imgSrc) + if ( + lcpImage && + !lcpImage.priority && + lcpImage.placeholder !== 'blur' && + !lcpImage.src.startsWith('data:') && + !lcpImage.src.startsWith('blob:') + ) { + // https://web.dev/lcp/#measure-lcp-in-javascript + warnOnce( + `Image with src "${lcpImage.src}" was detected as the Largest Contentful Paint (LCP). Please add the "priority" property if this image is above the fold.` + + `\nRead more: https://nextjs.org/docs/api-reference/next/image#priority` + ) + } + } }) - } catch (err) { - // Log error but don't crash the app - console.error(err) + try { + perfObserver.observe({ + type: 'largest-contentful-paint', + buffered: true, + }) + } catch (err) { + // Log error but don't crash the app + console.error(err) + } } } } @@ -652,6 +651,11 @@ const ImageElement = ({ style={{ ...imgStyle, ...blurStyle }} ref={useCallback( (img: ImgElementWithDataProp) => { + if (process.env.NODE_ENV !== 'production') { + if (img && !srcString) { + console.error(`Image is missing required "src" property:`, img) + } + } if (img?.complete) { handleLoading( img, diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index a80db3fc71529..d3eeaa233d6e5 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -429,10 +429,6 @@ export default function Image({ } src = typeof src === 'string' ? src : staticSrc - const widthInt = getInt(width) - const heightInt = getInt(height) - const qualityInt = getInt(quality) - let isLazy = !priority && (loading === 'lazy' || typeof loading === 'undefined') if (src.startsWith('data:') || src.startsWith('blob:')) { @@ -505,68 +501,73 @@ export default function Image({ objectPosition, } + let widthInt = getInt(width) + let heightInt = getInt(height) + const qualityInt = getInt(quality) + if (process.env.NODE_ENV !== 'production') { if (!src) { - throw new Error( - `Image is missing required "src" property. Make sure you pass "src" in props to the \`next/image\` component. Received: ${JSON.stringify( - { width, height, quality } - )}` - ) - } - if (!VALID_LAYOUT_VALUES.includes(layout)) { - if ((layout as any) === 'raw') { + // React doesn't show the stack trace and there's + // no `src` to help identify which image, so we + // instead console.error(ref) during mount. + widthInt = widthInt || 1 + heightInt = heightInt || 1 + unoptimized = true + } else { + if (!VALID_LAYOUT_VALUES.includes(layout)) { + if ((layout as any) === 'raw') { + throw new Error( + `The layout="raw" experiment has been moved to a new module. Please import \`next/future/image\` instead.` + ) + } throw new Error( - `The layout="raw" experiment has been moved to a new module. Please import \`next/future/image\` instead.` + `Image with src "${src}" has invalid "layout" property. Provided "${layout}" should be one of ${VALID_LAYOUT_VALUES.map( + String + ).join(',')}.` ) } - throw new Error( - `Image with src "${src}" has invalid "layout" property. Provided "${layout}" should be one of ${VALID_LAYOUT_VALUES.map( - String - ).join(',')}.` - ) - } - if ( - (typeof widthInt !== 'undefined' && isNaN(widthInt)) || - (typeof heightInt !== 'undefined' && isNaN(heightInt)) - ) { - throw new Error( - `Image with src "${src}" has invalid "width" or "height" property. These should be numeric values.` - ) - } - if (layout === 'fill' && (width || height)) { - warnOnce( - `Image with src "${src}" and "layout='fill'" has unused properties assigned. Please remove "width" and "height".` - ) - } - if (!VALID_LOADING_VALUES.includes(loading)) { - throw new Error( - `Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map( - String - ).join(',')}.` - ) - } - if (priority && loading === 'lazy') { - throw new Error( - `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` - ) - } - if (sizes && layout !== 'fill' && layout !== 'responsive') { - warnOnce( - `Image with src "${src}" has "sizes" property but it will be ignored. Only use "sizes" with "layout='fill'" or "layout='responsive'"` - ) - } - if (placeholder === 'blur') { - if (layout !== 'fill' && (widthInt || 0) * (heightInt || 0) < 1600) { + if ( + (typeof widthInt !== 'undefined' && isNaN(widthInt)) || + (typeof heightInt !== 'undefined' && isNaN(heightInt)) + ) { + throw new Error( + `Image with src "${src}" has invalid "width" or "height" property. These should be numeric values.` + ) + } + if (layout === 'fill' && (width || height)) { warnOnce( - `Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance.` + `Image with src "${src}" and "layout='fill'" has unused properties assigned. Please remove "width" and "height".` ) } - if (!blurDataURL) { - const VALID_BLUR_EXT = ['jpeg', 'png', 'webp', 'avif'] // should match next-image-loader - + if (!VALID_LOADING_VALUES.includes(loading)) { + throw new Error( + `Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map( + String + ).join(',')}.` + ) + } + if (priority && loading === 'lazy') { throw new Error( - `Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property. + `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` + ) + } + if (sizes && layout !== 'fill' && layout !== 'responsive') { + warnOnce( + `Image with src "${src}" has "sizes" property but it will be ignored. Only use "sizes" with "layout='fill'" or "layout='responsive'"` + ) + } + if (placeholder === 'blur') { + if (layout !== 'fill' && (widthInt || 0) * (heightInt || 0) < 1600) { + warnOnce( + `Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance.` + ) + } + if (!blurDataURL) { + const VALID_BLUR_EXT = ['jpeg', 'png', 'webp', 'avif'] // should match next-image-loader + + throw new Error( + `Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property. Possible solutions: - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image - Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join( @@ -574,80 +575,81 @@ export default function Image({ )} - Remove the "placeholder" property, effectively no blur effect Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url` - ) + ) + } } - } - if ('ref' in rest) { - warnOnce( - `Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.` - ) - } - - if (!unoptimized && loader !== defaultImageLoader) { - const urlStr = loader({ - config, - src, - width: widthInt || 400, - quality: qualityInt || 75, - }) - let url: URL | undefined - try { - url = new URL(urlStr) - } catch (err) {} - if (urlStr === src || (url && url.pathname === src && !url.search)) { + if ('ref' in rest) { warnOnce( - `Image with src "${src}" has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead.` + - `\nRead more: https://nextjs.org/docs/messages/next-image-missing-loader-width` + `Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.` ) } - } - if (style) { - let overwrittenStyles = Object.keys(style).filter( - (key) => key in layoutStyle - ) - if (overwrittenStyles.length) { - warnOnce( - `Image with src ${src} is assigned the following styles, which are overwritten by automatically-generated styles: ${overwrittenStyles.join( - ', ' - )}` + if (!unoptimized && loader !== defaultImageLoader) { + const urlStr = loader({ + config, + src, + width: widthInt || 400, + quality: qualityInt || 75, + }) + let url: URL | undefined + try { + url = new URL(urlStr) + } catch (err) {} + if (urlStr === src || (url && url.pathname === src && !url.search)) { + warnOnce( + `Image with src "${src}" has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead.` + + `\nRead more: https://nextjs.org/docs/messages/next-image-missing-loader-width` + ) + } + } + + if (style) { + let overwrittenStyles = Object.keys(style).filter( + (key) => key in layoutStyle ) + if (overwrittenStyles.length) { + warnOnce( + `Image with src ${src} is assigned the following styles, which are overwritten by automatically-generated styles: ${overwrittenStyles.join( + ', ' + )}` + ) + } } - } - if ( - typeof window !== 'undefined' && - !perfObserver && - window.PerformanceObserver - ) { - perfObserver = new PerformanceObserver((entryList) => { - for (const entry of entryList.getEntries()) { - // @ts-ignore - missing "LargestContentfulPaint" class with "element" prop - const imgSrc = entry?.element?.src || '' - const lcpImage = allImgs.get(imgSrc) - if ( - lcpImage && - !lcpImage.priority && - lcpImage.placeholder !== 'blur' && - !lcpImage.src.startsWith('data:') && - !lcpImage.src.startsWith('blob:') - ) { - // https://web.dev/lcp/#measure-lcp-in-javascript - warnOnce( - `Image with src "${lcpImage.src}" was detected as the Largest Contentful Paint (LCP). Please add the "priority" property if this image is above the fold.` + - `\nRead more: https://nextjs.org/docs/api-reference/next/image#priority` - ) + if ( + typeof window !== 'undefined' && + !perfObserver && + window.PerformanceObserver + ) { + perfObserver = new PerformanceObserver((entryList) => { + for (const entry of entryList.getEntries()) { + // @ts-ignore - missing "LargestContentfulPaint" class with "element" prop + const imgSrc = entry?.element?.src || '' + const lcpImage = allImgs.get(imgSrc) + if ( + lcpImage && + !lcpImage.priority && + lcpImage.placeholder !== 'blur' && + !lcpImage.src.startsWith('data:') && + !lcpImage.src.startsWith('blob:') + ) { + // https://web.dev/lcp/#measure-lcp-in-javascript + warnOnce( + `Image with src "${lcpImage.src}" was detected as the Largest Contentful Paint (LCP). Please add the "priority" property if this image is above the fold.` + + `\nRead more: https://nextjs.org/docs/api-reference/next/image#priority` + ) + } } - } - }) - try { - perfObserver.observe({ - type: 'largest-contentful-paint', - buffered: true, }) - } catch (err) { - // Log error but don't crash the app - console.error(err) + try { + perfObserver.observe({ + type: 'largest-contentful-paint', + buffered: true, + }) + } catch (err) { + // Log error but don't crash the app + console.error(err) + } } } } @@ -881,6 +883,11 @@ const ImageElement = ({ style={{ ...imgStyle, ...blurStyle }} ref={useCallback( (img: ImgElementWithDataProp) => { + if (process.env.NODE_ENV !== 'production') { + if (img && !srcString) { + console.error(`Image is missing required "src" property:`, img) + } + } setIntersection(img) if (img?.complete) { handleLoading( diff --git a/test/integration/image-component/base-path/test/index.test.js b/test/integration/image-component/base-path/test/index.test.js index 104ec48df4207..c26a714fde1bc 100644 --- a/test/integration/image-component/base-path/test/index.test.js +++ b/test/integration/image-component/base-path/test/index.test.js @@ -386,10 +386,13 @@ function runTests(mode) { it('should show missing src error', async () => { const browser = await webdriver(appPort, '/docs/missing-src') - expect(await hasRedbox(browser)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Image is missing required "src" property. Make sure you pass "src" in props to the `next/image` component. Received: {"width":200}' - ) + expect(await hasRedbox(browser)).toBe(false) + + await check(async () => { + return (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + }, /Image is missing required "src" property/gm) }) it('should show invalid src error', async () => { diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 8115d398f1bc5..4401c4fff9334 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -746,10 +746,13 @@ function runTests(mode) { it('should show missing src error', async () => { const browser = await webdriver(appPort, '/missing-src') - expect(await hasRedbox(browser)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Image is missing required "src" property. Make sure you pass "src" in props to the `next/image` component. Received: {"width":200}' - ) + expect(await hasRedbox(browser)).toBe(false) + + await check(async () => { + return (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + }, /Image is missing required "src" property/gm) }) it('should show invalid src error', async () => { diff --git a/test/integration/image-future/base-path/test/index.test.js b/test/integration/image-future/base-path/test/index.test.js index b394442d11962..0fce6379f5aa3 100644 --- a/test/integration/image-future/base-path/test/index.test.js +++ b/test/integration/image-future/base-path/test/index.test.js @@ -130,10 +130,13 @@ function runTests(mode) { it('should show missing src error', async () => { const browser = await webdriver(appPort, '/docs/missing-src') - expect(await hasRedbox(browser)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Image is missing required "src" property. Make sure you pass "src" in props to the `next/image` component. Received: {"width":200}' - ) + expect(await hasRedbox(browser)).toBe(false) + + await check(async () => { + return (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + }, /Image is missing required "src" property/gm) }) it('should show invalid src error', async () => { diff --git a/test/integration/image-future/default/test/index.test.js b/test/integration/image-future/default/test/index.test.js index 3ebb205e4c43b..e02c5b25a1c5f 100644 --- a/test/integration/image-future/default/test/index.test.js +++ b/test/integration/image-future/default/test/index.test.js @@ -605,10 +605,13 @@ function runTests(mode) { it('should show missing src error', async () => { const browser = await webdriver(appPort, '/missing-src') - expect(await hasRedbox(browser)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Image is missing required "src" property. Make sure you pass "src" in props to the `next/image` component. Received: {"width":200}' - ) + expect(await hasRedbox(browser)).toBe(false) + + await check(async () => { + return (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + }, /Image is missing required "src" property/gm) }) it('should show invalid src error', async () => { From ea9f8552531aa95ae0546096e12a65495e5c5274 Mon Sep 17 00:00:00 2001 From: Maia Teegarden Date: Wed, 20 Jul 2022 14:52:31 -0700 Subject: [PATCH 02/79] v12.2.3-canary.15 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 16 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lerna.json b/lerna.json index 7ded1982e379d..5b5a548e794c3 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.2.3-canary.14" + "version": "12.2.3-canary.15" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index addf7281b72e5..b9aee88071ec3 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 9fc9c94b2e2e3..6db04602767f9 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "12.2.3-canary.14", + "@next/eslint-plugin-next": "12.2.3-canary.15", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.21.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 199489d868fcf..0d2523b0c986c 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index e93a55c6f5041..5a9d517ed6faf 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index d6bb46600e82a..f3da911cb9b30 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index fcaf635db4926..af0a0c28d5d17 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 16f66ce7edfc7..4a3253779cfc6 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index f1da7d1984980..bfb6877912946 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index b0db8a27b1e37..d082bac0a869a 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 4006ddbff7b93..7d8d8880c2126 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index b3394a2f53585..62bba314ce3e9 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi native --features plugin", diff --git a/packages/next/package.json b/packages/next/package.json index 83c6150d4fb0b..c0964bb040f72 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -72,7 +72,7 @@ ] }, "dependencies": { - "@next/env": "12.2.3-canary.14", + "@next/env": "12.2.3-canary.15", "@swc/helpers": "0.4.3", "caniuse-lite": "^1.0.30001332", "postcss": "8.4.14", @@ -123,11 +123,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.4.4", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "12.2.3-canary.14", - "@next/polyfill-nomodule": "12.2.3-canary.14", - "@next/react-dev-overlay": "12.2.3-canary.14", - "@next/react-refresh-utils": "12.2.3-canary.14", - "@next/swc": "12.2.3-canary.14", + "@next/polyfill-module": "12.2.3-canary.15", + "@next/polyfill-nomodule": "12.2.3-canary.15", + "@next/react-dev-overlay": "12.2.3-canary.15", + "@next/react-refresh-utils": "12.2.3-canary.15", + "@next/swc": "12.2.3-canary.15", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", "@taskr/watch": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 6eb21c8f13ce6..4e1cd22d716b8 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index b1e9c5b102869..fd3d6b844744e 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "12.2.3-canary.14", + "version": "12.2.3-canary.15", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 981ea82f7ac37..4b9b96e8566c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -364,7 +364,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 12.2.3-canary.14 + '@next/eslint-plugin-next': 12.2.3-canary.15 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.21.0 eslint-import-resolver-node: ^0.3.6 @@ -420,12 +420,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.4.4 '@napi-rs/triples': 1.1.0 - '@next/env': 12.2.3-canary.14 - '@next/polyfill-module': 12.2.3-canary.14 - '@next/polyfill-nomodule': 12.2.3-canary.14 - '@next/react-dev-overlay': 12.2.3-canary.14 - '@next/react-refresh-utils': 12.2.3-canary.14 - '@next/swc': 12.2.3-canary.14 + '@next/env': 12.2.3-canary.15 + '@next/polyfill-module': 12.2.3-canary.15 + '@next/polyfill-nomodule': 12.2.3-canary.15 + '@next/react-dev-overlay': 12.2.3-canary.15 + '@next/react-refresh-utils': 12.2.3-canary.15 + '@next/swc': 12.2.3-canary.15 '@swc/helpers': 0.4.3 '@taskr/clear': 1.1.0 '@taskr/esnext': 1.1.0 @@ -21339,7 +21339,7 @@ packages: typescript: 4.6.3 /tty-browserify/0.0.0: - resolution: {integrity: sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=} + resolution: {integrity: sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==} dev: true /tty-browserify/0.0.1: From f086283e384c6665c4eda3ad772892cb3e2efcf4 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 21 Jul 2022 00:41:48 +0200 Subject: [PATCH 03/79] Remove the next/streaming endpoint and experimental refresh api (#38837) Drop the `next/streaming` endpoint. Currently it only holds the only one experimental api and we'd like to deprecate it in favor of oher future APIs for app dir --- docs/advanced-features/react-18/overview.md | 1 - docs/advanced-features/react-18/streaming.md | 4 +- docs/api-reference/next/streaming.md | 49 -------------------- docs/manifest.json | 6 ++- packages/next/client/index.tsx | 27 +---------- packages/next/client/streaming/index.ts | 1 - packages/next/client/streaming/refresh.ts | 7 --- packages/next/package.json | 2 - packages/next/streaming.d.ts | 1 - packages/next/streaming.js | 1 - 10 files changed, 7 insertions(+), 92 deletions(-) delete mode 100644 docs/api-reference/next/streaming.md delete mode 100644 packages/next/client/streaming/index.ts delete mode 100644 packages/next/client/streaming/refresh.ts delete mode 100644 packages/next/streaming.d.ts delete mode 100644 packages/next/streaming.js diff --git a/docs/advanced-features/react-18/overview.md b/docs/advanced-features/react-18/overview.md index f43b46baa05f3..eeb920e3bc1d8 100644 --- a/docs/advanced-features/react-18/overview.md +++ b/docs/advanced-features/react-18/overview.md @@ -1,7 +1,6 @@ # React 18 [React 18](https://reactjs.org/blog/2022/03/29/react-v18.html) adds new features including Suspense, automatic batching of updates, APIs like `startTransition`, and a new streaming API for server rendering with support for `React.lazy`. -Next.js also provides streaming related APIs, please checkout [next/streaming](/docs/api-reference/next/streaming.md) for details. React 18 is now released. Read more about [React 18](https://reactjs.org/blog/2022/03/29/react-v18.html). diff --git a/docs/advanced-features/react-18/streaming.md b/docs/advanced-features/react-18/streaming.md index f18fc2515da77..e64d1eb09eb4f 100644 --- a/docs/advanced-features/react-18/streaming.md +++ b/docs/advanced-features/react-18/streaming.md @@ -2,8 +2,6 @@ React 18 includes architectural improvements to React server-side rendering (SSR) performance. This means you can use `Suspense` in your React components in streaming SSR mode and React will render content on the server and send updates through HTTP streams. -React Server Components, an experimental feature, is based on streaming. You can read more about Server Components related streaming APIs in [`next/streaming`](/docs/api-reference/next/streaming.md). However, this guide focuses on streaming with React 18. - ## Using Streaming Server-Rendering When you use Suspense in a server-rendered page, there is no extra configuration required to use streaming SSR. When deployed, streaming can be utilized through infrastructure like [Edge Functions](https://vercel.com/edge) on Vercel (with the Edge Runtime) or with a Node.js server (with the Node.js runtime). AWS Lambda Functions do not currently support streaming responses. @@ -12,7 +10,7 @@ All SSR pages have the ability to render components into streams and the client As an added bonus, in streaming SSR mode the client will also use selective hydration to prioritize component hydration based on user interactions, further improving performance. -For non-SSR pages, all Suspense boundaries will still be [statically optimized](/docs/advanced-features/automatic-static-optimization.md). Check out [`next/streaming`](/docs/api-reference/next/streaming.md) for the API reference for streaming SSR. +For non-SSR pages, all Suspense boundaries will still be [statically optimized](/docs/advanced-features/automatic-static-optimization.md). ## Streaming Features diff --git a/docs/api-reference/next/streaming.md b/docs/api-reference/next/streaming.md deleted file mode 100644 index 86a6ade3c4fd9..0000000000000 --- a/docs/api-reference/next/streaming.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -description: Streaming related APIs to build Next.js apps in streaming SSR or with React Server Components. ---- - -# next/streaming - -The experimental `next/streaming` module provides streaming related APIs to port the existing functionality of Next.js apps to streaming scenarios and facilitate the usage of React Server Components. - -## unstable_useRefreshRoot - -Since Server Components are rendered on the server-side, in some cases you might need to partially refresh content from the server. - -For example, a search bar (client component) which displays search results as server components. You'd want to update the search results while typing and rerender the results list with a certain frequency (e.g. with each keystroke or on a debounce). - -The `unstable_useRefreshRoot` hook returns a `refresh` API to let you re-render the React tree smoothly without flickering. This is only allowed for use on the client-side and will only affect Server Components at the moment. - -```jsx -// pages/index.server.js -import Search from '../components/search.client.js' -import SearchResults from '../components/search-results.server.js' - -function Home() { - return ( -
- - -
- ) -} -``` - -```jsx -// components/search.client.js -import { unstable_useRefreshRoot as useRefreshRoot } from 'next/streaming' - -export default function Search() { - const refresh = useRefreshRoot() - - return ( - { - refresh() - // Or refresh with updated props: - // refresh(nextProps) - }} - /> - ) -} -``` diff --git a/docs/manifest.json b/docs/manifest.json index a4a5955c1d145..5f890a704c79c 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -414,8 +414,10 @@ "path": "/docs/api-reference/next/server.md" }, { - "title": "next/streaming", - "path": "/docs/api-reference/next/streaming.md" + "path": "/docs/api-reference/next/streaming", + "redirect": { + "destination": "/docs/advanced-features/react-18" + } }, { "title": "next/future/image (experimental)", diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 3c63fac814671..5c4f482da59bb 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -1,6 +1,6 @@ /* global location */ import '../build/polyfills/polyfill-module' -import React, { useState } from 'react' +import React from 'react' import { HeadManagerContext } from '../shared/lib/head-manager-context' import mitt, { MittEmitter } from '../shared/lib/mitt' import { RouterContext } from '../shared/lib/router-context' @@ -678,8 +678,6 @@ if (process.env.__NEXT_RSC) { createFromFetch, createFromReadableStream, } = require('next/dist/compiled/react-server-dom-webpack') - const { RefreshContext } = require('./streaming/refresh') - const encoder = new TextEncoder() let initialServerDataBuffer: string[] | undefined = undefined @@ -808,28 +806,7 @@ if (process.env.__NEXT_RSC) { RSCComponent = (props: any) => { const cacheKey = getCacheKey() const { __flight__ } = props - const [, dispatch] = useState({}) - const startTransition = (React as any).startTransition - 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 } } diff --git a/packages/next/client/streaming/index.ts b/packages/next/client/streaming/index.ts deleted file mode 100644 index 566fab06ab74b..0000000000000 --- a/packages/next/client/streaming/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useRefreshRoot as unstable_useRefreshRoot } from './refresh' diff --git a/packages/next/client/streaming/refresh.ts b/packages/next/client/streaming/refresh.ts deleted file mode 100644 index c79d3c21ecd32..0000000000000 --- a/packages/next/client/streaming/refresh.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createContext, useContext } from 'react' - -export const RefreshContext = createContext((_props?: any) => {}) - -export function useRefreshRoot() { - return useContext(RefreshContext) -} diff --git a/packages/next/package.json b/packages/next/package.json index c0964bb040f72..12636093bc466 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -45,8 +45,6 @@ "jest.d.ts", "amp.js", "amp.d.ts", - "streaming.js", - "streaming.d.ts", "index.d.ts", "types/index.d.ts", "types/global.d.ts", diff --git a/packages/next/streaming.d.ts b/packages/next/streaming.d.ts deleted file mode 100644 index c0cc6709ecb1a..0000000000000 --- a/packages/next/streaming.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dist/client/streaming' diff --git a/packages/next/streaming.js b/packages/next/streaming.js deleted file mode 100644 index 2d8b5c4f47e28..0000000000000 --- a/packages/next/streaming.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/client/streaming') From 8fe44c31020781efa57ac6d223fdef01c6dd8099 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 20 Jul 2022 18:52:26 -0500 Subject: [PATCH 04/79] Update edge blob asset e2e test (#38857) --- test/e2e/edge-compiler-can-import-blob-assets/index.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/e2e/edge-compiler-can-import-blob-assets/index.test.ts b/test/e2e/edge-compiler-can-import-blob-assets/index.test.ts index 2e9668d185e0c..efe222b49943a 100644 --- a/test/e2e/edge-compiler-can-import-blob-assets/index.test.ts +++ b/test/e2e/edge-compiler-can-import-blob-assets/index.test.ts @@ -9,6 +9,12 @@ import type { MiddlewareManifest } from 'next/build/webpack/plugins/middleware-p describe('Edge Compiler can import asset assets', () => { let next: NextInstance + // TODO: remove after this is supported for deploy + if ((global as any).isNextDeploy) { + it('should skip for deploy for now', () => {}) + return + } + beforeAll(async () => { next = await createNext({ files: new FileRef(path.join(__dirname, './app')), From 420d7850be578f0f74b3c8f0ac988c455174756f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 20 Jul 2022 16:55:45 -0700 Subject: [PATCH 05/79] Update Convex example. (#38850) --- examples/convex/README.md | 18 ++++++++++++--- examples/convex/convex/README.md | 38 +++++++++----------------------- examples/convex/package.json | 2 +- examples/convex/pages/_app.tsx | 2 +- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/examples/convex/README.md b/examples/convex/README.md index 1daf2d81e67c2..38c329b0e3e01 100644 --- a/examples/convex/README.md +++ b/examples/convex/README.md @@ -20,16 +20,28 @@ yarn create next-app --example with-convex with-convex-app pnpm create next-app --example with-convex with-convex-app ``` -After creating a [Convex account](https://www.convex.dev) and getting a beta key, +Log in to Convex, ```bash -npx convex init --beta-key +npx convex login ``` -Next, push the Convex functions for this project. +initialize a new Convex project, + +```bash +npx convex init +``` + +and push the Convex functions for this project. ```bash npx convex push ``` +Now you can run your code locally with a Convex backend with + +```bash +npm run dev +``` + Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/convex/convex/README.md b/examples/convex/convex/README.md index 639e0a21bbd88..e4bfbb0a59379 100644 --- a/examples/convex/convex/README.md +++ b/examples/convex/convex/README.md @@ -1,45 +1,29 @@ -# Welcome to your functions directory +# Welcome to your Convex functions directory! -Write your convex functions in this directory. +Write your Convex functions here. -A query function (how you read data) looks like this: +A query function looks like: ```typescript -// getCounter.ts +// myQueryFunction.ts import { query } from './_generated/server' -export default query(async ({ db }): Promise => { - const counterDoc = await db.table('counter_table').first() - console.log('Got stuff') - if (counterDoc === null) { - return 0 - } - return counterDoc.counter +export default query(async ({ db }) => { + // Load data with `db` here! }) ``` -A mutation function (how you write data) looks like this: +A mutation function looks like: ```typescript -// incrementCounter.ts +// myMutationFunction.ts import { mutation } from './_generated/server' -export default mutation(async ({ db }, increment: number) => { - let counterDoc = await db.table('counter_table').first() - if (counterDoc === null) { - counterDoc = { - counter: increment, - } - db.insert('counter_table', counterDoc) - } else { - counterDoc.counter += increment - db.replace(counterDoc._id, counterDoc) - } - // Like console.log but relays log messages from the server to client. - console.log(`Value of counter is now ${counterDoc.counter}`) +export default mutation(async ({ db }) => { + // Edit data with `db` here! }) ``` -The convex cli is your friend. See everything it can do by running +The Convex CLI is your friend. See everything it can do by running `npx convex -h` in your project root directory. To learn more, launch the docs with `npx convex docs`. diff --git a/examples/convex/package.json b/examples/convex/package.json index d21a26b138c20..daee8a99275a3 100644 --- a/examples/convex/package.json +++ b/examples/convex/package.json @@ -9,7 +9,7 @@ "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0", - "convex-dev": "0.1.4" + "convex": "^0.1.6" }, "devDependencies": { "@types/node": "~16.11.12", diff --git a/examples/convex/pages/_app.tsx b/examples/convex/pages/_app.tsx index da837f4c99c19..b6575f9c6346d 100644 --- a/examples/convex/pages/_app.tsx +++ b/examples/convex/pages/_app.tsx @@ -1,7 +1,7 @@ import '../styles/globals.css' import type { AppProps } from 'next/app' -import { ConvexProvider, ConvexReactClient } from 'convex-dev/react' +import { ConvexProvider, ConvexReactClient } from 'convex/react' import convexConfig from '../convex.json' const convex = new ConvexReactClient(convexConfig.origin) From adf40a6fceec84f40ef0040c913b78b8ea083b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 21 Jul 2022 04:22:58 +0200 Subject: [PATCH 06/79] chore: convert `with-dynamic-import` to TypeScript (#38844) Closes #38806 ## 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 `pnpm lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- examples/with-dynamic-import/.gitignore | 3 +++ .../components/{Header.js => Header.tsx} | 10 ++-------- .../components/{hello1.js => hello1.tsx} | 0 .../components/{hello2.js => hello2.tsx} | 0 .../components/{hello3.js => hello3.tsx} | 0 .../components/{hello4.js => hello4.tsx} | 0 .../components/{hello5.js => hello5.tsx} | 0 examples/with-dynamic-import/next-env.d.ts | 5 +++++ examples/with-dynamic-import/package.json | 12 +++++++---- examples/with-dynamic-import/pages/about.js | 10 ---------- .../pages/{index.js => index.tsx} | 0 examples/with-dynamic-import/tsconfig.json | 20 +++++++++++++++++++ 12 files changed, 38 insertions(+), 22 deletions(-) rename examples/with-dynamic-import/components/{Header.js => Header.tsx} (58%) rename examples/with-dynamic-import/components/{hello1.js => hello1.tsx} (100%) rename examples/with-dynamic-import/components/{hello2.js => hello2.tsx} (100%) rename examples/with-dynamic-import/components/{hello3.js => hello3.tsx} (100%) rename examples/with-dynamic-import/components/{hello4.js => hello4.tsx} (100%) rename examples/with-dynamic-import/components/{hello5.js => hello5.tsx} (100%) create mode 100644 examples/with-dynamic-import/next-env.d.ts delete mode 100644 examples/with-dynamic-import/pages/about.js rename examples/with-dynamic-import/pages/{index.js => index.tsx} (100%) create mode 100644 examples/with-dynamic-import/tsconfig.json diff --git a/examples/with-dynamic-import/.gitignore b/examples/with-dynamic-import/.gitignore index 1437c53f70bc2..ab8b66ddcd18b 100644 --- a/examples/with-dynamic-import/.gitignore +++ b/examples/with-dynamic-import/.gitignore @@ -32,3 +32,6 @@ yarn-error.log* # vercel .vercel + +# TypeScript +*.tsbuildinfo \ No newline at end of file diff --git a/examples/with-dynamic-import/components/Header.js b/examples/with-dynamic-import/components/Header.tsx similarity index 58% rename from examples/with-dynamic-import/components/Header.js rename to examples/with-dynamic-import/components/Header.tsx index ae4bfb4752436..1f4841f34282c 100644 --- a/examples/with-dynamic-import/components/Header.js +++ b/examples/with-dynamic-import/components/Header.tsx @@ -4,18 +4,12 @@ export default function Header() { return (
- Home + Home - About + About
) } - -const styles = { - a: { - marginRight: 10, - }, -} diff --git a/examples/with-dynamic-import/components/hello1.js b/examples/with-dynamic-import/components/hello1.tsx similarity index 100% rename from examples/with-dynamic-import/components/hello1.js rename to examples/with-dynamic-import/components/hello1.tsx diff --git a/examples/with-dynamic-import/components/hello2.js b/examples/with-dynamic-import/components/hello2.tsx similarity index 100% rename from examples/with-dynamic-import/components/hello2.js rename to examples/with-dynamic-import/components/hello2.tsx diff --git a/examples/with-dynamic-import/components/hello3.js b/examples/with-dynamic-import/components/hello3.tsx similarity index 100% rename from examples/with-dynamic-import/components/hello3.js rename to examples/with-dynamic-import/components/hello3.tsx diff --git a/examples/with-dynamic-import/components/hello4.js b/examples/with-dynamic-import/components/hello4.tsx similarity index 100% rename from examples/with-dynamic-import/components/hello4.js rename to examples/with-dynamic-import/components/hello4.tsx diff --git a/examples/with-dynamic-import/components/hello5.js b/examples/with-dynamic-import/components/hello5.tsx similarity index 100% rename from examples/with-dynamic-import/components/hello5.js rename to examples/with-dynamic-import/components/hello5.tsx diff --git a/examples/with-dynamic-import/next-env.d.ts b/examples/with-dynamic-import/next-env.d.ts new file mode 100644 index 0000000000000..4f11a03dc6cc3 --- /dev/null +++ b/examples/with-dynamic-import/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/with-dynamic-import/package.json b/examples/with-dynamic-import/package.json index 077ab3ccf98a9..db6ba2c492aaf 100644 --- a/examples/with-dynamic-import/package.json +++ b/examples/with-dynamic-import/package.json @@ -3,13 +3,17 @@ "scripts": { "dev": "next", "build": "next build", - "export": "next export", "start": "next start" }, "dependencies": { - "fuse.js": "6.4.1", + "fuse.js": "6.6.2", "next": "latest", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^18", + "@types/react": "^18", + "typescript": "^4.7.4" } } diff --git a/examples/with-dynamic-import/pages/about.js b/examples/with-dynamic-import/pages/about.js deleted file mode 100644 index 0434e5e05a986..0000000000000 --- a/examples/with-dynamic-import/pages/about.js +++ /dev/null @@ -1,10 +0,0 @@ -import Header from '../components/Header' - -const About = () => ( -
-
-

This is the about page.

-
-) - -export default About diff --git a/examples/with-dynamic-import/pages/index.js b/examples/with-dynamic-import/pages/index.tsx similarity index 100% rename from examples/with-dynamic-import/pages/index.js rename to examples/with-dynamic-import/pages/index.tsx diff --git a/examples/with-dynamic-import/tsconfig.json b/examples/with-dynamic-import/tsconfig.json new file mode 100644 index 0000000000000..b8d597880a1ae --- /dev/null +++ b/examples/with-dynamic-import/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} From 5752d4a66833ea5616039d4405720643f5d6418c Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 21 Jul 2022 11:11:01 +0200 Subject: [PATCH 07/79] Ensure module require is awaited in app-render (#38860) Fixes a bug where `import { nanoid } from 'nanoid'` broke the tests. ## 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 `pnpm lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) --- packages/next/server/app-render.tsx | 126 +++++++++++++++------------- 1 file changed, 70 insertions(+), 56 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 1af4421f72f1f..aa97b3551fd64 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -538,7 +538,7 @@ export async function renderToHTML( return segmentTree } - const createComponentTree = ({ + const createComponentTree = async ({ createSegmentPath, tree: [segment, parallelRoutes, { layout, loading, page }], parentParams, @@ -550,11 +550,15 @@ export async function renderToHTML( parentParams: { [key: string]: any } rootLayoutIncluded?: boolean firstItem?: boolean - }): { Component: React.ComponentType } => { - const Loading = loading ? interopDefault(loading()) : undefined + }): Promise<{ Component: React.ComponentType }> => { + const Loading = loading ? await interopDefault(loading()) : undefined const isLayout = typeof layout !== 'undefined' const isPage = typeof page !== 'undefined' - const layoutOrPageMod = isLayout ? layout() : isPage ? page() : undefined + const layoutOrPageMod = isLayout + ? await layout() + : isPage + ? await page() + : undefined const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded const rootLayoutIncludedAtThisLevelOrAbove = rootLayoutIncluded || rootLayoutAtThisLevel @@ -594,45 +598,53 @@ export async function renderToHTML( : segment // This happens outside of rendering in order to eagerly kick off data fetching for layouts / the page further down - const parallelRouteComponents = Object.keys(parallelRoutes).reduce( - (list, currentValue) => { - const currentSegmentPath = firstItem - ? [currentValue] - : [actualSegment, currentValue] - - const { Component: ChildComponent } = createComponentTree({ - createSegmentPath: (child) => { - return createSegmentPath([...currentSegmentPath, ...child]) - }, - tree: parallelRoutes[currentValue], - parentParams: currentParams, - rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, - }) - - const childSegmentParam = getDynamicParamFromSegment( - parallelRoutes[currentValue][0] - ) - const childProp: ChildProp = { - current: , - segment: childSegmentParam - ? [ - childSegmentParam.param, - childSegmentParam.treeValue, - childSegmentParam.type, - ] - : parallelRoutes[currentValue][0], - } + const parallelRouteMap = await Promise.all( + Object.keys(parallelRoutes).map( + async (parallelRouteKey): Promise<[string, React.ReactNode]> => { + const currentSegmentPath = firstItem + ? [parallelRouteKey] + : [actualSegment, parallelRouteKey] + + const { Component: ChildComponent } = await createComponentTree({ + createSegmentPath: (child) => { + return createSegmentPath([...currentSegmentPath, ...child]) + }, + tree: parallelRoutes[parallelRouteKey], + parentParams: currentParams, + rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, + }) + + const childSegmentParam = getDynamicParamFromSegment( + parallelRoutes[parallelRouteKey][0] + ) + const childProp: ChildProp = { + current: , + segment: childSegmentParam + ? [ + childSegmentParam.param, + childSegmentParam.treeValue, + childSegmentParam.type, + ] + : parallelRoutes[parallelRouteKey][0], + } - list[currentValue] = ( - : undefined} - childProp={childProp} - rootLayoutIncluded={rootLayoutIncludedAtThisLevelOrAbove} - /> - ) + return [ + parallelRouteKey, + : undefined} + childProp={childProp} + rootLayoutIncluded={rootLayoutIncludedAtThisLevelOrAbove} + />, + ] + } + ) + ) + const parallelRouteComponents = parallelRouteMap.reduce( + (list, [parallelRouteKey, Comp]) => { + list[parallelRouteKey] = Comp return list }, {} as { [key: string]: React.ReactNode } @@ -759,12 +771,12 @@ export async function renderToHTML( if (isFlight) { // TODO-APP: throw on invalid flightRouterState - const walkTreeWithFlightRouterState = ( + const walkTreeWithFlightRouterState = async ( treeToFilter: LoaderTree, parentParams: { [key: string]: string | string[] }, flightRouterState?: FlightRouterState, parentRendered?: boolean - ): FlightDataPath => { + ): Promise => { const [segment, parallelRoutes] = treeToFilter const parallelRoutesKeys = Object.keys(parallelRoutes) @@ -794,14 +806,16 @@ export async function renderToHTML( actualSegment, createFlightRouterStateFromLoaderTree(treeToFilter), React.createElement( - createComponentTree( - // This ensures flightRouterPath is valid and filters down the tree - { - createSegmentPath: (child) => child, - tree: treeToFilter, - parentParams: currentParams, - firstItem: true, - } + ( + await createComponentTree( + // This ensures flightRouterPath is valid and filters down the tree + { + createSegmentPath: (child) => child, + tree: treeToFilter, + parentParams: currentParams, + firstItem: true, + } + ) ).Component ), ] @@ -809,7 +823,7 @@ export async function renderToHTML( for (const parallelRouteKey of parallelRoutesKeys) { const parallelRoute = parallelRoutes[parallelRouteKey] - const path = walkTreeWithFlightRouterState( + const path = await walkTreeWithFlightRouterState( parallelRoute, currentParams, flightRouterState && flightRouterState[1][parallelRouteKey], @@ -831,9 +845,9 @@ export async function renderToHTML( ) const flightData: FlightData = [ // TODO-APP: change walk to output without '' - walkTreeWithFlightRouterState(tree, {}, providedFlightRouterState).slice( - 1 - ), + ( + await walkTreeWithFlightRouterState(tree, {}, providedFlightRouterState) + ).slice(1), ] return new RenderResult( @@ -858,7 +872,7 @@ export async function renderToHTML( dev ) - const { Component: ComponentTree } = createComponentTree({ + const { Component: ComponentTree } = await createComponentTree({ createSegmentPath: (child) => child, tree, parentParams: {}, From 7921b675260d377f963d62f6278a5289fb87b337 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 21 Jul 2022 14:38:04 +0200 Subject: [PATCH 08/79] Leverage mini css plugin hmr for app dir (#38830) Co-authored-by: Tim Neutkens --- .../build/webpack/config/blocks/css/index.ts | 11 ++- .../config/blocks/css/loaders/client.ts | 45 ++++----- .../webpack/plugins/flight-manifest-plugin.ts | 46 +++------- packages/next/client/app-index.tsx | 92 ++----------------- packages/next/client/app-next-dev.js | 5 +- .../client/components/app-router.client.tsx | 50 +--------- packages/next/server/app-render.tsx | 73 ++------------- .../next/server/node-web-streams-helper.ts | 15 +-- .../app/app/dashboard/client-comp.client.jsx | 4 +- .../app/app/dashboard/client-comp.module.css | 3 + .../app-dir/app/app/dashboard/page.server.js | 1 + test/e2e/app-dir/app/app/layout.server.js | 2 + test/e2e/app-dir/app/styles/global.css | 3 + 13 files changed, 74 insertions(+), 276 deletions(-) create mode 100644 test/e2e/app-dir/app/app/dashboard/client-comp.module.css create mode 100644 test/e2e/app-dir/app/styles/global.css diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts index d6b734173ff00..1177e746a649f 100644 --- a/packages/next/build/webpack/config/blocks/css/index.ts +++ b/packages/next/build/webpack/config/blocks/css/index.ts @@ -470,7 +470,8 @@ export const css = curry(async function css( ) } - if (ctx.isClient && ctx.isProduction) { + // Enable full mini-css-extract-plugin hmr for prod mode pages or app dir + if (ctx.isClient && (ctx.isProduction || ctx.experimental.appDir)) { // Extract CSS as CSS file(s) in the client-side production bundle. const MiniCssExtractPlugin = require('../../../plugins/mini-css-extract-plugin').default @@ -478,8 +479,12 @@ export const css = curry(async function css( plugin( // @ts-ignore webpack 5 compat new MiniCssExtractPlugin({ - filename: 'static/css/[contenthash].css', - chunkFilename: 'static/css/[contenthash].css', + filename: ctx.isProduction + ? 'static/css/[contenthash].css' + : 'static/css/[name].css', + chunkFilename: ctx.isProduction + ? 'static/css/[contenthash].css' + : 'static/css/[name].css', // Next.js guarantees that CSS order "doesn't matter", due to imposed // restrictions: // 1. Global CSS can only be defined in a single entrypoint (_app) diff --git a/packages/next/build/webpack/config/blocks/css/loaders/client.ts b/packages/next/build/webpack/config/blocks/css/loaders/client.ts index ce4f0f9820f66..427898caf5da0 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/client.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/client.ts @@ -9,37 +9,30 @@ export function getClientStyleLoader({ isDevelopment: boolean assetPrefix: string }): webpack.RuleSetUseItem { - if (isDevelopment) { + // Keep next-style-loader for development mode in `pages/` + if (isDevelopment && !isAppDir) { return { loader: 'next-style-loader', options: { - insert: isAppDir - ? function (element: Node) { - // There is currently no anchor element in . - // We temporarily insert the element as the last child - // of the first . - const head = document.querySelector('head')! - head.insertBefore(element, head.lastChild) - } - : function (element: Node) { - // By default, style-loader injects CSS into the bottom - // of . This causes ordering problems between dev - // and prod. To fix this, we render a