From cbaaaeb0766a5d571e63dda2a779ceaf9cdf4e05 Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Mon, 1 Aug 2022 11:21:36 -0700 Subject: [PATCH 01/10] Add "fill" mode to `next/future/image` (#38855) This PR adds a `fill` attribute to the future image component, that behave similarly to the fill mode on the existing image component. Functionally, it mainly adds `position: "absolute"` and `object-fit: "contain"` to the image element, along with `height: "100%"` and `width: "100%"` to preserve the image aspect ratio. All of these can be overwritten by the user, except for `position: "absolute"`, which will throw an error. This is because changing that property without height and width is likely to cause layout shift. Because we no longer have the wrapper element, this new version of `fill` requires that the user set `overflow: "hidden"` on the parent element themself, if they want that behavior. This PR also includes several runtime checks to catch instances where the fill mode may provide unexpected results. These runtime checks warn if: * The image doesn't have the `sizes` attribute and loads much smaller than the viewport * The containing element does not have `position: "relative"` * The image height value is 0 Co-authored-by: Steven <229881+styfle@users.noreply.github.com> --- docs/api-reference/next/future/image.md | 22 +- packages/next/client/future/image.tsx | 297 +++++++++++------- .../default/pages/fill-warnings.js | 35 +++ .../image-future/default/pages/fill.js | 23 ++ .../default/pages/invalid-fill-position.js | 28 ++ .../default/pages/invalid-fill-width.js | 29 ++ .../image-future/default/test/index.test.js | 75 ++++- .../image-future/typescript/pages/valid.tsx | 5 + 8 files changed, 399 insertions(+), 115 deletions(-) create mode 100644 test/integration/image-future/default/pages/fill-warnings.js create mode 100644 test/integration/image-future/default/pages/fill.js create mode 100644 test/integration/image-future/default/pages/invalid-fill-position.js create mode 100644 test/integration/image-future/default/pages/invalid-fill-width.js diff --git a/docs/api-reference/next/future/image.md b/docs/api-reference/next/future/image.md index 6c5551c02555..940ea805cb88 100644 --- a/docs/api-reference/next/future/image.md +++ b/docs/api-reference/next/future/image.md @@ -9,6 +9,7 @@ description: Try the latest Image Optimization with the experimental `next/futur | Version | Changes | | --------- | -------------------------------------------- | +| `v12.2.4` | Support for `fill` property added. | | `v12.2.0` | Experimental `next/future/image` introduced. | @@ -36,7 +37,6 @@ Compared to `next/image`, the new `next/future/image` component has the followin - Removes `layout`, `objectFit`, and `objectPosition` props in favor of `style` or `className` - Removes `IntersectionObserver` implementation in favor of [native lazy loading](https://caniuse.com/loading-lazy-attr) - Removes `loader` config in favor of [`loader`](#loader) prop -- Note: there is no `fill` mode so `width` & `height` props are required - Note: the [`onError`](#onerror) prop might behave differently The default layout for `next/image` was `intrinsic`, which would shrink the `width` if the image was larger than it's container. Since no styles are automatically applied to `next/future/image`, you'll need to add the following CSS to achieve the same behavior: @@ -64,13 +64,13 @@ When using an external URL, you must add it to [domains](#domains) in `next.conf The `width` property represents the _rendered_ width in pixels, so it will affect how large the image appears. -Required, except for [statically imported images](/docs/basic-features/image-optimization.md#local-images). +Required, except for [statically imported images](/docs/basic-features/image-optimization.md#local-images) or images with the [`fill` property](#fill). ### height The `height` property represents the _rendered_ height in pixels, so it will affect how large the image appears. -Required, except for [statically imported images](/docs/basic-features/image-optimization.md#local-images). +Required, except for [statically imported images](/docs/basic-features/image-optimization.md#local-images) or images with the [`fill` property](#fill). ## Optional Props @@ -108,6 +108,22 @@ const MyImage = (props) => { } ``` +### fill + +A boolean that causes the image to fill the parent element instead of setting [`width`](#width) and [`height`](#height). + +The parent element _must_ assign `position: "relative"`, `position: "fixed"`, or `position: "absolute"` style. + +By default, the img element will automatically assign `object-fit: "contain"` and `position: "absolute"` styles. + +Optionally, `object-fit` can be assigned any other value such as `object-fit: "cover"`. For this to look correct, the `overflow: "hidden"` style should be assigned to the parent element. + +See also: + +- [position](https://developer.mozilla.org/en-US/docs/Web/CSS/position) +- [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) +- [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) + ### sizes A string that provides information about how wide the image will be at different breakpoints. diff --git a/packages/next/client/future/image.tsx b/packages/next/client/future/image.tsx index 088c00cc9f80..9d1592e22b2e 100644 --- a/packages/next/client/future/image.tsx +++ b/packages/next/client/future/image.tsx @@ -102,6 +102,7 @@ export type ImageProps = Omit< src: string | StaticImport width?: number | string height?: number | string + fill?: boolean loader?: ImageLoader quality?: number | string priority?: boolean @@ -121,6 +122,7 @@ type ImageElementProps = Omit & { imgStyle: ImgElementStyle blurStyle: ImgElementStyle isLazy: boolean + fill?: boolean loading: LoadingValue config: ImageConfig unoptimized: boolean @@ -270,6 +272,37 @@ function handleLoading( onLoadingCompleteRef.current({ naturalWidth, naturalHeight }) } if (process.env.NODE_ENV !== 'production') { + if (img.getAttribute('data-nimg') === 'future-fill') { + if ( + !img.getAttribute('sizes') || + img.getAttribute('sizes') === '100vw' + ) { + let widthViewportRatio = + img.getBoundingClientRect().width / window.innerWidth + if (widthViewportRatio < 0.6) { + warnOnce( + `Image with src "${src}" has "fill" but is missing "sizes" prop. Please add it to improve page performance. Read more: https://nextjs.org/docs/api-reference/next/future/image#sizes` + ) + } + } + if (img.parentElement) { + const { position } = window.getComputedStyle(img.parentElement) + const valid = ['absolute', 'fixed', 'relative'] + if (!valid.includes(position)) { + warnOnce( + `Image with src "${src}" has "fill" and parent element with invalid "position". Provided "${position}" should be one of ${valid + .map(String) + .join(',')}.` + ) + } + } + if (img.height === 0) { + warnOnce( + `Image with src "${src}" has "fill" and a height value of 0. This is likely because the parent element of the image has not been styled to have a set height.` + ) + } + } + const heightModified = img.height.toString() !== img.getAttribute('height') const widthModified = img.width.toString() !== img.getAttribute('width') @@ -295,6 +328,7 @@ export default function Image({ quality, width, height, + fill, style, onLoadingComplete, placeholder = 'empty', @@ -380,132 +414,171 @@ export default function Image({ // 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 ('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.` + if (fill) { + if (width) { + throw new Error( + `Image with src "${src}" has both "width" and "fill" properties. Only one should be used.` ) } - if (!blurDataURL) { - const VALID_BLUR_EXT = ['jpeg', 'png', 'webp', 'avif'] // should match next-image-loader - + if (height) { + throw new Error( + `Image with src "${src}" has both "height" and "fill" properties. Only one should be used.` + ) + } + if (style?.position && style.position !== 'absolute') { 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 both "fill" and "style.position" properties. Images with "fill" always use position absolute - it cannot be modified.` + ) + } + if (style?.width && style.width !== '100%') { + throw new Error( + `Image with src "${src}" has both "fill" and "style.width" properties. Images with "fill" always use width 100% - it cannot be modified.` + ) + } + if (style?.height && style.height !== '100%') { + throw new Error( + `Image with src "${src}" has both "fill" and "style.height" properties. Images with "fill" always use height 100% - it cannot be modified.` + ) + } + } 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 ('ref' in rest) { + } + 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" property.` + ) + } + if ('objectPosition' in rest) { + throw new Error( + `Image with src "${src}" has unknown prop "objectPosition". This style should be specified using the "style" property.` + ) + } + + if (placeholder === 'blur') { + if ((widthInt || 0) * (heightInt || 0) < 1600) { warnOnce( - `Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.` + `Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance.` ) } - 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` - ) - } + 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}" 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` + ) + } + } + + 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) } } } - const imgStyle = Object.assign({}, style) + const imgStyle = Object.assign( + fill + ? { + position: 'absolute', + objectFit: 'contain', + height: '100%', + width: '100%', + } + : {}, + style + ) const svgBlurPlaceholder = `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 ${widthInt} ${heightInt}'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='${blurDataURL}'/%3E%3C/svg%3E")` const blurStyle = placeholder === 'blur' && !blurComplete @@ -576,6 +649,7 @@ export default function Image({ blurStyle, loading, config, + fill, unoptimized, placeholder, loader, @@ -622,6 +696,7 @@ const ImageElement = ({ imgStyle, blurStyle, isLazy, + fill, placeholder, loading, srcString, @@ -644,7 +719,7 @@ const ImageElement = ({ width={widthInt} height={heightInt} decoding="async" - data-nimg="future" + data-nimg={`future${fill ? '-fill' : ''}`} className={className} // @ts-ignore - TODO: upgrade to `@types/react@17` loading={loading} @@ -707,7 +782,7 @@ const ImageElement = ({ width={widthInt} height={heightInt} decoding="async" - data-nimg="future" + data-nimg={`future${fill ? '-fill' : ''}`} style={imgStyle} className={className} // @ts-ignore - TODO: upgrade to `@types/react@17` diff --git a/test/integration/image-future/default/pages/fill-warnings.js b/test/integration/image-future/default/pages/fill-warnings.js new file mode 100644 index 000000000000..e73e453b53f8 --- /dev/null +++ b/test/integration/image-future/default/pages/fill-warnings.js @@ -0,0 +1,35 @@ +import React from 'react' +import Image from 'next/future/image' + +const Page = () => { + return ( +
+

Fill Mode

+
+ +
+
+ +
+
+ +
+
+ ) +} + +export default Page diff --git a/test/integration/image-future/default/pages/fill.js b/test/integration/image-future/default/pages/fill.js new file mode 100644 index 000000000000..27c0fe8da07f --- /dev/null +++ b/test/integration/image-future/default/pages/fill.js @@ -0,0 +1,23 @@ +import React from 'react' +import Image from 'next/future/image' + +const Page = () => { + return ( +
+

Fill Mode

+
+ +
+
+ ) +} + +export default Page diff --git a/test/integration/image-future/default/pages/invalid-fill-position.js b/test/integration/image-future/default/pages/invalid-fill-position.js new file mode 100644 index 000000000000..5aa363c3ff0b --- /dev/null +++ b/test/integration/image-future/default/pages/invalid-fill-position.js @@ -0,0 +1,28 @@ +import React from 'react' +import Image from 'next/future/image' + +const Page = () => { + return ( +
+

Fill Mode

+
+ +
+
+ ) +} + +export default Page diff --git a/test/integration/image-future/default/pages/invalid-fill-width.js b/test/integration/image-future/default/pages/invalid-fill-width.js new file mode 100644 index 000000000000..0987b2a5d5f4 --- /dev/null +++ b/test/integration/image-future/default/pages/invalid-fill-width.js @@ -0,0 +1,29 @@ +import React from 'react' +import Image from 'next/future/image' + +const Page = () => { + return ( +
+

Fill Mode

+
+ +
+
+ ) +} + +export default Page diff --git a/test/integration/image-future/default/test/index.test.js b/test/integration/image-future/default/test/index.test.js index e02c5b25a1c5..6245ec788f7b 100644 --- a/test/integration/image-future/default/test/index.test.js +++ b/test/integration/image-future/default/test/index.test.js @@ -677,6 +677,24 @@ function runTests(mode) { ) }) + it('should show error when width prop on fill image', async () => { + const browser = await webdriver(appPort, '/invalid-fill-width') + + expect(await hasRedbox(browser)).toBe(true) + expect(await getRedboxHeader(browser)).toContain( + `Image with src "/wide.png" has both "width" and "fill" properties.` + ) + }) + + it('should show error when CSS position changed on fill image', async () => { + const browser = await webdriver(appPort, '/invalid-fill-position') + + expect(await hasRedbox(browser)).toBe(true) + expect(await getRedboxHeader(browser)).toContain( + `Image with src "/wide.png" has both "fill" and "style.position" properties. Images with "fill" always use position absolute - it cannot be modified.` + ) + }) + it('should show error when static import and placeholder=blur and blurDataUrl is missing', async () => { const browser = await webdriver( appPort, @@ -931,7 +949,62 @@ function runTests(mode) { await getComputedStyle(browser, 'img-blur', 'background-position') ).toBe('1px 2px') }) - + describe('Fill-mode tests', () => { + let browser + beforeAll(async () => { + browser = await webdriver(appPort, '/fill') + }) + it('should include a data-attribute on fill images', async () => { + expect( + await browser.elementById('fill-image-1').getAttribute('data-nimg') + ).toBe('future-fill') + }) + it('should add position:absolute and object-fit to fill images', async () => { + expect(await getComputedStyle(browser, 'fill-image-1', 'position')).toBe( + 'absolute' + ) + expect( + await getComputedStyle(browser, 'fill-image-1', 'object-fit') + ).toBe('contain') + }) + it('should add 100% width and height to fill images', async () => { + expect( + await browser.eval( + `document.getElementById("fill-image-1").style.height` + ) + ).toBe('100%') + expect( + await browser.eval( + `document.getElementById("fill-image-1").style.width` + ) + ).toBe('100%') + }) + if (mode === 'dev') { + it('should not log incorrect warnings', async () => { + await waitFor(1000) + const warnings = (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + expect(warnings).not.toMatch(/Image with src (.*) has "fill"/gm) + }) + it('should log warnings when using fill mode incorrectly', async () => { + browser = await webdriver(appPort, '/fill-warnings') + await waitFor(1000) + const warnings = (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + expect(warnings).toContain( + 'Image with src "/wide.png" has "fill" and parent element with invalid "position". Provided "static" should be one of absolute,fixed,relative.' + ) + expect(warnings).toContain( + 'Image with src "/wide.png" has "fill" and a height value of 0. This is likely because the parent element of the image has not been styled to have a set height.' + ) + expect(warnings).toContain( + 'Image with src "/wide.png" has "fill" but is missing "sizes" prop. Please add it to improve page performance. Read more:' + ) + }) + } + }) // Tests that use the `unsized` attribute: if (mode !== 'dev') { it('should correctly rotate image', async () => { diff --git a/test/integration/image-future/typescript/pages/valid.tsx b/test/integration/image-future/typescript/pages/valid.tsx index ef1f75907a3f..2bc8a517a95e 100644 --- a/test/integration/image-future/typescript/pages/valid.tsx +++ b/test/integration/image-future/typescript/pages/valid.tsx @@ -22,6 +22,11 @@ const Page = () => { width="500" height="500" /> + Date: Mon, 1 Aug 2022 15:05:48 -0400 Subject: [PATCH 02/10] Update tests to use random directory (#39230) This PR fixes an issue where tests fail because the generated tmp directory already exists. This is likely caused from two tmp dirs being generated in the same millisecond. ``` Failed to create next instance [Error: EEXIST: file already exists, mkdir '/tmp/next-repo-1659368089063/packages'] { errno: -17, code: 'EEXIST', syscall: 'mkdir', path: '/tmp/next-repo-1659368089063/packages' } ``` https://github.com/vercel/next.js/runs/7615808051?check_suite_focus=true --- test/lib/create-next-install.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/lib/create-next-install.js b/test/lib/create-next-install.js index c8d776f4d6bf..5b4d5eedd513 100644 --- a/test/lib/create-next-install.js +++ b/test/lib/create-next-install.js @@ -3,6 +3,7 @@ const path = require('path') const execa = require('execa') const fs = require('fs-extra') const childProcess = require('child_process') +const { randomBytes } = require('crypto') const { linkPackages } = require('../../.github/actions/next-stats-action/src/prepare/repo-setup')() @@ -14,8 +15,14 @@ async function createNextInstall( ) { const tmpDir = await fs.realpath(process.env.NEXT_TEST_DIR || os.tmpdir()) const origRepoDir = path.join(__dirname, '../../') - const installDir = path.join(tmpDir, `next-install-${Date.now()}`) - const tmpRepoDir = path.join(tmpDir, `next-repo-${Date.now()}`) + const installDir = path.join( + tmpDir, + `next-install-${randomBytes(32).toString('hex')}` + ) + const tmpRepoDir = path.join( + tmpDir, + `next-repo-${randomBytes(32).toString('hex')}` + ) // ensure swc binary is present in the native folder if // not already built From 7c0a504a168ff41b30e88cb4b6edd89c33f59d02 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 1 Aug 2022 17:34:23 -0500 Subject: [PATCH 03/10] Handle assetPrefix in app (#39236) This ensures we properly leverage the `assetPrefix` for `app`. Note for reviewing the test changes are mostly spacing so hiding that may help. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` Fixes: [slack thread](https://vercel.slack.com/archives/C035J346QQL/p1659387244559979) --- packages/next/client/app-index.tsx | 3 +- packages/next/server/app-render.tsx | 9 +- test/e2e/app-dir/app/next.config.js | 16 +- test/e2e/app-dir/index.test.ts | 1830 ++++++++++++++------------- 4 files changed, 952 insertions(+), 906 deletions(-) diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index 1e605c8274ae..9d0ec8accb6f 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -33,7 +33,8 @@ self.__next_require__ = __webpack_require__ ;(self as any).__next_chunk_load__ = (chunk: string) => { if (!chunk) return Promise.resolve() if (chunk.endsWith('.css')) { - const chunkPath = `/_next/${chunk}` + // @ts-expect-error __webpack_public_path__ is inlined by webpack + const chunkPath = `${__webpack_public_path__ || ''}${chunk}` const existingTag = document.querySelector(`link[href="${chunkPath}"]`) if (!existingTag) { const link = document.createElement('link') diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index d3e2d98ed090..540fc4484b61 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -37,6 +37,7 @@ export type RenderOptsPartial = { supportsDynamicHTML?: boolean runtime?: ServerRuntime serverComponents?: boolean + assetPrefix?: string } export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial @@ -962,7 +963,11 @@ export async function renderToHTMLOrFlight( return ( } + hotReloader={ + HotReloader && ( + + ) + } initialCanonicalUrl={initialCanonicalUrl} initialTree={initialTree} initialStylesheets={initialStylesheets} @@ -1021,7 +1026,7 @@ export async function renderToHTMLOrFlight( streamOptions: { // Include hydration scripts in the HTML bootstrapScripts: buildManifest.rootMainFiles.map( - (src) => '/_next/' + src + (src) => `${renderOpts.assetPrefix || ''}/_next/` + src ), }, }) diff --git a/test/e2e/app-dir/app/next.config.js b/test/e2e/app-dir/app/next.config.js index fad482204ef0..a908886893f7 100644 --- a/test/e2e/app-dir/app/next.config.js +++ b/test/e2e/app-dir/app/next.config.js @@ -6,12 +6,16 @@ module.exports = { legacyBrowsers: false, browsersListForSwc: true, }, + // assetPrefix: '/assets', rewrites: async () => { - return [ - { - source: '/rewritten-to-dashboard', - destination: '/dashboard', - }, - ] + return { + // beforeFiles: [ { source: '/assets/:path*', destination: '/:path*' } ], + afterFiles: [ + { + source: '/rewritten-to-dashboard', + destination: '/dashboard', + }, + ], + } }, } diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index 9fe5df5af35a..e9984ce8fa0f 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -19,1069 +19,1105 @@ describe('app dir', () => { } let next: NextInstance - beforeAll(async () => { - next = await createNext({ - files: { - public: new FileRef(path.join(__dirname, 'app/public')), - styles: new FileRef(path.join(__dirname, 'app/styles')), - pages: new FileRef(path.join(__dirname, 'app/pages')), - app: new FileRef(path.join(__dirname, 'app/app')), - 'next.config.js': new FileRef( - path.join(__dirname, 'app/next.config.js') - ), - }, - dependencies: { - react: 'experimental', - 'react-dom': 'experimental', - }, - }) - }) - afterAll(() => next.destroy()) - - it('should pass props from getServerSideProps in root layout', async () => { - const html = await renderViaHTTP(next.url, '/dashboard') - const $ = cheerio.load(html) - expect($('title').text()).toBe('hello world') - }) - - it('should serve from pages', async () => { - const html = await renderViaHTTP(next.url, '/') - expect(html).toContain('hello from pages/index') - }) - - it('should serve dynamic route from pages', async () => { - const html = await renderViaHTTP(next.url, '/blog/first') - expect(html).toContain('hello from pages/blog/[slug]') - }) - - it('should serve from public', async () => { - const html = await renderViaHTTP(next.url, '/hello.txt') - expect(html).toContain('hello world') - }) - - it('should serve from app', async () => { - const html = await renderViaHTTP(next.url, '/dashboard') - expect(html).toContain('hello from app/dashboard') - }) - - it('should serve /index as separate page', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/index') - expect(html).toContain('hello from app/dashboard/index') - // should load chunks generated via async import correctly with React.lazy - expect(html).toContain('hello from lazy') - // should support `dynamic` in both server and client components - expect(html).toContain('hello from dynamic on server') - expect(html).toContain('hello from dynamic on client') - }) - - // TODO-APP: handle css modules fouc in dev - it.skip('should handle css imports in next/dynamic correctly', async () => { - const browser = await webdriver(next.url, '/dashboard/index') + function runTests({ assetPrefix }: { assetPrefix?: boolean }) { + beforeAll(async () => { + next = await createNext({ + files: { + public: new FileRef(path.join(__dirname, 'app/public')), + styles: new FileRef(path.join(__dirname, 'app/styles')), + pages: new FileRef(path.join(__dirname, 'app/pages')), + app: new FileRef(path.join(__dirname, 'app/app')), + 'next.config.js': new FileRef( + path.join(__dirname, 'app/next.config.js') + ), + }, + dependencies: { + react: 'experimental', + 'react-dom': 'experimental', + }, + skipStart: true, + }) - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('#css-text-dynamic-server')).color` - ) - ).toBe('rgb(0, 0, 255)') - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('#css-text-lazy')).color` - ) - ).toBe('rgb(128, 0, 128)') - }) + if (assetPrefix) { + const content = await next.readFile('next.config.js') + await next.patchFile( + 'next.config.js', + content + .replace('// assetPrefix', 'assetPrefix') + .replace('// beforeFiles', 'beforeFiles') + ) + } + await next.start() + }) + afterAll(() => next.destroy()) - it('should include layouts when no direct parent layout', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/integrations') - const $ = cheerio.load(html) - // Should not be nested in dashboard - expect($('h1').text()).toBe('Dashboard') - // Should include the page text - expect($('p').text()).toBe('hello from app/dashboard/integrations') - }) + it('should pass props from getServerSideProps in root layout', async () => { + const html = await renderViaHTTP(next.url, '/dashboard') + const $ = cheerio.load(html) + expect($('title').text()).toBe('hello world') + }) - // TODO: handle new root layout - it.skip('should not include parent when not in parent directory with route in directory', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/hello') - const $ = cheerio.load(html) + it('should serve from pages', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('hello from pages/index') + }) - // new root has to provide it's own custom root layout or the default - // is used instead - expect(html).toContain(' { + const html = await renderViaHTTP(next.url, '/blog/first') + expect(html).toContain('hello from pages/blog/[slug]') + }) - // Should not be nested in dashboard - expect($('h1').text()).toBeFalsy() + it('should serve from public', async () => { + const html = await renderViaHTTP(next.url, '/hello.txt') + expect(html).toContain('hello world') + }) - // Should render the page text - expect($('p').text()).toBe('hello from app/dashboard/rootonly/hello') - }) + it('should serve from app', async () => { + const html = await renderViaHTTP(next.url, '/dashboard') + expect(html).toContain('hello from app/dashboard') + }) - it('should use new root layout when provided', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/another') - const $ = cheerio.load(html) + it('should serve /index as separate page', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/index') + expect(html).toContain('hello from app/dashboard/index') + // should load chunks generated via async import correctly with React.lazy + expect(html).toContain('hello from lazy') + // should support `dynamic` in both server and client components + expect(html).toContain('hello from dynamic on server') + expect(html).toContain('hello from dynamic on client') + }) - // new root has to provide it's own custom root layout or the default - // is used instead - expect($('html').hasClass('this-is-another-document-html')).toBeTruthy() - expect($('body').hasClass('this-is-another-document-body')).toBeTruthy() + // TODO-APP: handle css modules fouc in dev + it.skip('should handle css imports in next/dynamic correctly', async () => { + const browser = await webdriver(next.url, '/dashboard/index') - // Should not be nested in dashboard - expect($('h1').text()).toBeFalsy() + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('#css-text-dynamic-server')).color` + ) + ).toBe('rgb(0, 0, 255)') + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('#css-text-lazy')).color` + ) + ).toBe('rgb(128, 0, 128)') + }) - // Should render the page text - expect($('p').text()).toBe('hello from newroot/dashboard/another') - }) + it('should include layouts when no direct parent layout', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/integrations') + const $ = cheerio.load(html) + // Should not be nested in dashboard + expect($('h1').text()).toBe('Dashboard') + // Should include the page text + expect($('p').text()).toBe('hello from app/dashboard/integrations') + }) - it('should not create new root layout when nested (optional)', async () => { - const html = await renderViaHTTP( - next.url, - '/dashboard/deployments/breakdown' - ) - const $ = cheerio.load(html) + // TODO: handle new root layout + it.skip('should not include parent when not in parent directory with route in directory', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/hello') + const $ = cheerio.load(html) - // new root has to provide it's own custom root layout or the default - // is used instead - expect($('html').hasClass('this-is-the-document-html')).toBeTruthy() - expect($('body').hasClass('this-is-the-document-body')).toBeTruthy() + // new root has to provide it's own custom root layout or the default + // is used instead + expect(html).toContain(' { - const html = await renderViaHTTP(next.url, '/dashboard/integrations') - const $ = cheerio.load(html) + it('should use new root layout when provided', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/another') + const $ = cheerio.load(html) - expect($('html').hasClass('this-is-the-document-html')).toBeTruthy() - expect($('body').hasClass('this-is-the-document-body')).toBeTruthy() - }) + // new root has to provide it's own custom root layout or the default + // is used instead + expect($('html').hasClass('this-is-another-document-html')).toBeTruthy() + expect($('body').hasClass('this-is-another-document-body')).toBeTruthy() - it('should not include parent when not in parent directory', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/changelog') - const $ = cheerio.load(html) - // Should not be nested in dashboard - expect($('h1').text()).toBeFalsy() - // Should include the page text - expect($('p').text()).toBe('hello from app/dashboard/changelog') - }) + // Should not be nested in dashboard + expect($('h1').text()).toBeFalsy() - it('should serve nested parent', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/deployments/123') - const $ = cheerio.load(html) - // Should be nested in dashboard - expect($('h1').text()).toBe('Dashboard') - // Should be nested in deployments - expect($('h2').text()).toBe('Deployments hello') - }) - - it('should serve dynamic parameter', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/deployments/123') - const $ = cheerio.load(html) - // Should include the page text with the parameter - expect($('p').text()).toBe( - 'hello from app/dashboard/deployments/[id]. ID is: 123' - ) - }) + // Should render the page text + expect($('p').text()).toBe('hello from newroot/dashboard/another') + }) - it('should include document html and body', async () => { - const html = await renderViaHTTP(next.url, '/dashboard') - const $ = cheerio.load(html) + it('should not create new root layout when nested (optional)', async () => { + const html = await renderViaHTTP( + next.url, + '/dashboard/deployments/breakdown' + ) + const $ = cheerio.load(html) - expect($('html').hasClass('this-is-the-document-html')).toBeTruthy() - expect($('body').hasClass('this-is-the-document-body')).toBeTruthy() - }) + // new root has to provide it's own custom root layout or the default + // is used instead + expect($('html').hasClass('this-is-the-document-html')).toBeTruthy() + expect($('body').hasClass('this-is-the-document-body')).toBeTruthy() - it('should not serve when layout is provided but no folder index', async () => { - const res = await fetchViaHTTP(next.url, '/dashboard/deployments') - expect(res.status).toBe(404) - expect(await res.text()).toContain('This page could not be found') - }) + // Should be nested in dashboard + expect($('h1').text()).toBe('Dashboard') + expect($('h2').text()).toBe('Custom dashboard') - // TODO: do we want to make this only work for /root or is it allowed - // to work for /pages as well? - it.skip('should match partial parameters', async () => { - const html = await renderViaHTTP(next.url, '/partial-match-123') - expect(html).toContain('hello from app/partial-match-[id]. ID is: 123') - }) + // Should render the page text + expect($('p').text()).toBe( + 'hello from app/dashboard/(custom)/deployments/breakdown' + ) + }) - it('should support rewrites', async () => { - const html = await renderViaHTTP(next.url, '/rewritten-to-dashboard') - expect(html).toContain('hello from app/dashboard') - }) + it('should include parent document when no direct parent layout', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/integrations') + const $ = cheerio.load(html) - // TODO-APP: Enable in development - ;(isDev ? it.skip : it)( - 'should not rerender layout when navigating between routes in the same layout', - async () => { - const browser = await webdriver(next.url, '/same-layout/first') + expect($('html').hasClass('this-is-the-document-html')).toBeTruthy() + expect($('body').hasClass('this-is-the-document-body')).toBeTruthy() + }) - try { - // Get the render id from the dom and click the first link. - const firstRenderID = await browser.elementById('render-id').text() - await browser.elementById('link').click() - await browser.waitForElementByCss('#second-page') - - // Get the render id from the dom again, it should be the same! - const secondRenderID = await browser.elementById('render-id').text() - expect(secondRenderID).toBe(firstRenderID) - - // Navigate back to the first page again by clicking the link. - await browser.elementById('link').click() - await browser.waitForElementByCss('#first-page') - - // Get the render id from the dom again, it should be the same! - const thirdRenderID = await browser.elementById('render-id').text() - expect(thirdRenderID).toBe(firstRenderID) - } finally { - await browser.close() - } - } - ) - - it('should handle hash in initial url', async () => { - const browser = await webdriver(next.url, '/dashboard#abc') - - try { - // Check if hash is preserved - expect(await browser.eval('window.location.hash')).toBe('#abc') - await waitFor(1000) - // Check again to be sure as it might be timed different - expect(await browser.eval('window.location.hash')).toBe('#abc') - } finally { - await browser.close() - } - }) + it('should not include parent when not in parent directory', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/changelog') + const $ = cheerio.load(html) + // Should not be nested in dashboard + expect($('h1').text()).toBeFalsy() + // Should include the page text + expect($('p').text()).toBe('hello from app/dashboard/changelog') + }) - describe('', () => { - // TODO-APP: fix development test - it.skip('should hard push', async () => { - const browser = await webdriver(next.url, '/link-hard-push') + it('should serve nested parent', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/deployments/123') + const $ = cheerio.load(html) + // Should be nested in dashboard + expect($('h1').text()).toBe('Dashboard') + // Should be nested in deployments + expect($('h2').text()).toBe('Deployments hello') + }) - try { - // Click the link on the page, and verify that the history entry was - // added. - expect(await browser.eval('window.history.length')).toBe(2) - await browser.elementById('link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(3) - - // Get the id on the rendered page. - const firstID = await browser.elementById('render-id').text() - - // Go back, and redo the navigation by clicking the link. - await browser.back() - await browser.elementById('link').click() - await browser.waitForElementByCss('#render-id') - - // Get the id again, and compare, they should not be the same. - const secondID = await browser.elementById('render-id').text() - expect(secondID).not.toBe(firstID) - } finally { - await browser.close() - } + it('should serve dynamic parameter', async () => { + const html = await renderViaHTTP(next.url, '/dashboard/deployments/123') + const $ = cheerio.load(html) + // Should include the page text with the parameter + expect($('p').text()).toBe( + 'hello from app/dashboard/deployments/[id]. ID is: 123' + ) }) - // TODO-APP: fix development test - it.skip('should hard replace', async () => { - const browser = await webdriver(next.url, '/link-hard-replace') + it('should include document html and body', async () => { + const html = await renderViaHTTP(next.url, '/dashboard') + const $ = cheerio.load(html) - try { - // Get the render ID so we can compare it. - const firstID = await browser.elementById('render-id').text() - - // Click the link on the page, and verify that the history entry was NOT - // added. - expect(await browser.eval('window.history.length')).toBe(2) - await browser.elementById('self-link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(2) - - // Get the date again, and compare, they should not be the same. - const secondID = await browser.elementById('render-id').text() - expect(secondID).not.toBe(firstID) - - // Navigate to the subpage, verify that the history entry was NOT added. - await browser.elementById('subpage-link').click() - await browser.waitForElementByCss('#back-link') - expect(await browser.eval('window.history.length')).toBe(2) - - // Navigate back again, verify that the history entry was NOT added. - await browser.elementById('back-link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(2) - - // Get the date again, and compare, they should not be the same. - const thirdID = await browser.elementById('render-id').text() - expect(thirdID).not.toBe(secondID) - } finally { - await browser.close() - } + expect($('html').hasClass('this-is-the-document-html')).toBeTruthy() + expect($('body').hasClass('this-is-the-document-body')).toBeTruthy() }) - it('should soft push', async () => { - const browser = await webdriver(next.url, '/link-soft-push') + it('should not serve when layout is provided but no folder index', async () => { + const res = await fetchViaHTTP(next.url, '/dashboard/deployments') + expect(res.status).toBe(404) + expect(await res.text()).toContain('This page could not be found') + }) - try { - // Click the link on the page, and verify that the history entry was - // added. - expect(await browser.eval('window.history.length')).toBe(2) - await browser.elementById('link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(3) - - // Get the id on the rendered page. - const firstID = await browser.elementById('render-id').text() - - // Go back, and redo the navigation by clicking the link. - await browser.back() - await browser.elementById('link').click() - - // Get the date again, and compare, they should be the same. - const secondID = await browser.elementById('render-id').text() - expect(firstID).toBe(secondID) - } finally { - await browser.close() - } + // TODO: do we want to make this only work for /root or is it allowed + // to work for /pages as well? + it.skip('should match partial parameters', async () => { + const html = await renderViaHTTP(next.url, '/partial-match-123') + expect(html).toContain('hello from app/partial-match-[id]. ID is: 123') }) - it('should soft replace', async () => { - const browser = await webdriver(next.url, '/link-soft-replace') + it('should support rewrites', async () => { + const html = await renderViaHTTP(next.url, '/rewritten-to-dashboard') + expect(html).toContain('hello from app/dashboard') + }) - try { - // Get the render ID so we can compare it. - const firstID = await browser.elementById('render-id').text() - - // Click the link on the page, and verify that the history entry was NOT - // added. - expect(await browser.eval('window.history.length')).toBe(2) - await browser.elementById('self-link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(2) - - // Get the id on the rendered page. - const secondID = await browser.elementById('render-id').text() - expect(secondID).toBe(firstID) - - // Navigate to the subpage, verify that the history entry was NOT added. - await browser.elementById('subpage-link').click() - await browser.waitForElementByCss('#back-link') - expect(await browser.eval('window.history.length')).toBe(2) - - // Navigate back again, verify that the history entry was NOT added. - await browser.elementById('back-link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(2) - - // Get the date again, and compare, they should be the same. - const thirdID = await browser.elementById('render-id').text() - expect(thirdID).toBe(firstID) - } finally { - await browser.close() + // TODO-APP: Enable in development + ;(isDev ? it.skip : it)( + 'should not rerender layout when navigating between routes in the same layout', + async () => { + const browser = await webdriver(next.url, '/same-layout/first') + + try { + // Get the render id from the dom and click the first link. + const firstRenderID = await browser.elementById('render-id').text() + await browser.elementById('link').click() + await browser.waitForElementByCss('#second-page') + + // Get the render id from the dom again, it should be the same! + const secondRenderID = await browser.elementById('render-id').text() + expect(secondRenderID).toBe(firstRenderID) + + // Navigate back to the first page again by clicking the link. + await browser.elementById('link').click() + await browser.waitForElementByCss('#first-page') + + // Get the render id from the dom again, it should be the same! + const thirdRenderID = await browser.elementById('render-id').text() + expect(thirdRenderID).toBe(firstRenderID) + } finally { + await browser.close() + } } - }) + ) - it('should be soft for back navigation', async () => { - const browser = await webdriver(next.url, '/with-id') + it('should handle hash in initial url', async () => { + const browser = await webdriver(next.url, '/dashboard#abc') try { - // Get the id on the rendered page. - const firstID = await browser.elementById('render-id').text() - - // Click the link, and go back. - await browser.elementById('link').click() - await browser.waitForElementByCss('#from-navigation') - await browser.back() - - // Get the date again, and compare, they should be the same. - const secondID = await browser.elementById('render-id').text() - expect(firstID).toBe(secondID) + // Check if hash is preserved + expect(await browser.eval('window.location.hash')).toBe('#abc') + await waitFor(1000) + // Check again to be sure as it might be timed different + expect(await browser.eval('window.location.hash')).toBe('#abc') } finally { await browser.close() } }) - it('should be soft for forward navigation', async () => { - const browser = await webdriver(next.url, '/with-id') - - try { - // Click the link. - await browser.elementById('link').click() - await browser.waitForElementByCss('#from-navigation') + describe('', () => { + // TODO-APP: fix development test + it.skip('should hard push', async () => { + const browser = await webdriver(next.url, '/link-hard-push') + + try { + // Click the link on the page, and verify that the history entry was + // added. + expect(await browser.eval('window.history.length')).toBe(2) + await browser.elementById('link').click() + await browser.waitForElementByCss('#render-id') + expect(await browser.eval('window.history.length')).toBe(3) + + // Get the id on the rendered page. + const firstID = await browser.elementById('render-id').text() + + // Go back, and redo the navigation by clicking the link. + await browser.back() + await browser.elementById('link').click() + await browser.waitForElementByCss('#render-id') + + // Get the id again, and compare, they should not be the same. + const secondID = await browser.elementById('render-id').text() + expect(secondID).not.toBe(firstID) + } finally { + await browser.close() + } + }) - // Get the id on the rendered page. - const firstID = await browser.elementById('render-id').text() + // TODO-APP: fix development test + it.skip('should hard replace', async () => { + const browser = await webdriver(next.url, '/link-hard-replace') + + try { + // Get the render ID so we can compare it. + const firstID = await browser.elementById('render-id').text() + + // Click the link on the page, and verify that the history entry was NOT + // added. + expect(await browser.eval('window.history.length')).toBe(2) + await browser.elementById('self-link').click() + await browser.waitForElementByCss('#render-id') + expect(await browser.eval('window.history.length')).toBe(2) + + // Get the date again, and compare, they should not be the same. + const secondID = await browser.elementById('render-id').text() + expect(secondID).not.toBe(firstID) + + // Navigate to the subpage, verify that the history entry was NOT added. + await browser.elementById('subpage-link').click() + await browser.waitForElementByCss('#back-link') + expect(await browser.eval('window.history.length')).toBe(2) + + // Navigate back again, verify that the history entry was NOT added. + await browser.elementById('back-link').click() + await browser.waitForElementByCss('#render-id') + expect(await browser.eval('window.history.length')).toBe(2) + + // Get the date again, and compare, they should not be the same. + const thirdID = await browser.elementById('render-id').text() + expect(thirdID).not.toBe(secondID) + } finally { + await browser.close() + } + }) - // Go back, then forward. - await browser.back() - await browser.forward() + it('should soft push', async () => { + const browser = await webdriver(next.url, '/link-soft-push') + + try { + // Click the link on the page, and verify that the history entry was + // added. + expect(await browser.eval('window.history.length')).toBe(2) + await browser.elementById('link').click() + await browser.waitForElementByCss('#render-id') + expect(await browser.eval('window.history.length')).toBe(3) + + // Get the id on the rendered page. + const firstID = await browser.elementById('render-id').text() + + // Go back, and redo the navigation by clicking the link. + await browser.back() + await browser.elementById('link').click() + + // Get the date again, and compare, they should be the same. + const secondID = await browser.elementById('render-id').text() + expect(firstID).toBe(secondID) + } finally { + await browser.close() + } + }) - // Get the date again, and compare, they should be the same. - const secondID = await browser.elementById('render-id').text() - expect(firstID).toBe(secondID) - } finally { - await browser.close() - } - }) + it('should soft replace', async () => { + const browser = await webdriver(next.url, '/link-soft-replace') + + try { + // Get the render ID so we can compare it. + const firstID = await browser.elementById('render-id').text() + + // Click the link on the page, and verify that the history entry was NOT + // added. + expect(await browser.eval('window.history.length')).toBe(2) + await browser.elementById('self-link').click() + await browser.waitForElementByCss('#render-id') + expect(await browser.eval('window.history.length')).toBe(2) + + // Get the id on the rendered page. + const secondID = await browser.elementById('render-id').text() + expect(secondID).toBe(firstID) + + // Navigate to the subpage, verify that the history entry was NOT added. + await browser.elementById('subpage-link').click() + await browser.waitForElementByCss('#back-link') + expect(await browser.eval('window.history.length')).toBe(2) + + // Navigate back again, verify that the history entry was NOT added. + await browser.elementById('back-link').click() + await browser.waitForElementByCss('#render-id') + expect(await browser.eval('window.history.length')).toBe(2) + + // Get the date again, and compare, they should be the same. + const thirdID = await browser.elementById('render-id').text() + expect(thirdID).toBe(firstID) + } finally { + await browser.close() + } + }) - it('should respect rewrites', async () => { - const browser = await webdriver(next.url, '/rewrites') + it('should be soft for back navigation', async () => { + const browser = await webdriver(next.url, '/with-id') - try { - // Click the link. - await browser.elementById('link').click() - await browser.waitForElementByCss('#from-dashboard') + try { + // Get the id on the rendered page. + const firstID = await browser.elementById('render-id').text() - // Check to see that we were rewritten and not redirected. - const pathname = await browser.eval('window.location.pathname') - expect(pathname).toBe('/rewritten-to-dashboard') + // Click the link, and go back. + await browser.elementById('link').click() + await browser.waitForElementByCss('#from-navigation') + await browser.back() - // Check to see that the page we navigated to is in fact the dashboard. - const html = await browser.eval( - 'window.document.documentElement.innerText' - ) - expect(html).toContain('hello from app/dashboard') - } finally { - await browser.close() - } - }) + // Get the date again, and compare, they should be the same. + const secondID = await browser.elementById('render-id').text() + expect(firstID).toBe(secondID) + } finally { + await browser.close() + } + }) - // TODO-APP: should enable when implemented - it.skip('should allow linking from app page to pages page', async () => { - const browser = await webdriver(next.url, '/pages-linking') + it('should be soft for forward navigation', async () => { + const browser = await webdriver(next.url, '/with-id') - try { - // Click the link. - await browser.elementById('app-link').click() - await browser.waitForElementByCss('#pages-link') + try { + // Click the link. + await browser.elementById('link').click() + await browser.waitForElementByCss('#from-navigation') - // Click the other link. - await browser.elementById('pages-link').click() - await browser.waitForElementByCss('#app-link') - } finally { - await browser.close() - } - }) - }) + // Get the id on the rendered page. + const firstID = await browser.elementById('render-id').text() - describe('server components', () => { - // TODO: why is this not servable but /dashboard+rootonly/hello.server.js - // should be? Seems like they both either should be servable or not - it('should not serve .server.js as a path', async () => { - // Without .server.js should serve - const html = await renderViaHTTP(next.url, '/should-not-serve-server') - expect(html).toContain('hello from app/should-not-serve-server') + // Go back, then forward. + await browser.back() + await browser.forward() - // Should not serve `.server` - const res = await fetchViaHTTP( - next.url, - '/should-not-serve-server.server' - ) - expect(res.status).toBe(404) - expect(await res.text()).toContain('This page could not be found') + // Get the date again, and compare, they should be the same. + const secondID = await browser.elementById('render-id').text() + expect(firstID).toBe(secondID) + } finally { + await browser.close() + } + }) - // Should not serve `.server.js` - const res2 = await fetchViaHTTP( - next.url, - '/should-not-serve-server.server.js' - ) - expect(res2.status).toBe(404) - expect(await res2.text()).toContain('This page could not be found') - }) + it('should respect rewrites', async () => { + const browser = await webdriver(next.url, '/rewrites') - it('should not serve .client.js as a path', async () => { - // Without .client.js should serve - const html = await renderViaHTTP(next.url, '/should-not-serve-client') - expect(html).toContain('hello from app/should-not-serve-client') + try { + // Click the link. + await browser.elementById('link').click() + await browser.waitForElementByCss('#from-dashboard') - // Should not serve `.client` - const res = await fetchViaHTTP( - next.url, - '/should-not-serve-client.client' - ) - expect(res.status).toBe(404) - expect(await res.text()).toContain('This page could not be found') + // Check to see that we were rewritten and not redirected. + const pathname = await browser.eval('window.location.pathname') + expect(pathname).toBe('/rewritten-to-dashboard') - // Should not serve `.client.js` - const res2 = await fetchViaHTTP( - next.url, - '/should-not-serve-client.client.js' - ) - expect(res2.status).toBe(404) - expect(await res2.text()).toContain('This page could not be found') - }) + // Check to see that the page we navigated to is in fact the dashboard. + const html = await browser.eval( + 'window.document.documentElement.innerText' + ) + expect(html).toContain('hello from app/dashboard') + } finally { + await browser.close() + } + }) - it('should serve shared component', async () => { - // Without .client.js should serve - const html = await renderViaHTTP(next.url, '/shared-component-route') - expect(html).toContain('hello from app/shared-component-route') + // TODO-APP: should enable when implemented + it.skip('should allow linking from app page to pages page', async () => { + const browser = await webdriver(next.url, '/pages-linking') + + try { + // Click the link. + await browser.elementById('app-link').click() + await browser.waitForElementByCss('#pages-link') + + // Click the other link. + await browser.elementById('pages-link').click() + await browser.waitForElementByCss('#app-link') + } finally { + await browser.close() + } + }) }) - describe('dynamic routes', () => { - it('should only pass params that apply to the layout', async () => { - const html = await renderViaHTTP(next.url, '/dynamic/books/hello-world') - const $ = cheerio.load(html) + describe('server components', () => { + // TODO: why is this not servable but /dashboard+rootonly/hello.server.js + // should be? Seems like they both either should be servable or not + it('should not serve .server.js as a path', async () => { + // Without .server.js should serve + const html = await renderViaHTTP(next.url, '/should-not-serve-server') + expect(html).toContain('hello from app/should-not-serve-server') - expect($('#dynamic-layout-params').text()).toBe('{}') - expect($('#category-layout-params').text()).toBe('{"category":"books"}') - expect($('#id-layout-params').text()).toBe( - '{"category":"books","id":"hello-world"}' - ) - expect($('#id-page-params').text()).toBe( - '{"category":"books","id":"hello-world"}' + // Should not serve `.server` + const res = await fetchViaHTTP( + next.url, + '/should-not-serve-server.server' ) - }) - }) + expect(res.status).toBe(404) + expect(await res.text()).toContain('This page could not be found') - describe('catch-all routes', () => { - it('should handle optional segments', async () => { - const params = ['this', 'is', 'a', 'test'] - const route = params.join('/') - const html = await renderViaHTTP( + // Should not serve `.server.js` + const res2 = await fetchViaHTTP( next.url, - `/optional-catch-all/${route}` + '/should-not-serve-server.server.js' ) - const $ = cheerio.load(html) - expect($('#text').attr('data-params')).toBe(route) + expect(res2.status).toBe(404) + expect(await res2.text()).toContain('This page could not be found') }) - it('should handle optional segments root', async () => { - const html = await renderViaHTTP(next.url, `/optional-catch-all`) - const $ = cheerio.load(html) - expect($('#text').attr('data-params')).toBe('') - }) - - it('should handle required segments', async () => { - const params = ['this', 'is', 'a', 'test'] - const route = params.join('/') - const html = await renderViaHTTP(next.url, `/catch-all/${route}`) - const $ = cheerio.load(html) - expect($('#text').attr('data-params')).toBe(route) - }) + it('should not serve .client.js as a path', async () => { + // Without .client.js should serve + const html = await renderViaHTTP(next.url, '/should-not-serve-client') + expect(html).toContain('hello from app/should-not-serve-client') - it('should handle required segments root as not found', async () => { - const res = await fetchViaHTTP(next.url, `/catch-all`) + // Should not serve `.client` + const res = await fetchViaHTTP( + next.url, + '/should-not-serve-client.client' + ) expect(res.status).toBe(404) expect(await res.text()).toContain('This page could not be found') - }) - }) - describe('should serve client component', () => { - it('should serve server-side', async () => { - const html = await renderViaHTTP(next.url, '/client-component-route') - const $ = cheerio.load(html) - expect($('p').text()).toBe( - 'hello from app/client-component-route. count: 0' - ) - }) - - // TODO: investigate hydration not kicking in on some runs - it.skip('should serve client-side', async () => { - const browser = await webdriver(next.url, '/client-component-route') - - // After hydration count should be 1 - expect(await browser.elementByCss('p').text()).toBe( - 'hello from app/client-component-route. count: 1' + // Should not serve `.client.js` + const res2 = await fetchViaHTTP( + next.url, + '/should-not-serve-client.client.js' ) + expect(res2.status).toBe(404) + expect(await res2.text()).toContain('This page could not be found') }) - }) - describe('should include client component layout with server component route', () => { - it('should include it server-side', async () => { - const html = await renderViaHTTP(next.url, '/client-nested') - const $ = cheerio.load(html) - // Should not be nested in dashboard - expect($('h1').text()).toBe('Client Nested. Count: 0') - // Should include the page text - expect($('p').text()).toBe('hello from app/client-nested') + it('should serve shared component', async () => { + // Without .client.js should serve + const html = await renderViaHTTP(next.url, '/shared-component-route') + expect(html).toContain('hello from app/shared-component-route') }) - // TODO: investigate hydration not kicking in on some runs - it.skip('should include it client-side', async () => { - const browser = await webdriver(next.url, '/client-nested') - - // After hydration count should be 1 - expect(await browser.elementByCss('h1').text()).toBe( - 'Client Nested. Count: 1' - ) + describe('dynamic routes', () => { + it('should only pass params that apply to the layout', async () => { + const html = await renderViaHTTP( + next.url, + '/dynamic/books/hello-world' + ) + const $ = cheerio.load(html) - // After hydration count should be 1 - expect(await browser.elementByCss('p').text()).toBe( - 'hello from app/client-nested' - ) + expect($('#dynamic-layout-params').text()).toBe('{}') + expect($('#category-layout-params').text()).toBe( + '{"category":"books"}' + ) + expect($('#id-layout-params').text()).toBe( + '{"category":"books","id":"hello-world"}' + ) + expect($('#id-page-params').text()).toBe( + '{"category":"books","id":"hello-world"}' + ) + }) }) - }) - describe('Loading', () => { - it('should render loading.js in initial html for slow page', async () => { - const html = await renderViaHTTP(next.url, '/slow-page-with-loading') - const $ = cheerio.load(html) + describe('catch-all routes', () => { + it('should handle optional segments', async () => { + const params = ['this', 'is', 'a', 'test'] + const route = params.join('/') + const html = await renderViaHTTP( + next.url, + `/optional-catch-all/${route}` + ) + const $ = cheerio.load(html) + expect($('#text').attr('data-params')).toBe(route) + }) - expect($('#loading').text()).toBe('Loading...') - }) + it('should handle optional segments root', async () => { + const html = await renderViaHTTP(next.url, `/optional-catch-all`) + const $ = cheerio.load(html) + expect($('#text').attr('data-params')).toBe('') + }) - it('should render loading.js in browser for slow page', async () => { - const browser = await webdriver(next.url, '/slow-page-with-loading', { - waitHydration: false, + it('should handle required segments', async () => { + const params = ['this', 'is', 'a', 'test'] + const route = params.join('/') + const html = await renderViaHTTP(next.url, `/catch-all/${route}`) + const $ = cheerio.load(html) + expect($('#text').attr('data-params')).toBe(route) }) - // TODO: `await webdriver()` causes waiting for the full page to complete streaming. At that point "Loading..." is replaced by the actual content - // expect(await browser.elementByCss('#loading').text()).toBe('Loading...') - expect(await browser.elementByCss('#slow-page-message').text()).toBe( - 'hello from slow page' - ) + it('should handle required segments root as not found', async () => { + const res = await fetchViaHTTP(next.url, `/catch-all`) + expect(res.status).toBe(404) + expect(await res.text()).toContain('This page could not be found') + }) }) - it('should render loading.js in initial html for slow layout', async () => { - const html = await renderViaHTTP( - next.url, - '/slow-layout-with-loading/slow' - ) - const $ = cheerio.load(html) + describe('should serve client component', () => { + it('should serve server-side', async () => { + const html = await renderViaHTTP(next.url, '/client-component-route') + const $ = cheerio.load(html) + expect($('p').text()).toBe( + 'hello from app/client-component-route. count: 0' + ) + }) - expect($('#loading').text()).toBe('Loading...') + // TODO: investigate hydration not kicking in on some runs + it.skip('should serve client-side', async () => { + const browser = await webdriver(next.url, '/client-component-route') + + // After hydration count should be 1 + expect(await browser.elementByCss('p').text()).toBe( + 'hello from app/client-component-route. count: 1' + ) + }) }) - it('should render loading.js in browser for slow layout', async () => { - const browser = await webdriver( - next.url, - '/slow-layout-with-loading/slow', - { - waitHydration: false, - } - ) - // TODO: `await webdriver()` causes waiting for the full page to complete streaming. At that point "Loading..." is replaced by the actual content - // expect(await browser.elementByCss('#loading').text()).toBe('Loading...') + describe('should include client component layout with server component route', () => { + it('should include it server-side', async () => { + const html = await renderViaHTTP(next.url, '/client-nested') + const $ = cheerio.load(html) + // Should not be nested in dashboard + expect($('h1').text()).toBe('Client Nested. Count: 0') + // Should include the page text + expect($('p').text()).toBe('hello from app/client-nested') + }) - expect(await browser.elementByCss('#slow-layout-message').text()).toBe( - 'hello from slow layout' - ) + // TODO: investigate hydration not kicking in on some runs + it.skip('should include it client-side', async () => { + const browser = await webdriver(next.url, '/client-nested') - expect(await browser.elementByCss('#page-message').text()).toBe( - 'Hello World' - ) + // After hydration count should be 1 + expect(await browser.elementByCss('h1').text()).toBe( + 'Client Nested. Count: 1' + ) + + // After hydration count should be 1 + expect(await browser.elementByCss('p').text()).toBe( + 'hello from app/client-nested' + ) + }) }) - it('should render loading.js in initial html for slow layout and page', async () => { - const html = await renderViaHTTP( - next.url, - '/slow-layout-and-page-with-loading/slow' - ) - const $ = cheerio.load(html) + describe('Loading', () => { + it('should render loading.js in initial html for slow page', async () => { + const html = await renderViaHTTP(next.url, '/slow-page-with-loading') + const $ = cheerio.load(html) - expect($('#loading-layout').text()).toBe('Loading layout...') - expect($('#loading-page').text()).toBe('Loading page...') - }) + expect($('#loading').text()).toBe('Loading...') + }) - it('should render loading.js in browser for slow layout and page', async () => { - const browser = await webdriver( - next.url, - '/slow-layout-and-page-with-loading/slow', - { + it('should render loading.js in browser for slow page', async () => { + const browser = await webdriver(next.url, '/slow-page-with-loading', { waitHydration: false, - } - ) - // TODO: `await webdriver()` causes waiting for the full page to complete streaming. At that point "Loading..." is replaced by the actual content - // expect(await browser.elementByCss('#loading-layout').text()).toBe('Loading...') - // expect(await browser.elementByCss('#loading-page').text()).toBe('Loading...') - - expect(await browser.elementByCss('#slow-layout-message').text()).toBe( - 'hello from slow layout' - ) + }) + // TODO: `await webdriver()` causes waiting for the full page to complete streaming. At that point "Loading..." is replaced by the actual content + // expect(await browser.elementByCss('#loading').text()).toBe('Loading...') - expect(await browser.elementByCss('#slow-page-message').text()).toBe( - 'hello from slow page' - ) - }) - }) + expect(await browser.elementByCss('#slow-page-message').text()).toBe( + 'hello from slow page' + ) + }) - describe('hooks', () => { - describe('useCookies', () => { - it('should retrive cookies in a server component', async () => { - const browser = await webdriver(next.url, '/hooks/use-cookies') + it('should render loading.js in initial html for slow layout', async () => { + const html = await renderViaHTTP( + next.url, + '/slow-layout-with-loading/slow' + ) + const $ = cheerio.load(html) - try { - await browser.waitForElementByCss('#does-not-have-cookie') - browser.addCookie({ name: 'use-cookies', value: 'value' }) - browser.refresh() + expect($('#loading').text()).toBe('Loading...') + }) - await browser.waitForElementByCss('#has-cookie') - browser.deleteCookies() - browser.refresh() + it('should render loading.js in browser for slow layout', async () => { + const browser = await webdriver( + next.url, + '/slow-layout-with-loading/slow', + { + waitHydration: false, + } + ) + // TODO: `await webdriver()` causes waiting for the full page to complete streaming. At that point "Loading..." is replaced by the actual content + // expect(await browser.elementByCss('#loading').text()).toBe('Loading...') - await browser.waitForElementByCss('#does-not-have-cookie') - } finally { - await browser.close() - } - }) + expect( + await browser.elementByCss('#slow-layout-message').text() + ).toBe('hello from slow layout') - it('should access cookies on navigation', async () => { - const browser = await webdriver(next.url, '/navigation') - - try { - // Click the cookies link to verify it can't see the cookie that's - // not there. - await browser.elementById('use-cookies').click() - await browser.waitForElementByCss('#does-not-have-cookie') - - // Go back and add the cookies. - await browser.back() - await browser.waitForElementByCss('#from-navigation') - browser.addCookie({ name: 'use-cookies', value: 'value' }) - - // Click the cookies link again to see that the cookie can be picked - // up again. - await browser.elementById('use-cookies').click() - await browser.waitForElementByCss('#has-cookie') - - // Go back and remove the cookies. - await browser.back() - await browser.waitForElementByCss('#from-navigation') - browser.deleteCookies() - - // Verify for the last time that after clicking the cookie link - // again, there are no cookies. - await browser.elementById('use-cookies').click() - await browser.waitForElementByCss('#does-not-have-cookie') - } finally { - await browser.close() - } + expect(await browser.elementByCss('#page-message').text()).toBe( + 'Hello World' + ) }) - }) - describe('useHeaders', () => { - it('should have access to incoming headers in a server component', async () => { - // Check to see that we can't see the header when it's not present. - let html = await renderViaHTTP( + it('should render loading.js in initial html for slow layout and page', async () => { + const html = await renderViaHTTP( next.url, - '/hooks/use-headers', - {}, - { headers: {} } + '/slow-layout-and-page-with-loading/slow' ) - let $ = cheerio.load(html) - expect($('#does-not-have-header').length).toBe(1) - expect($('#has-header').length).toBe(0) + const $ = cheerio.load(html) - // Check to see that we can see the header when it's present. - html = await renderViaHTTP( + expect($('#loading-layout').text()).toBe('Loading layout...') + expect($('#loading-page').text()).toBe('Loading page...') + }) + + it('should render loading.js in browser for slow layout and page', async () => { + const browser = await webdriver( next.url, - '/hooks/use-headers', - {}, - { headers: { 'x-use-headers': 'value' } } + '/slow-layout-and-page-with-loading/slow', + { + waitHydration: false, + } ) - $ = cheerio.load(html) - expect($('#has-header').length).toBe(1) - expect($('#does-not-have-header').length).toBe(0) - }) + // TODO: `await webdriver()` causes waiting for the full page to complete streaming. At that point "Loading..." is replaced by the actual content + // expect(await browser.elementByCss('#loading-layout').text()).toBe('Loading...') + // expect(await browser.elementByCss('#loading-page').text()).toBe('Loading...') - it('should access headers on navigation', async () => { - const browser = await webdriver(next.url, '/navigation') + expect( + await browser.elementByCss('#slow-layout-message').text() + ).toBe('hello from slow layout') - try { - await browser.elementById('use-headers').click() - await browser.waitForElementByCss('#has-referer') - } finally { - await browser.close() - } + expect(await browser.elementByCss('#slow-page-message').text()).toBe( + 'hello from slow page' + ) }) }) - describe('usePreviewData', () => { - it('should return no preview data when there is none', async () => { - const browser = await webdriver(next.url, '/hooks/use-preview-data') - - try { - await browser.waitForElementByCss('#does-not-have-preview-data') - } finally { - await browser.close() - } + describe('hooks', () => { + describe('useCookies', () => { + it('should retrive cookies in a server component', async () => { + const browser = await webdriver(next.url, '/hooks/use-cookies') + + try { + await browser.waitForElementByCss('#does-not-have-cookie') + browser.addCookie({ name: 'use-cookies', value: 'value' }) + browser.refresh() + + await browser.waitForElementByCss('#has-cookie') + browser.deleteCookies() + browser.refresh() + + await browser.waitForElementByCss('#does-not-have-cookie') + } finally { + await browser.close() + } + }) + + it('should access cookies on navigation', async () => { + const browser = await webdriver(next.url, '/navigation') + + try { + // Click the cookies link to verify it can't see the cookie that's + // not there. + await browser.elementById('use-cookies').click() + await browser.waitForElementByCss('#does-not-have-cookie') + + // Go back and add the cookies. + await browser.back() + await browser.waitForElementByCss('#from-navigation') + browser.addCookie({ name: 'use-cookies', value: 'value' }) + + // Click the cookies link again to see that the cookie can be picked + // up again. + await browser.elementById('use-cookies').click() + await browser.waitForElementByCss('#has-cookie') + + // Go back and remove the cookies. + await browser.back() + await browser.waitForElementByCss('#from-navigation') + browser.deleteCookies() + + // Verify for the last time that after clicking the cookie link + // again, there are no cookies. + await browser.elementById('use-cookies').click() + await browser.waitForElementByCss('#does-not-have-cookie') + } finally { + await browser.close() + } + }) }) - it('should return preview data when there is some', async () => { - const browser = await webdriver(next.url, '/api/preview') - - try { - await browser.loadPage(next.url + '/hooks/use-preview-data', { - disableCache: false, - beforePageLoad: null, - }) - await browser.waitForElementByCss('#has-preview-data') - } finally { - await browser.close() - } + describe('useHeaders', () => { + it('should have access to incoming headers in a server component', async () => { + // Check to see that we can't see the header when it's not present. + let html = await renderViaHTTP( + next.url, + '/hooks/use-headers', + {}, + { headers: {} } + ) + let $ = cheerio.load(html) + expect($('#does-not-have-header').length).toBe(1) + expect($('#has-header').length).toBe(0) + + // Check to see that we can see the header when it's present. + html = await renderViaHTTP( + next.url, + '/hooks/use-headers', + {}, + { headers: { 'x-use-headers': 'value' } } + ) + $ = cheerio.load(html) + expect($('#has-header').length).toBe(1) + expect($('#does-not-have-header').length).toBe(0) + }) + + it('should access headers on navigation', async () => { + const browser = await webdriver(next.url, '/navigation') + + try { + await browser.elementById('use-headers').click() + await browser.waitForElementByCss('#has-referer') + } finally { + await browser.close() + } + }) }) - }) - describe('useRouter', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP(next.url, '/hooks/use-router/server') - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('usePreviewData', () => { + it('should return no preview data when there is none', async () => { + const browser = await webdriver(next.url, '/hooks/use-preview-data') + + try { + await browser.waitForElementByCss('#does-not-have-preview-data') + } finally { + await browser.close() + } + }) + + it('should return preview data when there is some', async () => { + const browser = await webdriver(next.url, '/api/preview') + + try { + await browser.loadPage(next.url + '/hooks/use-preview-data', { + disableCache: false, + beforePageLoad: null, + }) + await browser.waitForElementByCss('#has-preview-data') + } finally { + await browser.close() + } + }) }) - }) - describe('useParams', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP(next.url, '/hooks/use-params/server') - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('useRouter', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP(next.url, '/hooks/use-router/server') + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('useSearchParams', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP( - next.url, - '/hooks/use-search-params/server' - ) - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('useParams', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP(next.url, '/hooks/use-params/server') + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('usePathname', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP(next.url, '/hooks/use-pathname/server') - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('useSearchParams', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-search-params/server' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('useLayoutSegments', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP( - next.url, - '/hooks/use-layout-segments/server' - ) - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('usePathname', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-pathname/server' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('useSelectedLayoutSegment', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP( - next.url, - '/hooks/use-selected-layout-segment/server' - ) - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('useLayoutSegments', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-layout-segments/server' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - }) - }) - describe('client components', () => { - describe('hooks', () => { - describe('useCookies', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP(next.url, '/hooks/use-cookies/client') - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('useSelectedLayoutSegment', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-selected-layout-segment/server' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) }) + }) - describe('usePreviewData', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP( - next.url, - '/hooks/use-preview-data/client' - ) - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('client components', () => { + describe('hooks', () => { + describe('useCookies', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-cookies/client' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('useHeaders', () => { - // TODO-APP: should enable when implemented - it.skip('should throw an error when imported', async () => { - const res = await fetchViaHTTP(next.url, '/hooks/use-headers/client') - expect(res.status).toBe(500) - expect(await res.text()).toContain('Internal Server Error') + describe('usePreviewData', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-preview-data/client' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('usePathname', () => { - it('should have the correct pathname', async () => { - const html = await renderViaHTTP(next.url, '/hooks/use-pathname') - const $ = cheerio.load(html) - expect($('#pathname').attr('data-pathname')).toBe( - '/hooks/use-pathname' - ) + describe('useHeaders', () => { + // TODO-APP: should enable when implemented + it.skip('should throw an error when imported', async () => { + const res = await fetchViaHTTP( + next.url, + '/hooks/use-headers/client' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain('Internal Server Error') + }) }) - }) - describe('useSearchParams', () => { - it('should have the correct search params', async () => { - const html = await renderViaHTTP( - next.url, - '/hooks/use-search-params?first=value&second=other%20value&third' - ) - const $ = cheerio.load(html) - const el = $('#params') - expect(el.attr('data-param-first')).toBe('value') - expect(el.attr('data-param-second')).toBe('other value') - expect(el.attr('data-param-third')).toBe('') - expect(el.attr('data-param-not-real')).toBe('N/A') + describe('usePathname', () => { + it('should have the correct pathname', async () => { + const html = await renderViaHTTP(next.url, '/hooks/use-pathname') + const $ = cheerio.load(html) + expect($('#pathname').attr('data-pathname')).toBe( + '/hooks/use-pathname' + ) + }) }) - }) - describe('useRouter', () => { - it('should allow access to the router', async () => { - const browser = await webdriver(next.url, '/hooks/use-router') - - try { - // Wait for the page to load, click the button (which uses a method - // on the router) and then wait for the correct page to load. - await browser.waitForElementByCss('#router') - await browser.elementById('button-push').click() - await browser.waitForElementByCss('#router-sub-page') - - // Go back (confirming we did do a hard push), and wait for the - // correct previous page. - await browser.back() - await browser.waitForElementByCss('#router') - } finally { - await browser.close() - } + describe('useSearchParams', () => { + it('should have the correct search params', async () => { + const html = await renderViaHTTP( + next.url, + '/hooks/use-search-params?first=value&second=other%20value&third' + ) + const $ = cheerio.load(html) + const el = $('#params') + expect(el.attr('data-param-first')).toBe('value') + expect(el.attr('data-param-second')).toBe('other value') + expect(el.attr('data-param-third')).toBe('') + expect(el.attr('data-param-not-real')).toBe('N/A') + }) }) - it('should have consistent query and params handling', async () => { - const html = await renderViaHTTP( - next.url, - '/param-and-query/params?slug=query' - ) - const $ = cheerio.load(html) - const el = $('#params-and-query') - expect(el.attr('data-params')).toBe('params') - expect(el.attr('data-query')).toBe('query') + describe('useRouter', () => { + it('should allow access to the router', async () => { + const browser = await webdriver(next.url, '/hooks/use-router') + + try { + // Wait for the page to load, click the button (which uses a method + // on the router) and then wait for the correct page to load. + await browser.waitForElementByCss('#router') + await browser.elementById('button-push').click() + await browser.waitForElementByCss('#router-sub-page') + + // Go back (confirming we did do a hard push), and wait for the + // correct previous page. + await browser.back() + await browser.waitForElementByCss('#router') + } finally { + await browser.close() + } + }) + + it('should have consistent query and params handling', async () => { + const html = await renderViaHTTP( + next.url, + '/param-and-query/params?slug=query' + ) + const $ = cheerio.load(html) + const el = $('#params-and-query') + expect(el.attr('data-params')).toBe('params') + expect(el.attr('data-query')).toBe('query') + }) }) }) - }) - it('should throw an error when getStaticProps is used', async () => { - const res = await fetchViaHTTP( - next.url, - '/client-with-errors/get-static-props' - ) - expect(res.status).toBe(500) - expect(await res.text()).toContain( - isDev - ? 'getStaticProps is not supported on Client Components' - : 'Internal Server Error' - ) - }) + it('should throw an error when getStaticProps is used', async () => { + const res = await fetchViaHTTP( + next.url, + '/client-with-errors/get-static-props' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain( + isDev + ? 'getStaticProps is not supported on Client Components' + : 'Internal Server Error' + ) + }) - it('should throw an error when getServerSideProps is used', async () => { - const res = await fetchViaHTTP( - next.url, - '/client-with-errors/get-server-side-props' - ) - expect(res.status).toBe(500) - expect(await res.text()).toContain( - isDev - ? 'getServerSideProps is not supported on Client Components' - : 'Internal Server Error' - ) + it('should throw an error when getServerSideProps is used', async () => { + const res = await fetchViaHTTP( + next.url, + '/client-with-errors/get-server-side-props' + ) + expect(res.status).toBe(500) + expect(await res.text()).toContain( + isDev + ? 'getServerSideProps is not supported on Client Components' + : 'Internal Server Error' + ) + }) }) - }) - describe('css support', () => { - describe('server layouts', () => { - it('should support global css inside server layouts', async () => { - const browser = await webdriver(next.url, '/dashboard') - - // Should body text in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('.p')).color` - ) - ).toBe('rgb(255, 0, 0)') + describe('css support', () => { + describe('server layouts', () => { + it('should support global css inside server layouts', async () => { + const browser = await webdriver(next.url, '/dashboard') + + // Should body text in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('.p')).color` + ) + ).toBe('rgb(255, 0, 0)') + + // Should inject global css for .green selectors + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('.green')).color` + ) + ).toBe('rgb(0, 128, 0)') + }) - // Should inject global css for .green selectors - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('.green')).color` - ) - ).toBe('rgb(0, 128, 0)') + it('should support css modules inside server layouts', async () => { + const browser = await webdriver(next.url, '/css/css-nested') + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('#server-cssm')).color` + ) + ).toBe('rgb(0, 128, 0)') + }) }) - it('should support css modules inside server layouts', async () => { - const browser = await webdriver(next.url, '/css/css-nested') - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('#server-cssm')).color` - ) - ).toBe('rgb(0, 128, 0)') + describe.skip('server pages', () => { + it('should support global css inside server pages', async () => {}) + it('should support css modules inside server pages', async () => {}) }) - }) - - describe.skip('server pages', () => { - it('should support global css inside server pages', async () => {}) - it('should support css modules inside server pages', async () => {}) - }) - describe('client layouts', () => { - it('should support css modules inside client layouts', async () => { - const browser = await webdriver(next.url, '/client-nested') + describe('client layouts', () => { + it('should support css modules inside client layouts', async () => { + const browser = await webdriver(next.url, '/client-nested') - // Should render h1 in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('h1')).color` - ) - ).toBe('rgb(255, 0, 0)') - }) + // Should render h1 in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(255, 0, 0)') + }) - it('should support global css inside client layouts', async () => { - const browser = await webdriver(next.url, '/client-nested') + it('should support global css inside client layouts', async () => { + const browser = await webdriver(next.url, '/client-nested') - // Should render button in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('button')).color` - ) - ).toBe('rgb(255, 0, 0)') + // Should render button in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('button')).color` + ) + ).toBe('rgb(255, 0, 0)') + }) }) - }) - describe('client pages', () => { - it('should support css modules inside client pages', async () => { - const browser = await webdriver(next.url, '/client-component-route') + describe('client pages', () => { + it('should support css modules inside client pages', async () => { + const browser = await webdriver(next.url, '/client-component-route') - // Should render p in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('p')).color` - ) - ).toBe('rgb(255, 0, 0)') - }) + // Should render p in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('p')).color` + ) + ).toBe('rgb(255, 0, 0)') + }) - it('should support global css inside client pages', async () => { - const browser = await webdriver(next.url, '/client-component-route') + it('should support global css inside client pages', async () => { + const browser = await webdriver(next.url, '/client-component-route') - // Should render `b` in blue - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('b')).color` - ) - ).toBe('rgb(0, 0, 255)') + // Should render `b` in blue + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('b')).color` + ) + ).toBe('rgb(0, 0, 255)') + }) }) }) + } + + describe('without assetPrefix', () => { + runTests({}) + }) + + describe('with assetPrefix', () => { + runTests({ assetPrefix: true }) }) }) From ad75204b34692efa24b4a10d74dd7964a2c10397 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 1 Aug 2022 17:51:35 -0500 Subject: [PATCH 04/10] v12.2.4-canary.9 --- 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 | 14 +++++++------- 16 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lerna.json b/lerna.json index e6b4046586cf..73dbc95dc565 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.2.4-canary.8" + "version": "12.2.4-canary.9" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index fdfc232dea84..136472f25514 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.4-canary.8", + "version": "12.2.4-canary.9", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index ff1a644e1143..17790b4a8979 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.4-canary.8", + "version": "12.2.4-canary.9", "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.4-canary.8", + "@next/eslint-plugin-next": "12.2.4-canary.9", "@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 4eb0bb2f4951..5d1d3bb35f20 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.4-canary.8", + "version": "12.2.4-canary.9", "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 e0bbf329b911..51eb5f71599f 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.4-canary.8", + "version": "12.2.4-canary.9", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index e7366968355a..e0937d1fc9dd 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.2.4-canary.8", + "version": "12.2.4-canary.9", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 58425cc10491..4e8ac67fe35a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.2.4-canary.8", + "version": "12.2.4-canary.9", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 38568a5a4438..e2f6667d68c4 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.2.4-canary.8", + "version": "12.2.4-canary.9", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 1cf728fbecac..258d9384b65c 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.4-canary.8", + "version": "12.2.4-canary.9", "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 77412d88fb30..41daf2adfce1 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.4-canary.8", + "version": "12.2.4-canary.9", "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 969a7eef0485..27682507d539 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.4-canary.8", + "version": "12.2.4-canary.9", "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 2bc63afe62e6..bae8d3801dfb 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.2.4-canary.8", + "version": "12.2.4-canary.9", "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 a974e66a11d0..8f92e4877b2d 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.2.4-canary.8", + "version": "12.2.4-canary.9", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,7 +70,7 @@ ] }, "dependencies": { - "@next/env": "12.2.4-canary.8", + "@next/env": "12.2.4-canary.9", "@swc/helpers": "0.4.3", "caniuse-lite": "^1.0.30001332", "postcss": "8.4.14", @@ -121,11 +121,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.7.0", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "12.2.4-canary.8", - "@next/polyfill-nomodule": "12.2.4-canary.8", - "@next/react-dev-overlay": "12.2.4-canary.8", - "@next/react-refresh-utils": "12.2.4-canary.8", - "@next/swc": "12.2.4-canary.8", + "@next/polyfill-module": "12.2.4-canary.9", + "@next/polyfill-nomodule": "12.2.4-canary.9", + "@next/react-dev-overlay": "12.2.4-canary.9", + "@next/react-refresh-utils": "12.2.4-canary.9", + "@next/swc": "12.2.4-canary.9", "@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 825d051440bc..948e01e13fba 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.4-canary.8", + "version": "12.2.4-canary.9", "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 6259d3e71f08..a4c576b0aea0 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.4-canary.8", + "version": "12.2.4-canary.9", "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 0bfddc53a0f8..0d3834857514 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -362,7 +362,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 12.2.4-canary.8 + '@next/eslint-plugin-next': 12.2.4-canary.9 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.21.0 eslint-import-resolver-node: ^0.3.6 @@ -418,12 +418,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.7.0 '@napi-rs/triples': 1.1.0 - '@next/env': 12.2.4-canary.8 - '@next/polyfill-module': 12.2.4-canary.8 - '@next/polyfill-nomodule': 12.2.4-canary.8 - '@next/react-dev-overlay': 12.2.4-canary.8 - '@next/react-refresh-utils': 12.2.4-canary.8 - '@next/swc': 12.2.4-canary.8 + '@next/env': 12.2.4-canary.9 + '@next/polyfill-module': 12.2.4-canary.9 + '@next/polyfill-nomodule': 12.2.4-canary.9 + '@next/react-dev-overlay': 12.2.4-canary.9 + '@next/react-refresh-utils': 12.2.4-canary.9 + '@next/swc': 12.2.4-canary.9 '@swc/helpers': 0.4.3 '@taskr/clear': 1.1.0 '@taskr/esnext': 1.1.0 From e3181c2d7798d801ac849aab02994932f76a8923 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Tue, 2 Aug 2022 05:04:08 +0300 Subject: [PATCH 05/10] Export URLPattern from next/server (#39219) This commit allows the users to import URLPattern from `next/server`, by defining a key that uses `global.URLPattern`. Why is this any good? or: why don't we add URLPattern to the global namespace? URLPattern is exposed as global on Edge Runtime _only_. This means that if we define a constructor in global namespace in our TypeScript definitions, people might have runtime errors in their Node.js functions. Importing from `next/server` enables users to get the constructor without risking in runtime errors and wrong type definitions. Keep in mind, that with the current implementation, we do not check if the constructor actually exists, but `next/server` shouldn't be imported in Node.js functions, AFAIK. ## Related - Fixes #38131 ## Bug - [x] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- package.json | 2 +- .../primitives/abort-controller.d.ts | 4 + .../primitives/abort-controller/package.json | 2 +- .../@edge-runtime/primitives/blob.d.ts | 3 + .../primitives/blob/package.json | 2 +- .../@edge-runtime/primitives/cache.d.ts | 12 + .../primitives/cache/package.json | 2 +- .../@edge-runtime/primitives/console.d.ts | 18 + .../primitives/console/package.json | 2 +- .../@edge-runtime/primitives/crypto.d.ts | 7 + .../primitives/crypto/package.json | 2 +- .../@edge-runtime/primitives/encoding.d.ts | 8 + .../primitives/encoding/package.json | 2 +- .../@edge-runtime/primitives/events.d.ts | 391 ++++++++++++++++++ .../primitives/events/package.json | 2 +- .../@edge-runtime/primitives/fetch.d.ts | 17 + .../@edge-runtime/primitives/fetch.js | 1 + .../primitives/fetch/package.json | 2 +- .../@edge-runtime/primitives/package.json | 2 +- .../@edge-runtime/primitives/streams.d.ts | 22 + .../primitives/streams/package.json | 2 +- .../primitives/structured-clone.d.ts | 3 + .../primitives/structured-clone/package.json | 2 +- .../@edge-runtime/primitives/url.d.ts | 52 +++ .../@edge-runtime/primitives/url/package.json | 2 +- packages/next/compiled/edge-runtime/index.js | 2 +- packages/next/package.json | 4 +- packages/next/server.d.ts | 1 + packages/next/server.js | 1 + packages/next/taskfile.js | 5 + pnpm-lock.yaml | 41 +- test/e2e/middleware-general/app/middleware.js | 4 +- .../app/middleware.js | 3 +- .../middleware-typescript/app/middleware.ts | 7 +- 34 files changed, 591 insertions(+), 41 deletions(-) create mode 100644 packages/next/compiled/@edge-runtime/primitives/abort-controller.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/blob.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/cache.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/console.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/crypto.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/encoding.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/events.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/fetch.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/streams.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/structured-clone.d.ts create mode 100644 packages/next/compiled/@edge-runtime/primitives/url.d.ts diff --git a/package.json b/package.json index 05cba00d0e49..881653e3f2bc 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@babel/plugin-proposal-object-rest-spread": "7.14.7", "@babel/preset-flow": "7.14.5", "@babel/preset-react": "7.14.5", - "@edge-runtime/jest-environment": "1.1.0-beta.23", + "@edge-runtime/jest-environment": "1.1.0-beta.25", "@fullhuman/postcss-purgecss": "1.3.0", "@mdx-js/loader": "0.18.0", "@next/bundle-analyzer": "workspace:*", diff --git a/packages/next/compiled/@edge-runtime/primitives/abort-controller.d.ts b/packages/next/compiled/@edge-runtime/primitives/abort-controller.d.ts new file mode 100644 index 000000000000..eb6041885419 --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/abort-controller.d.ts @@ -0,0 +1,4 @@ +declare const AbortControllerConstructor: typeof AbortController +declare const AbortSignalConstructor: typeof AbortSignal + +export { AbortControllerConstructor as AbortController, AbortSignalConstructor as AbortSignal }; diff --git a/packages/next/compiled/@edge-runtime/primitives/abort-controller/package.json b/packages/next/compiled/@edge-runtime/primitives/abort-controller/package.json index dd446039dec0..c91ba815ffb1 100644 --- a/packages/next/compiled/@edge-runtime/primitives/abort-controller/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/abort-controller/package.json @@ -1 +1 @@ -{"main":"../abort-controller.js"} +{"main":"../abort-controller.js","types":"../abort-controller.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/blob.d.ts b/packages/next/compiled/@edge-runtime/primitives/blob.d.ts new file mode 100644 index 000000000000..457984d6515d --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/blob.d.ts @@ -0,0 +1,3 @@ +declare const BlobConstructor: typeof Blob + +export { BlobConstructor as Blob }; diff --git a/packages/next/compiled/@edge-runtime/primitives/blob/package.json b/packages/next/compiled/@edge-runtime/primitives/blob/package.json index b8b407f5c3e8..d5fe70655a70 100644 --- a/packages/next/compiled/@edge-runtime/primitives/blob/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/blob/package.json @@ -1 +1 @@ -{"main":"../blob.js"} +{"main":"../blob.js","types":"../blob.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/cache.d.ts b/packages/next/compiled/@edge-runtime/primitives/cache.d.ts new file mode 100644 index 000000000000..59e3c66cad5e --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/cache.d.ts @@ -0,0 +1,12 @@ +declare function createCaches(): { + cacheStorage: () => CacheStorage + Cache: typeof Cache + CacheStorage: typeof CacheStorage +} + +declare const caches: CacheStorage + +declare const CacheStorageConstructor: typeof CacheStorage +declare const CacheConstructor: typeof Cache + +export { CacheConstructor as Cache, CacheStorageConstructor as CacheStorage, caches, createCaches }; diff --git a/packages/next/compiled/@edge-runtime/primitives/cache/package.json b/packages/next/compiled/@edge-runtime/primitives/cache/package.json index 15254d97238d..cdace7804d83 100644 --- a/packages/next/compiled/@edge-runtime/primitives/cache/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/cache/package.json @@ -1 +1 @@ -{"main":"../cache.js"} +{"main":"../cache.js","types":"../cache.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/console.d.ts b/packages/next/compiled/@edge-runtime/primitives/console.d.ts new file mode 100644 index 000000000000..027a071306ea --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/console.d.ts @@ -0,0 +1,18 @@ +interface IConsole { + assert: Console['assert'] + count: Console['count'] + debug: Console['debug'] + dir: Console['dir'] + error: Console['error'] + info: Console['info'] + log: Console['log'] + time: Console['time'] + timeEnd: Console['timeEnd'] + timeLog: Console['timeLog'] + trace: Console['trace'] + warn: Console['warn'] +} + +declare const console: IConsole + +export { console }; diff --git a/packages/next/compiled/@edge-runtime/primitives/console/package.json b/packages/next/compiled/@edge-runtime/primitives/console/package.json index 3581b4688ae5..2dd0d9af1fc6 100644 --- a/packages/next/compiled/@edge-runtime/primitives/console/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/console/package.json @@ -1 +1 @@ -{"main":"../console.js"} +{"main":"../console.js","types":"../console.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/crypto.d.ts b/packages/next/compiled/@edge-runtime/primitives/crypto.d.ts new file mode 100644 index 000000000000..9049fc46496b --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/crypto.d.ts @@ -0,0 +1,7 @@ +declare const crypto: Crypto + +declare const CryptoConstructor: typeof Crypto +declare const CryptoKeyConstructor: typeof CryptoKey +declare const SubtleCryptoConstructor: typeof SubtleCrypto + +export { CryptoConstructor as Crypto, CryptoKeyConstructor as CryptoKey, SubtleCryptoConstructor as SubtleCrypto, crypto }; diff --git a/packages/next/compiled/@edge-runtime/primitives/crypto/package.json b/packages/next/compiled/@edge-runtime/primitives/crypto/package.json index 0608e8fc1163..5e5c9c6c1f9f 100644 --- a/packages/next/compiled/@edge-runtime/primitives/crypto/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/crypto/package.json @@ -1 +1 @@ -{"main":"../crypto.js"} +{"main":"../crypto.js","types":"../crypto.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/encoding.d.ts b/packages/next/compiled/@edge-runtime/primitives/encoding.d.ts new file mode 100644 index 000000000000..fbb0627df868 --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/encoding.d.ts @@ -0,0 +1,8 @@ +declare const TextEncoderConstructor: typeof TextEncoder +declare const TextDecoderConstructor: typeof TextDecoder + + +declare const atob: (encoded: string) => string +declare const btoa: (str: string) => string + +export { TextDecoderConstructor as TextDecoder, TextEncoderConstructor as TextEncoder, atob, btoa }; diff --git a/packages/next/compiled/@edge-runtime/primitives/encoding/package.json b/packages/next/compiled/@edge-runtime/primitives/encoding/package.json index 80550e1a8e02..3fafbe5cdfe1 100644 --- a/packages/next/compiled/@edge-runtime/primitives/encoding/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/encoding/package.json @@ -1 +1 @@ -{"main":"../encoding.js"} +{"main":"../encoding.js","types":"../encoding.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/events.d.ts b/packages/next/compiled/@edge-runtime/primitives/events.d.ts new file mode 100644 index 000000000000..76e9a3456b37 --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/events.d.ts @@ -0,0 +1,391 @@ +/** + * `Event` interface. + * @see https://dom.spec.whatwg.org/#event + */ +interface Event$1 { + /** + * The type of this event. + */ + readonly type: string + + /** + * The target of this event. + */ + readonly target: EventTarget<{}, {}, "standard"> | null + + /** + * The current target of this event. + */ + readonly currentTarget: EventTarget<{}, {}, "standard"> | null + + /** + * The target of this event. + * @deprecated + */ + readonly srcElement: any | null + + /** + * The composed path of this event. + */ + composedPath(): EventTarget<{}, {}, "standard">[] + + /** + * Constant of NONE. + */ + readonly NONE: number + + /** + * Constant of CAPTURING_PHASE. + */ + readonly CAPTURING_PHASE: number + + /** + * Constant of BUBBLING_PHASE. + */ + readonly BUBBLING_PHASE: number + + /** + * Constant of AT_TARGET. + */ + readonly AT_TARGET: number + + /** + * Indicates which phase of the event flow is currently being evaluated. + */ + readonly eventPhase: number + + /** + * Stop event bubbling. + */ + stopPropagation(): void + + /** + * Stop event bubbling. + */ + stopImmediatePropagation(): void + + /** + * Initialize event. + * @deprecated + */ + initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void + + /** + * The flag indicating bubbling. + */ + readonly bubbles: boolean + + /** + * Stop event bubbling. + * @deprecated + */ + cancelBubble: boolean + + /** + * Set or get cancellation flag. + * @deprecated + */ + returnValue: boolean + + /** + * The flag indicating whether the event can be canceled. + */ + readonly cancelable: boolean + + /** + * Cancel this event. + */ + preventDefault(): void + + /** + * The flag to indicating whether the event was canceled. + */ + readonly defaultPrevented: boolean + + /** + * The flag to indicating if event is composed. + */ + readonly composed: boolean + + /** + * Indicates whether the event was dispatched by the user agent. + */ + readonly isTrusted: boolean + + /** + * The unix time of this event. + */ + readonly timeStamp: number +} + +/** + * The constructor of `EventTarget` interface. + */ +type EventTargetConstructor$1< + TEvents extends EventTarget.EventDefinition = {}, + TEventAttributes extends EventTarget.EventDefinition = {}, + TMode extends EventTarget.Mode = "loose" +> = { + prototype: EventTarget + new(): EventTarget +} + +/** + * `EventTarget` interface. + * @see https://dom.spec.whatwg.org/#interface-eventtarget + */ +type EventTarget< + TEvents extends EventTarget.EventDefinition = {}, + TEventAttributes extends EventTarget.EventDefinition = {}, + TMode extends EventTarget.Mode = "loose" +> = EventTarget.EventAttributes & { + /** + * Add a given listener to this event target. + * @param eventName The event name to add. + * @param listener The listener to add. + * @param options The options for this listener. + */ + addEventListener>( + type: TEventType, + listener: + | EventTarget.Listener> + | null, + options?: boolean | EventTarget.AddOptions + ): void + + /** + * Remove a given listener from this event target. + * @param eventName The event name to remove. + * @param listener The listener to remove. + * @param options The options for this listener. + */ + removeEventListener>( + type: TEventType, + listener: + | EventTarget.Listener> + | null, + options?: boolean | EventTarget.RemoveOptions + ): void + + /** + * Dispatch a given event. + * @param event The event to dispatch. + * @returns `false` if canceled. + */ + dispatchEvent>( + event: EventTarget.EventData + ): boolean +} + +declare const EventTarget: EventTargetConstructor$1 & { + /** + * Create an `EventTarget` instance with detailed event definition. + * + * The detailed event definition requires to use `defineEventAttribute()` + * function later. + * + * Unfortunately, the second type parameter `TEventAttributes` was needed + * because we cannot compute string literal types. + * + * @example + * const signal = new EventTarget<{ abort: Event }, { onabort: Event }>() + * defineEventAttribute(signal, "abort") + */ + new < + TEvents extends EventTarget.EventDefinition, + TEventAttributes extends EventTarget.EventDefinition, + TMode extends EventTarget.Mode = "loose" + >(): EventTarget + + /** + * Define an `EventTarget` constructor with attribute events and detailed event definition. + * + * Unfortunately, the second type parameter `TEventAttributes` was needed + * because we cannot compute string literal types. + * + * @example + * class AbortSignal extends EventTarget<{ abort: Event }, { onabort: Event }>("abort") { + * abort(): void {} + * } + * + * @param events Optional event attributes (e.g. passing in `"click"` adds `onclick` to prototype). + */ + < + TEvents extends EventTarget.EventDefinition = {}, + TEventAttributes extends EventTarget.EventDefinition = {}, + TMode extends EventTarget.Mode = "loose" + >(events: string[]): EventTargetConstructor$1< + TEvents, + TEventAttributes, + TMode + > + + /** + * Define an `EventTarget` constructor with attribute events and detailed event definition. + * + * Unfortunately, the second type parameter `TEventAttributes` was needed + * because we cannot compute string literal types. + * + * @example + * class AbortSignal extends EventTarget<{ abort: Event }, { onabort: Event }>("abort") { + * abort(): void {} + * } + * + * @param events Optional event attributes (e.g. passing in `"click"` adds `onclick` to prototype). + */ + < + TEvents extends EventTarget.EventDefinition = {}, + TEventAttributes extends EventTarget.EventDefinition = {}, + TMode extends EventTarget.Mode = "loose" + >(event0: string, ...events: string[]): EventTargetConstructor$1< + TEvents, + TEventAttributes, + TMode + > +} + +declare namespace EventTarget { + /** + * Options of `removeEventListener()` method. + */ + export interface RemoveOptions { + /** + * The flag to indicate that the listener is for the capturing phase. + */ + capture?: boolean + } + + /** + * Options of `addEventListener()` method. + */ + export interface AddOptions extends RemoveOptions { + /** + * The flag to indicate that the listener doesn't support + * `event.preventDefault()` operation. + */ + passive?: boolean + /** + * The flag to indicate that the listener will be removed on the first + * event. + */ + once?: boolean + } + + /** + * The type of regular listeners. + */ + export interface FunctionListener { + (event: TEvent): void + } + + /** + * The type of object listeners. + */ + export interface ObjectListener { + handleEvent(event: TEvent): void + } + + /** + * The type of listeners. + */ + export type Listener = + | FunctionListener + | ObjectListener + + /** + * Event definition. + */ + export type EventDefinition = { + readonly [key: string]: Event$1 + } + + /** + * Mapped type for event attributes. + */ + export type EventAttributes = { + [P in keyof TEventAttributes]: + | FunctionListener + | null + } + + /** + * The type of event data for `dispatchEvent()` method. + */ + export type EventData< + TEvents extends EventDefinition, + TEventType extends keyof TEvents | string, + TMode extends Mode + > = + TEventType extends keyof TEvents + ? ( + // Require properties which are not generated automatically. + & Pick< + TEvents[TEventType], + Exclude + > + // Properties which are generated automatically are optional. + & Partial> + ) + : ( + TMode extends "standard" + ? Event$1 + : Event$1 | NonStandardEvent + ) + + /** + * The string literal types of the properties which are generated + * automatically in `dispatchEvent()` method. + */ + export type OmittableEventKeys = Exclude + + /** + * The type of event data. + */ + export type NonStandardEvent = { + [key: string]: any + type: string + } + + /** + * The type of listeners. + */ + export type PickEvent< + TEvents extends EventDefinition, + TEventType extends keyof TEvents | string, + > = + TEventType extends keyof TEvents + ? TEvents[TEventType] + : Event$1 + + /** + * Event type candidates. + */ + export type EventType< + TEvents extends EventDefinition, + TMode extends Mode + > = + TMode extends "strict" + ? keyof TEvents + : keyof TEvents | string + + /** + * - `"strict"` ..... Methods don't accept unknown events. + * `dispatchEvent()` accepts partial objects. + * - `"loose"` ...... Methods accept unknown events. + * `dispatchEvent()` accepts partial objects. + * - `"standard"` ... Methods accept unknown events. + * `dispatchEvent()` doesn't accept partial objects. + */ + export type Mode = "strict" | "standard" | "loose" +} + +declare const EventTargetConstructor: typeof EventTarget +declare const EventConstructor: typeof Event + + +declare class FetchEvent { + awaiting: Set> + constructor(request: Request) +} + +export { EventConstructor as Event, EventTargetConstructor as EventTarget, FetchEvent, EventTarget as PromiseRejectionEvent }; diff --git a/packages/next/compiled/@edge-runtime/primitives/events/package.json b/packages/next/compiled/@edge-runtime/primitives/events/package.json index 5744ecd2bfe8..4b09f8698aec 100644 --- a/packages/next/compiled/@edge-runtime/primitives/events/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/events/package.json @@ -1 +1 @@ -{"main":"../events.js"} +{"main":"../events.js","types":"../events.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/fetch.d.ts b/packages/next/compiled/@edge-runtime/primitives/fetch.d.ts new file mode 100644 index 000000000000..36f11f031d1c --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/fetch.d.ts @@ -0,0 +1,17 @@ +declare class Headers extends globalThis.Headers { + getAll(key: 'set-cookie'): string[] +} + +declare class Request extends globalThis.Request { + readonly headers: Headers +} + +declare class Response extends globalThis.Response { + readonly headers: Headers +} + +declare const fetchImplementation: typeof fetch +declare const FileConstructor: typeof File +declare const FormDataConstructor: typeof FormData + +export { FileConstructor as File, FormDataConstructor as FormData, Headers, Request, Response, fetchImplementation as fetch }; diff --git a/packages/next/compiled/@edge-runtime/primitives/fetch.js b/packages/next/compiled/@edge-runtime/primitives/fetch.js index 1cb11622fddb..c0d8295169ff 100644 --- a/packages/next/compiled/@edge-runtime/primitives/fetch.js +++ b/packages/next/compiled/@edge-runtime/primitives/fetch.js @@ -6658,6 +6658,7 @@ var import_request = __toESM(require_request()); var import_file = __toESM(require_file()); global.AbortController = import_abort_controller.AbortController; global.AbortSignal = import_abort_controller2.AbortSignal; +define_process_default.nextTick = setImmediate; var SCookies = Symbol("set-cookie"); var __append = HeadersModule.HeadersList.prototype.append; HeadersModule.HeadersList.prototype.append = function(name, value) { diff --git a/packages/next/compiled/@edge-runtime/primitives/fetch/package.json b/packages/next/compiled/@edge-runtime/primitives/fetch/package.json index 7b1f8b2ccf9f..60c46ac57cf1 100644 --- a/packages/next/compiled/@edge-runtime/primitives/fetch/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/fetch/package.json @@ -1 +1 @@ -{"main":"../fetch.js"} +{"main":"../fetch.js","types":"../fetch.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/package.json b/packages/next/compiled/@edge-runtime/primitives/package.json index 1adaa1c02e9e..835ca8cc3c16 100644 --- a/packages/next/compiled/@edge-runtime/primitives/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/package.json @@ -1 +1 @@ -{"name":"@edge-runtime/primitives","version":"1.1.0-beta.23","main":"./index.js","license":"MPLv2"} +{"name":"@edge-runtime/primitives","version":"1.1.0-beta.25","main":"./index.js","license":"MPLv2"} diff --git a/packages/next/compiled/@edge-runtime/primitives/streams.d.ts b/packages/next/compiled/@edge-runtime/primitives/streams.d.ts new file mode 100644 index 000000000000..f1be6142f69d --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/streams.d.ts @@ -0,0 +1,22 @@ +/** + * The type of `ReadableStreamBYOBReader` is not included in Typescript so we + * are declaring it inline to not have to worry about bundling. + */ +declare class ReadableStreamBYOBReader { + constructor(stream: ReadableStream) + get closed(): Promise + cancel(reason?: any): Promise + read( + view: T + ): Promise<{ done: false; value: T } | { done: true; value: T | undefined }> + releaseLock(): void +} + +declare const ReadableStreamConstructor: typeof ReadableStream +declare const ReadableStreamBYOBReaderConstructor: typeof ReadableStreamBYOBReader +declare const ReadableStreamDefaultReaderConstructor: typeof ReadableStreamDefaultReader +declare const TransformStreamConstructor: typeof TransformStream +declare const WritableStreamConstructor: typeof WritableStream +declare const WritableStreamDefaultWriterConstructor: typeof WritableStreamDefaultWriter + +export { ReadableStreamConstructor as ReadableStream, ReadableStreamBYOBReaderConstructor as ReadableStreamBYOBReader, ReadableStreamDefaultReaderConstructor as ReadableStreamDefaultReader, TransformStreamConstructor as TransformStream, WritableStreamConstructor as WritableStream, WritableStreamDefaultWriterConstructor as WritableStreamDefaultWriter }; diff --git a/packages/next/compiled/@edge-runtime/primitives/streams/package.json b/packages/next/compiled/@edge-runtime/primitives/streams/package.json index f66ceb0d7515..4a9072f961bd 100644 --- a/packages/next/compiled/@edge-runtime/primitives/streams/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/streams/package.json @@ -1 +1 @@ -{"main":"../streams.js"} +{"main":"../streams.js","types":"../streams.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/structured-clone.d.ts b/packages/next/compiled/@edge-runtime/primitives/structured-clone.d.ts new file mode 100644 index 000000000000..fecd33be123b --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/structured-clone.d.ts @@ -0,0 +1,3 @@ +declare const structuredClone: (any: T, options?: { lossy?: boolean }) => T + +export { structuredClone }; diff --git a/packages/next/compiled/@edge-runtime/primitives/structured-clone/package.json b/packages/next/compiled/@edge-runtime/primitives/structured-clone/package.json index 11189f898641..328d06c27928 100644 --- a/packages/next/compiled/@edge-runtime/primitives/structured-clone/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/structured-clone/package.json @@ -1 +1 @@ -{"main":"../structured-clone.js"} +{"main":"../structured-clone.js","types":"../structured-clone.d.ts"} diff --git a/packages/next/compiled/@edge-runtime/primitives/url.d.ts b/packages/next/compiled/@edge-runtime/primitives/url.d.ts new file mode 100644 index 000000000000..bf30268d5235 --- /dev/null +++ b/packages/next/compiled/@edge-runtime/primitives/url.d.ts @@ -0,0 +1,52 @@ +type URLPatternInput = URLPatternInit | string + +declare class URLPattern { + constructor(init?: URLPatternInput, baseURL?: string) + test(input?: URLPatternInput, baseURL?: string): boolean + exec(input?: URLPatternInput, baseURL?: string): URLPatternResult | null + readonly protocol: string + readonly username: string + readonly password: string + readonly hostname: string + readonly port: string + readonly pathname: string + readonly search: string + readonly hash: string +} + +interface URLPatternInit { + baseURL?: string + username?: string + password?: string + protocol?: string + hostname?: string + port?: string + pathname?: string + search?: string + hash?: string +} + +interface URLPatternResult { + inputs: [URLPatternInput] + protocol: URLPatternComponentResult + username: URLPatternComponentResult + password: URLPatternComponentResult + hostname: URLPatternComponentResult + port: URLPatternComponentResult + pathname: URLPatternComponentResult + search: URLPatternComponentResult + hash: URLPatternComponentResult +} + +interface URLPatternComponentResult { + input: string + groups: { + [key: string]: string | undefined + } +} + +declare const URLPatternConstructor: typeof URLPattern +declare const URLConstructor: typeof URL +declare const URLSearchParamsConstructor: typeof URLSearchParams + +export { URLConstructor as URL, URLPatternConstructor as URLPattern, URLSearchParamsConstructor as URLSearchParams }; diff --git a/packages/next/compiled/@edge-runtime/primitives/url/package.json b/packages/next/compiled/@edge-runtime/primitives/url/package.json index 8e96fd91df6a..5744fda3eda9 100644 --- a/packages/next/compiled/@edge-runtime/primitives/url/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/url/package.json @@ -1 +1 @@ -{"main":"../url.js"} +{"main":"../url.js","types":"../url.d.ts"} diff --git a/packages/next/compiled/edge-runtime/index.js b/packages/next/compiled/edge-runtime/index.js index 931ce1ef213e..a042b3d70eb8 100644 --- a/packages/next/compiled/edge-runtime/index.js +++ b/packages/next/compiled/edge-runtime/index.js @@ -1 +1 @@ -(()=>{var __webpack_modules__={416:(__unused_webpack_module,exports,__nccwpck_require__)=>{"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EdgeVM=void 0;const buffer_1=__nccwpck_require__(300);const require_1=__nccwpck_require__(263);const vm_1=__nccwpck_require__(235);class EdgeVM extends vm_1.VM{constructor(e={}){super({...e,extend:t=>e.extend?e.extend(addPrimitives(t)):addPrimitives(t)})}}exports.EdgeVM=EdgeVM;function addPrimitives(context){defineProperty(context,"self",{enumerable:true,value:context});defineProperty(context,"globalThis",{value:context});defineProperty(context,"Symbol",{value:Symbol});defineProperty(context,"clearInterval",{value:clearInterval});defineProperty(context,"clearTimeout",{value:clearTimeout});defineProperty(context,"setInterval",{value:setInterval});defineProperty(context,"setTimeout",{value:setTimeout});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/console"),scopedContext:{console:console}}),nonenumerable:["console"]});const encodings=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/encoding"),scopedContext:{Buffer:buffer_1.Buffer,global:{ArrayBuffer:ArrayBuffer}}});defineProperties(context,{exports:encodings,nonenumerable:["atob","btoa","TextEncoder","TextDecoder"]});const streams=(0,require_1.requireWithCache)({path:require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),context:context});defineProperties(context,{exports:streams,nonenumerable:["ReadableStream","ReadableStreamBYOBReader","ReadableStreamDefaultReader","TransformStream","WritableStream","WritableStreamDefaultWriter"]});const abort=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/abort-controller")});defineProperties(context,{exports:abort,nonenumerable:["AbortController","AbortSignal"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([["punycode",{exports:__nccwpck_require__(477)}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/url"),scopedContext:{TextEncoder:encodings.TextEncoder,TextDecoder:encodings.TextDecoder}}),nonenumerable:["URL","URLSearchParams","URLPattern"]});const blob=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/blob")});defineProperties(context,{exports:blob,nonenumerable:["Blob"]});const webFetch=(0,require_1.requireWithCache)({context:context,cache:new Map([["abort-controller",{exports:abort}],["assert",{exports:__nccwpck_require__(491)}],["buffer",{exports:__nccwpck_require__(300)}],["events",{exports:__nccwpck_require__(361)}],["http",{exports:__nccwpck_require__(685)}],["net",{exports:__nccwpck_require__(808)}],["perf_hooks",{exports:__nccwpck_require__(74)}],["stream",{exports:__nccwpck_require__(781)}],["tls",{exports:__nccwpck_require__(404)}],["util",{exports:__nccwpck_require__(837)}],["zlib",{exports:__nccwpck_require__(796)}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),{exports:streams}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/blob"),{exports:blob}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),scopedContext:{Buffer:buffer_1.Buffer,FinalizationRegistry:function(){return{register:function(){}}},global:{},queueMicrotask:queueMicrotask,setImmediate:setImmediate}});defineProperties(context,{exports:webFetch,nonenumerable:["fetch","File","FormData","Headers","Request","Response"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([[require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),{exports:webFetch}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/cache"),scopedContext:{global:{}}}),enumerable:["caches"],nonenumerable:["Cache","CacheStorage"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,cache:new Map([["crypto",{exports:__nccwpck_require__(113)}],["process",{exports:__nccwpck_require__(282)}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/crypto"),scopedContext:{Buffer:buffer_1.Buffer}}),enumerable:["crypto"],nonenumerable:["Crypto","CryptoKey","SubtleCrypto"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/events")}),nonenumerable:["Event","EventTarget","FetchEvent","PromiseRejectionEvent"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/structured-clone")}),nonenumerable:["structuredClone"]});return context}function defineProperty(e,t,r){var n,o,s;Object.defineProperty(e,t,{configurable:(n=r.configurable)!==null&&n!==void 0?n:false,enumerable:(o=r.enumerable)!==null&&o!==void 0?o:false,value:r.value,writable:(s=r.writable)!==null&&s!==void 0?s:true})}function defineProperties(e,t){var r,n;for(const n of(r=t.enumerable)!==null&&r!==void 0?r:[]){if(!t.exports[n]){throw new Error(`Attempt to export a nullable value for "${n}"`)}defineProperty(e,n,{enumerable:true,value:t.exports[n]})}for(const r of(n=t.nonenumerable)!==null&&n!==void 0?n:[]){if(!t.exports[r]){throw new Error(`Attempt to export a nullable value for "${r}"`)}defineProperty(e,r,{value:t.exports[r]})}}},574:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=t.EdgeVM=void 0;var n=r(416);Object.defineProperty(t,"EdgeVM",{enumerable:true,get:function(){return n.EdgeVM}});var o=r(235);Object.defineProperty(t,"VM",{enumerable:true,get:function(){return o.VM}})},263:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.requireWithCache=t.createRequire=t.requireDependencies=void 0;const n=r(147);const o=r(144);const s=r(17);function requireDependencies(e){const{context:t,requireCache:r,dependencies:n}=e;const o=createRequire(t,r);for(const{path:e,mapExports:r}of n){const n=o(e,e);for(const e of Object.keys(r)){t[r[e]]=n[e]}}}t.requireDependencies=requireDependencies;function createRequire(e,t,r,i={}){return function requireFn(a,E){const c=require.resolve(E,{paths:[(0,s.dirname)(a)]});const u=t.get(E)||t.get(c);if(u!==undefined&&u!==null){return u.exports}const _={exports:{},loaded:false,id:c};t.set(c,_);r===null||r===void 0?void 0:r.add(c);const d=(0,o.runInContext)(`(function(module,exports,require,__dirname,__filename,${Object.keys(i).join(",")}) {${(0,n.readFileSync)(c,"utf-8")}\n})`,e);try{d(_,_.exports,requireFn.bind(null,c),(0,s.dirname)(c),c,...Object.values(i))}catch(e){t.delete(c);throw e}_.loaded=true;return _.exports}}t.createRequire=createRequire;function requireWithCache(e){var t;return createRequire(e.context,(t=e.cache)!==null&&t!==void 0?t:new Map,e.references,e.scopedContext).call(null,e.path,e.path)}t.requireWithCache=requireWithCache},145:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.tempFile=void 0;const o=r(277);const s=n(r(17));const i=n(r(147));const a=n(r(37));function tempFile(e){const t=s.default.join(a.default.tmpdir(),o.crypto.randomUUID());i.default.writeFileSync(t,e);return{path:t,remove:()=>i.default.unlinkSync(t)}}t.tempFile=tempFile},235:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=void 0;const n=r(144);const o=r(263);const s=r(145);class VM{constructor(e={}){var t,r,s,i;const a=(0,n.createContext)({},{name:"Edge Runtime",codeGeneration:(t=e.codeGeneration)!==null&&t!==void 0?t:{strings:false,wasm:true}});this.requireCache=(r=e.requireCache)!==null&&r!==void 0?r:new Map;this.context=(i=(s=e.extend)===null||s===void 0?void 0:s.call(e,a))!==null&&i!==void 0?i:a;this.requireFn=(0,o.createRequire)(this.context,this.requireCache)}evaluate(e){return(0,n.runInContext)(e,this.context)}require(e){return this.requireFn(e,e)}requireInContext(e){const t=this.require(e);for(const[e,r]of Object.entries(t)){this.context[e]=r}}requireInlineInContext(e){const t=(0,s.tempFile)(e);this.requireInContext(t.path);t.remove()}}t.VM=VM},734:e=>{"use strict";e.exports=e=>{const t=e[0]*1e9+e[1];const r=t/1e6;const n=t/1e9;return{seconds:n,milliseconds:r,nanoseconds:t}}},26:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.EdgeRuntime=void 0;const n=r(574);let o;let s;class EdgeRuntime extends n.EdgeVM{constructor(e){super({...e,extend:t=>{var r,n;return(n=(r=e===null||e===void 0?void 0:e.extend)===null||r===void 0?void 0:r.call(e,t))!==null&&n!==void 0?n:t}});defineHandlerProps({object:this,setterName:"__onUnhandledRejectionHandler",setter:e=>o=e,getterName:"__rejectionHandlers",getter:()=>o});defineHandlerProps({object:this,setterName:"__onErrorHandler",setter:e=>s=e,getterName:"__errorHandlers",getter:()=>s});Object.defineProperty(this.context,"EdgeRuntime",{configurable:false,enumerable:false,writable:false,value:"edge-runtime"});this.evaluate(getDefineEventListenersCode());this.dispatchFetch=this.evaluate(getDispatchFetchCode());if(e===null||e===void 0?void 0:e.initialCode){this.evaluate(e.initialCode)}}}t.EdgeRuntime=EdgeRuntime;process.on("unhandledRejection",(function invokeRejectionHandlers(e,t){o===null||o===void 0?void 0:o.forEach((r=>r(e,t)))}));process.on("uncaughtException",(function invokeErrorHandlers(e){s===null||s===void 0?void 0:s.forEach((t=>t(e)))}));function getDefineEventListenersCode(){return`\n Object.defineProperty(self, '__listeners', {\n configurable: false,\n enumerable: false,\n value: {},\n writable: true,\n })\n\n function __conditionallyUpdatesHandlerList(eventType) {\n if (eventType === 'unhandledrejection') {\n self.__onUnhandledRejectionHandler = self.__listeners[eventType];\n } else if (eventType === 'error') {\n self.__onErrorHandler = self.__listeners[eventType];\n }\n }\n\n function addEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (eventType === 'fetch' && self.__listeners.fetch) {\n throw new TypeError('You can register just one "fetch" event listener');\n }\n\n self.__listeners[eventType] = self.__listeners[eventType] || [];\n self.__listeners[eventType].push(handler);\n __conditionallyUpdatesHandlerList(eventType);\n }\n\n function removeEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (self.__listeners[eventType]) {\n self.__listeners[eventType] = self.__listeners[eventType].filter(item => {\n return item !== handler;\n });\n\n if (self.__listeners[eventType].length === 0) {\n delete self.__listeners[eventType];\n }\n }\n __conditionallyUpdatesHandlerList(eventType);\n }\n `}function getDispatchFetchCode(){return`(async function dispatchFetch(input, init) {\n const request = new Request(input, init);\n const event = new FetchEvent(request);\n if (!self.__listeners.fetch) {\n throw new Error("No fetch event listeners found");\n }\n\n const getResponse = ({ response, error }) => {\n if (error || !response || !(response instanceof Response)) {\n console.error(error ? error : 'The event listener did not respond')\n response = new Response(null, {\n statusText: 'Internal Server Error',\n status: 500\n })\n }\n\n response.waitUntil = () => Promise.all(event.awaiting);\n return response;\n }\n\n try {\n await self.__listeners.fetch[0].call(event, event)\n } catch (error) {\n return getResponse({ error })\n }\n\n return Promise.resolve(event.response)\n .then(response => getResponse({ response }))\n .catch(error => getResponse({ error }))\n })`}function defineHandlerProps({object:e,setterName:t,setter:r,getterName:n,getter:o}){Object.defineProperty(e.context,t,{set:r,configurable:false,enumerable:false});Object.defineProperty(e,n,{get:o,configurable:false,enumerable:false})}},72:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.getClonableBodyStream=void 0;const n=r(781);function getClonableBodyStream(e,t){let r=null;return{finalize(){if(r){replaceRequestBody(e,bodyStreamToNodeStream(r))}},cloneBodyStream(){const n=r!==null&&r!==void 0?r:requestToBodyStream(e,t);const[o,s]=n.tee();r=o;return s}}}t.getClonableBodyStream=getClonableBodyStream;function requestToBodyStream(e,t){const r=new t({start(t){e.on("data",(e=>t.enqueue(e)));e.on("end",(()=>t.terminate()));e.on("error",(e=>t.error(e)))}});return r.readable}function bodyStreamToNodeStream(e){const t=e.getReader();return n.Readable.from(async function*(){while(true){const{done:e,value:r}=await t.read();if(e){return}yield r}}())}function replaceRequestBody(e,t){for(const r in t){let n=t[r];if(typeof n==="function"){n=n.bind(t)}e[r]=n}return e}},303:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.createHandler=void 0;const o=r(72);const s=n(r(720));const i=n(r(242));const a=n(r(504));function createHandler(e){const t=new Set;return{handler:async(r,n)=>{var E,c;const u=(0,a.default)();const _=r.method!=="GET"&&r.method!=="HEAD"?(0,o.getClonableBodyStream)(r,e.runtime.context.TransformStream):undefined;const d=await e.runtime.dispatchFetch(String(getURL(r)),{headers:toRequestInitHeaders(r),method:r.method,body:_===null||_===void 0?void 0:_.cloneBodyStream()});const l=d.waitUntil();t.add(l);l.finally((()=>t.delete(l)));n.statusCode=d.status;n.statusMessage=d.statusText;for(const[e,t]of Object.entries(toNodeHeaders(d.headers))){if(e!=="content-encoding"&&t!==undefined){n.setHeader(e,t)}}if(d.body){for await(const e of d.body){n.write(e)}}const S=`${r.socket.remoteAddress} ${r.method} ${r.url}`;const R=`${(E=(0,s.default)(u()).match(/[a-zA-Z]+|[0-9]+/g))===null||E===void 0?void 0:E.join(" ")}`;const h=`${n.statusCode} ${i.default[n.statusCode]}`;(c=e.logger)===null||c===void 0?void 0:c.debug(`${S} → ${h} in ${R}`);n.end()},waitUntil:()=>Promise.all(t)}}t.createHandler=createHandler;function getURL(e){var t;const r=((t=e.socket)===null||t===void 0?void 0:t.encrypted)?"https":"http";return new URL(String(e.url),`${r}://${String(e.headers.host)}`)}function toRequestInitHeaders(e){return Object.keys(e.headers).map((t=>{const r=e.headers[t];return[t,Array.isArray(r)?r.join(", "):r!==null&&r!==void 0?r:""]}))}function toNodeHeaders(e){const t={};if(e){for(const[r,n]of e.entries()){t[r]=r.toLowerCase()==="set-cookie"?e.getAll("set-cookie"):n}}return t}},57:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.runServer=t.createHandler=void 0;var n=r(303);Object.defineProperty(t,"createHandler",{enumerable:true,get:function(){return n.createHandler}});var o=r(816);Object.defineProperty(t,"runServer",{enumerable:true,get:function(){return o.runServer}})},816:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.runServer=void 0;const o=r(303);const s=r(361);const i=n(r(685));async function runServer(e){const{handler:t,waitUntil:r}=(0,o.createHandler)(e);const n=i.default.createServer(t);n.listen(e.port);try{await(0,s.once)(n,"listening")}catch(t){if((t===null||t===void 0?void 0:t.code)==="EADDRINUSE"){return runServer({...e,port:undefined})}throw t}const a=n.address();const E=typeof a==="string"||a==null?String(a):`http://localhost:${a.port}`;return{url:E,close:async()=>{await r();await new Promise(((e,t)=>n.close((r=>{if(r)t(r);e()}))))},waitUntil:r}}t.runServer=runServer},242:e=>{var t;t={"1xx":"Informational","1xx_NAME":"INFORMATIONAL","1xx_MESSAGE":"Indicates an interim response for communicating connection status or request progress prior to completing the requested action and sending a final response.",INFORMATIONAL:"1xx","2xx":"Successful","2xx_NAME":"SUCCESSFUL","2xx_MESSAGE":"Indicates that the client's request was successfully received, understood, and accepted.",SUCCESSFUL:"2xx","3xx":"Redirection","3xx_NAME":"REDIRECTION","3xx_MESSAGE":"Indicates that further action needs to be taken by the user agent in order to fulfill the request.",REDIRECTION:"3xx","4xx":"Client Error","4xx_NAME":"CLIENT_ERROR","4xx_MESSAGE":"Indicates that the client seems to have erred.",CLIENT_ERROR:"4xx","5xx":"Server Error","5xx_NAME":"SERVER_ERROR","5xx_MESSAGE":"Indicates that the server is aware that it has erred or is incapable of performing the requested method.",SERVER_ERROR:"5xx"};e.exports={classes:t,100:"Continue","100_NAME":"CONTINUE","100_MESSAGE":"The server has received the request headers and the client should proceed to send the request body.","100_CLASS":t.INFORMATIONAL,CONTINUE:100,101:"Switching Protocols","101_NAME":"SWITCHING_PROTOCOLS","101_MESSAGE":"The requester has asked the server to switch protocols and the server has agreed to do so.","101_CLASS":t.INFORMATIONAL,SWITCHING_PROTOCOLS:101,102:"Processing","102_NAME":"PROCESSING","102_MESSAGE":"A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. This code indicates that the server has received and is processing the request, but no response is available yet.[7] This prevents the client from timing out and assuming the request was lost.","102_CLASS":t.INFORMATIONAL,PROCESSING:102,103:"Early Hints","103_NAME":"EARLY_HINTS","103_MESSAGE":"Used to return some response headers before final HTTP message.","103_CLASS":t.INFORMATIONAL,EARLY_HINTS:103,200:"OK","200_NAME":"OK","200_MESSAGE":"Standard response for successful HTTP requests.","200_CLASS":t.SUCCESSFUL,OK:200,201:"Created","201_NAME":"CREATED","201_MESSAGE":"The request has been fulfilled, resulting in the creation of a new resource.","201_CLASS":t.SUCCESSFUL,CREATED:201,202:"Accepted","202_NAME":"ACCEPTED","202_MESSAGE":"The request has been accepted for processing, but the processing has not been completed.","202_CLASS":t.SUCCESSFUL,ACCEPTED:202,203:"Non-Authoritative Information","203_NAME":"NON_AUTHORITATIVE_INFORMATION","203_MESSAGE":"The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response.","203_CLASS":t.SUCCESSFUL,NON_AUTHORITATIVE_INFORMATION:203,204:"No Content","204_NAME":"NO_CONTENT","204_MESSAGE":"The server successfully processed the request and is not returning any content.","204_CLASS":t.SUCCESSFUL,NO_CONTENT:204,205:"Reset Content","205_NAME":"RESET_CONTENT","205_MESSAGE":"The server successfully processed the request, but is not returning any content. Unlike a 204 response, this response requires that the requester reset the document view.","205_CLASS":t.SUCCESSFUL,RESET_CONTENT:205,206:"Partial Content","206_NAME":"PARTIAL_CONTENT","206_MESSAGE":"The server is delivering only part of the resource (byte serving) due to a range header sent by the client.","206_CLASS":t.SUCCESSFUL,PARTIAL_CONTENT:206,207:"Multi Status","207_NAME":"MULTI_STATUS","207_MESSAGE":"The message body that follows is by default an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.","207_CLASS":t.SUCCESSFUL,MULTI_STATUS:207,208:"Already Reported","208_NAME":"ALREADY_REPORTED","208_MESSAGE":"The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, and are not being included again.","208_CLASS":t.SUCCESSFUL,ALREADY_REPORTED:208,226:"IM Used","226_NAME":"IM_USED","226_MESSAGE":"The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.","226_CLASS":t.SUCCESSFUL,IM_USED:226,300:"Multiple Choices","300_NAME":"MULTIPLE_CHOICES","300_MESSAGE":"Indicates multiple options for the resource from which the client may choose.","300_CLASS":t.REDIRECTION,MULTIPLE_CHOICES:300,301:"Moved Permanently","301_NAME":"MOVED_PERMANENTLY","301_MESSAGE":"This and all future requests should be directed to the given URI.","301_CLASS":t.REDIRECTION,MOVED_PERMANENTLY:301,302:"Found","302_NAME":"FOUND","302_MESSAGE":'This is an example of industry practice contradicting the standard. The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours.',"302_CLASS":t.REDIRECTION,FOUND:302,303:"See Other","303_NAME":"SEE_OTHER","303_MESSAGE":"The response to the request can be found under another URI using the GET method.","303_CLASS":t.REDIRECTION,SEE_OTHER:303,304:"Not Modified","304_NAME":"NOT_MODIFIED","304_MESSAGE":"Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.","304_CLASS":t.REDIRECTION,NOT_MODIFIED:304,305:"Use Proxy","305_NAME":"USE_PROXY","305_MESSAGE":"The requested resource is available only through a proxy, the address for which is provided in the response.","305_CLASS":t.REDIRECTION,USE_PROXY:305,306:"Switch Proxy","306_NAME":"SWITCH_PROXY","306_MESSAGE":'No longer used. Originally meant "Subsequent requests should use the specified proxy.',"306_CLASS":t.REDIRECTION,SWITCH_PROXY:306,307:"Temporary Redirect","307_NAME":"TEMPORARY_REDIRECT","307_MESSAGE":"In this case, the request should be repeated with another URI; however, future requests should still use the original URI.","307_CLASS":t.REDIRECTION,TEMPORARY_REDIRECT:307,308:"Permanent Redirect","308_NAME":"PERMANENT_REDIRECT","308_MESSAGE":"The request and all future requests should be repeated using another URI.","308_CLASS":t.REDIRECTION,PERMANENT_REDIRECT:308,400:"Bad Request","400_NAME":"BAD_REQUEST","400_MESSAGE":"The server cannot or will not process the request due to an apparent client error.","400_CLASS":t.CLIENT_ERROR,BAD_REQUEST:400,401:"Unauthorized","401_NAME":"UNAUTHORIZED","401_MESSAGE":"Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.","401_CLASS":t.CLIENT_ERROR,UNAUTHORIZED:401,402:"Payment Required","402_NAME":"PAYMENT_REQUIRED","402_MESSAGE":"Reserved for future use. The original intention was that this code might be used as part of some form of digital cash or micropayment scheme, as proposed for example by GNU Taler, but that has not yet happened, and this code is not usually used.","402_CLASS":t.CLIENT_ERROR,PAYMENT_REQUIRED:402,403:"Forbidden","403_NAME":"FORBIDDEN","403_MESSAGE":"The request was valid, but the server is refusing action.","403_CLASS":t.CLIENT_ERROR,FORBIDDEN:403,404:"Not Found","404_NAME":"NOT_FOUND","404_MESSAGE":"The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.","404_CLASS":t.CLIENT_ERROR,NOT_FOUND:404,405:"Method Not Allowed","405_NAME":"METHOD_NOT_ALLOWED","405_MESSAGE":"A request method is not supported for the requested resource.","405_CLASS":t.CLIENT_ERROR,METHOD_NOT_ALLOWED:405,406:"Not Acceptable","406_NAME":"NOT_ACCEPTABLE","406_MESSAGE":"The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.","406_CLASS":t.CLIENT_ERROR,NOT_ACCEPTABLE:406,407:"Proxy Authentication Required","407_NAME":"PROXY_AUTHENTICATION_REQUIRED","407_MESSAGE":"The client must first authenticate itself with the proxy.","407_CLASS":t.CLIENT_ERROR,PROXY_AUTHENTICATION_REQUIRED:407,408:"Request Time-out","408_NAME":"REQUEST_TIMEOUT","408_MESSAGE":"The server timed out waiting for the request.","408_CLASS":t.CLIENT_ERROR,REQUEST_TIMEOUT:408,409:"Conflict","409_NAME":"CONFLICT","409_MESSAGE":"Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates.","409_CLASS":t.CLIENT_ERROR,CONFLICT:409,410:"Gone","410_NAME":"GONE","410_MESSAGE":"Indicates that the resource requested is no longer available and will not be available again.","410_CLASS":t.CLIENT_ERROR,GONE:410,411:"Length Required","411_NAME":"LENGTH_REQUIRED","411_MESSAGE":"The request did not specify the length of its content, which is required by the requested resource.","411_CLASS":t.CLIENT_ERROR,LENGTH_REQUIRED:411,412:"Precondition Failed","412_NAME":"PRECONDITION_FAILED","412_MESSAGE":"The server does not meet one of the preconditions that the requester put on the request.","412_CLASS":t.CLIENT_ERROR,PRECONDITION_FAILED:412,413:"Request Entity Too Large","413_NAME":"REQUEST_ENTITY_TOO_LARGE","413_MESSAGE":'The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".',"413_CLASS":t.CLIENT_ERROR,REQUEST_ENTITY_TOO_LARGE:413,414:"Request-URI Too Large","414_NAME":"REQUEST_URI_TOO_LONG","414_MESSAGE":"The URI provided was too long for the server to process.","414_CLASS":t.CLIENT_ERROR,REQUEST_URI_TOO_LONG:414,415:"Unsupported Media Type","415_NAME":"UNSUPPORTED_MEDIA_TYPE","415_MESSAGE":"The request entity has a media type which the server or resource does not support.","415_CLASS":t.CLIENT_ERROR,UNSUPPORTED_MEDIA_TYPE:415,416:"Requested Range not Satisfiable","416_NAME":"REQUESTED_RANGE_NOT_SATISFIABLE","416_MESSAGE":"The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.","416_CLASS":t.CLIENT_ERROR,REQUESTED_RANGE_NOT_SATISFIABLE:416,417:"Expectation Failed","417_NAME":"EXPECTATION_FAILED","417_MESSAGE":"The server cannot meet the requirements of the Expect request-header field.","417_CLASS":t.CLIENT_ERROR,EXPECTATION_FAILED:417,418:"I'm a teapot","418_NAME":"IM_A_TEAPOT","418_MESSAGE":'Any attempt to brew coffee with a teapot should result in the error code "418 I\'m a teapot". The resulting entity body MAY be short and stout.',"418_CLASS":t.CLIENT_ERROR,IM_A_TEAPOT:418,421:"Misdirected Request","421_NAME":"MISDIRECTED_REQUEST","421_MESSAGE":"The request was directed at a server that is not able to produce a response.","421_CLASS":t.CLIENT_ERROR,MISDIRECTED_REQUEST:421,422:"Unprocessable Entity","422_NAME":"UNPROCESSABLE_ENTITY","422_MESSAGE":"The request was well-formed but was unable to be followed due to semantic errors.","422_CLASS":t.CLIENT_ERROR,UNPROCESSABLE_ENTITY:422,423:"Locked","423_NAME":"LOCKED","423_MESSAGE":"The resource that is being accessed is locked.","423_CLASS":t.CLIENT_ERROR,LOCKED:423,424:"Failed Dependency","424_NAME":"FAILED_DEPENDENCY","424_MESSAGE":"The request failed because it depended on another request and that request failed.","424_CLASS":t.CLIENT_ERROR,FAILED_DEPENDENCY:424,426:"Upgrade Required","426_NAME":"UPGRADE_REQUIRED","426_MESSAGE":"The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.","426_CLASS":t.CLIENT_ERROR,UPGRADE_REQUIRED:426,428:"Precondition Required","428_NAME":"PRECONDITION_REQUIRED","428_MESSAGE":"The origin server requires the request to be conditional.","428_CLASS":t.CLIENT_ERROR,PRECONDITION_REQUIRED:428,429:"Too Many Requests","429_NAME":"TOO_MANY_REQUESTS","429_MESSAGE":"The user has sent too many requests in a given amount of time.","429_CLASS":t.CLIENT_ERROR,TOO_MANY_REQUESTS:429,431:"Request Header Fields Too Large","431_NAME":"REQUEST_HEADER_FIELDS_TOO_LARGE","431_MESSAGE":"The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.","431_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_FIELDS_TOO_LARGE:431,451:"Unavailable For Legal Reasons","451_NAME":"UNAVAILABLE_FOR_LEGAL_REASONS","451_MESSAGE":"A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.","451_CLASS":t.CLIENT_ERROR,UNAVAILABLE_FOR_LEGAL_REASONS:451,500:"Internal Server Error","500_NAME":"INTERNAL_SERVER_ERROR","500_MESSAGE":"A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.","500_CLASS":t.SERVER_ERROR,INTERNAL_SERVER_ERROR:500,501:"Not Implemented","501_NAME":"NOT_IMPLEMENTED","501_MESSAGE":"The server either does not recognize the request method, or it lacks the ability to fulfil the request. Usually this implies future availability.","501_CLASS":t.SERVER_ERROR,NOT_IMPLEMENTED:501,502:"Bad Gateway","502_NAME":"BAD_GATEWAY","502_MESSAGE":"The server was acting as a gateway or proxy and received an invalid response from the upstream server.","502_CLASS":t.SERVER_ERROR,BAD_GATEWAY:502,503:"Service Unavailable","503_NAME":"SERVICE_UNAVAILABLE","503_MESSAGE":"The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.","503_CLASS":t.SERVER_ERROR,SERVICE_UNAVAILABLE:503,504:"Gateway Time-out","504_NAME":"GATEWAY_TIMEOUT","504_MESSAGE":"The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.","504_CLASS":t.SERVER_ERROR,GATEWAY_TIMEOUT:504,505:"HTTP Version not Supported","505_NAME":"HTTP_VERSION_NOT_SUPPORTED","505_MESSAGE":"The server does not support the HTTP protocol version used in the request.","505_CLASS":t.SERVER_ERROR,HTTP_VERSION_NOT_SUPPORTED:505,506:"Variant Also Negotiates","506_NAME":"VARIANT_ALSO_NEGOTIATES","506_MESSAGE":"Transparent content negotiation for the request results in a circular reference.","506_CLASS":t.SERVER_ERROR,VARIANT_ALSO_NEGOTIATES:506,507:"Insufficient Storage","507_NAME":"INSUFFICIENT_STORAGE","507_MESSAGE":"The server is unable to store the representation needed to complete the request.","507_CLASS":t.SERVER_ERROR,INSUFFICIENT_STORAGE:507,508:"Loop Detected","508_NAME":"LOOP_DETECTED","508_MESSAGE":"The server detected an infinite loop while processing the request.","508_CLASS":t.SERVER_ERROR,LOOP_DETECTED:508,510:"Not Extended","510_NAME":"NOT_EXTENDED","510_MESSAGE":"Further extensions to the request are required for the server to fulfil it.","510_CLASS":t.SERVER_ERROR,NOT_EXTENDED:510,511:"Network Authentication Required","511_NAME":"NETWORK_AUTHENTICATION_REQUIRED","511_MESSAGE":"The client needs to authenticate to gain network access. Intended for use by intercepting proxies used to control access to the network.","511_CLASS":t.SERVER_ERROR,NETWORK_AUTHENTICATION_REQUIRED:511,extra:{unofficial:{103:"Checkpoint","103_NAME":"CHECKPOINT","103_MESSAGE":"Used in the resumable requests proposal to resume aborted PUT or POST requests.","103_CLASS":t.INFORMATIONAL,CHECKPOINT:103,419:"Page Expired","419_NAME":"PAGE_EXPIRED","419_MESSAGE":"Used by the Laravel Framework when a CSRF Token is missing or expired.","419_CLASS":t.CLIENT_ERROR,PAGE_EXPIRED:419,218:"This is fine","218_NAME":"THIS_IS_FINE","218_MESSAGE":"Used as a catch-all error condition for allowing response bodies to flow through Apache when ProxyErrorOverride is enabled. When ProxyErrorOverride is enabled in Apache, response bodies that contain a status code of 4xx or 5xx are automatically discarded by Apache in favor of a generic response or a custom response specified by the ErrorDocument directive.","218_CLASS":t.SUCCESSFUL,THIS_IS_FINE:218,420:"Enhance Your Calm","420_NAME":"ENHANCE_YOUR_CALM","420_MESSAGE":"Returned by version 1 of the Twitter Search and Trends API when the client is being rate limited; versions 1.1 and later use the 429 Too Many Requests response code instead.","420_CLASS":t.CLIENT_ERROR,ENHANCE_YOUR_CALM:420,450:"Blocked by Windows Parental Controls","450_NAME":"BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS","450_MESSAGE":"The Microsoft extension code indicated when Windows Parental Controls are turned on and are blocking access to the requested webpage.","450_CLASS":t.CLIENT_ERROR,BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS:450,498:"Invalid Token","498_NAME":"INVALID_TOKEN","498_MESSAGE":"Returned by ArcGIS for Server. Code 498 indicates an expired or otherwise invalid token.","498_CLASS":t.CLIENT_ERROR,INVALID_TOKEN:498,499:"Token Required","499_NAME":"TOKEN_REQUIRED","499_MESSAGE":"Returned by ArcGIS for Server. Code 499 indicates that a token is required but was not submitted.","499_CLASS":t.CLIENT_ERROR,TOKEN_REQUIRED:499,509:"Bandwidth Limit Exceeded","509_NAME":"BANDWIDTH_LIMIT_EXCEEDED","509_MESSAGE":"The server has exceeded the bandwidth specified by the server administrator.","509_CLASS":t.SERVER_ERROR,BANDWIDTH_LIMIT_EXCEEDED:509,530:"Site is frozen","530_NAME":"SITE_IS_FROZEN","530_MESSAGE":"Used by the Pantheon web platform to indicate a site that has been frozen due to inactivity.","530_CLASS":t.SERVER_ERROR,SITE_IS_FROZEN:530,598:"Network read timeout error","598_NAME":"NETWORK_READ_TIMEOUT_ERROR","598_MESSAGE":"Used by some HTTP proxies to signal a network read timeout behind the proxy to a client in front of the proxy.","598_CLASS":t.SERVER_ERROR,NETWORK_READ_TIMEOUT_ERROR:598},iis:{440:"Login Time-out","440_NAME":"LOGIN_TIME_OUT","440_MESSAGE":"The client's session has expired and must log in again.","440_CLASS":t.CLIENT_ERROR,LOGIN_TIME_OUT:440,449:"Retry With","449_NAME":"RETRY_WITH","449_MESSAGE":"The server cannot honour the request because the user has not provided the required information.","449_CLASS":t.CLIENT_ERROR,RETRY_WITH:449,451:"Redirect","451_NAME":"REDIRECT","451_MESSAGE":"Used in Exchange ActiveSync when either a more efficient server is available or the server cannot access the users' mailbox.","451_CLASS":t.CLIENT_ERROR,REDIRECT:451},nginx:{444:"No Response","444_NAME":"NO_RESPONSE","444_MESSAGE":"Used internally to instruct the server to return no information to the client and close the connection immediately.","444_CLASS":t.CLIENT_ERROR,NO_RESPONSE:444,494:"Request header too large","494_NAME":"REQUEST_HEADER_TOO_LARGE","494_MESSAGE":"Client sent too large request or too long header line.","494_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_TOO_LARGE:494,495:"SSL Certificate Error","495_NAME":"SSL_CERTIFICATE_ERROR","495_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has provided an invalid client certificate.","495_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_ERROR:495,496:"SSL Certificate Required","496_NAME":"SSL_CERTIFICATE_REQUIRED","496_MESSAGE":"An expansion of the 400 Bad Request response code, used when a client certificate is required but not provided.","496_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_REQUIRED:496,497:"HTTP Request Sent to HTTPS Port","497_NAME":"HTTP_REQUEST_SENT_TO_HTTPS_PORT","497_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests.","497_CLASS":t.CLIENT_ERROR,HTTP_REQUEST_SENT_TO_HTTPS_PORT:497,499:"Client Closed Request","499_NAME":"CLIENT_CLOSED_REQUEST","499_MESSAGE":"Used when the client has closed the request before the server could send a response.","499_CLASS":t.CLIENT_ERROR,CLIENT_CLOSED_REQUEST:499},cloudflare:{520:"Unknown Error","520_NAME":"UNKNOWN_ERROR","520_MESSAGE":'The 520 error is used as a "catch-all response for when the origin server returns something unexpected", listing connection resets, large headers, and empty or invalid responses as common triggers.',"520_CLASS":t.SERVER_ERROR,UNKNOWN_ERROR:520,521:"Web Server Is Down","521_NAME":"WEB_SERVER_IS_DOWN","521_MESSAGE":"The origin server has refused the connection from Cloudflare.","521_CLASS":t.SERVER_ERROR,WEB_SERVER_IS_DOWN:521,522:"Connection Timed Out","522_NAME":"CONNECTION_TIMED_OUT","522_MESSAGE":"Cloudflare could not negotiate a TCP handshake with the origin server.","522_CLASS":t.SERVER_ERROR,CONNECTION_TIMED_OUT:522,523:"Origin Is Unreachable","523_NAME":"ORIGIN_IS_UNREACHABLE","523_MESSAGE":"Cloudflare could not reach the origin server.","523_CLASS":t.SERVER_ERROR,ORIGIN_IS_UNREACHABLE:523,524:"A Timeout Occurred","524_NAME":"A_TIMEOUT_OCCURRED","524_MESSAGE":"Cloudflare was able to complete a TCP connection to the origin server, but did not receive a timely HTTP response.","524_CLASS":t.SERVER_ERROR,A_TIMEOUT_OCCURRED:524,525:"SSL Handshake Failed","525_NAME":"SSL_HANDSHAKE_FAILED","525_MESSAGE":"Cloudflare could not negotiate a SSL/TLS handshake with the origin server.","525_CLASS":t.SERVER_ERROR,SSL_HANDSHAKE_FAILED:525,526:"Invalid SSL Certificate","526_NAME":"INVALID_SSL_CERTIFICATE","526_MESSAGE":"Cloudflare could not validate the SSL/TLS certificate that the origin server presented.","526_CLASS":t.SERVER_ERROR,INVALID_SSL_CERTIFICATE:526,527:"Railgun Error","527_NAME":"RAILGUN_ERROR","527_MESSAGE":"Error 527 indicates that the request timed out or failed after the WAN connection had been established.","527_CLASS":t.SERVER_ERROR,RAILGUN_ERROR:527}}}},672:e=>{"use strict";e.exports=e=>{if(typeof e!=="number"){throw new TypeError("Expected a number")}const t=e>0?Math.floor:Math.ceil;return{days:t(e/864e5),hours:t(e/36e5)%24,minutes:t(e/6e4)%60,seconds:t(e/1e3)%60,milliseconds:t(e)%1e3,microseconds:t(e*1e3)%1e3,nanoseconds:t(e*1e6)%1e3}}},720:(e,t,r)=>{"use strict";const n=r(672);const pluralize=(e,t)=>t===1?e:`${e}s`;const o=1e-7;e.exports=(e,t={})=>{if(!Number.isFinite(e)){throw new TypeError("Expected a finite number")}if(t.colonNotation){t.compact=false;t.formatSubMilliseconds=false;t.separateMilliseconds=false;t.verbose=false}if(t.compact){t.secondsDecimalDigits=0;t.millisecondsDecimalDigits=0}const r=[];const floorDecimals=(e,t)=>{const r=Math.floor(e*10**t+o);const n=Math.round(r)/10**t;return n.toFixed(t)};const add=(e,n,o,s)=>{if((r.length===0||!t.colonNotation)&&e===0&&!(t.colonNotation&&o==="m")){return}s=(s||e||"0").toString();let i;let a;if(t.colonNotation){i=r.length>0?":":"";a="";const e=s.includes(".")?s.split(".")[0].length:s.length;const t=r.length>0?2:1;s="0".repeat(Math.max(0,t-e))+s}else{i="";a=t.verbose?" "+pluralize(n,e):o}r.push(i+s+a)};const s=n(e);add(Math.trunc(s.days/365),"year","y");add(s.days%365,"day","d");add(s.hours,"hour","h");add(s.minutes,"minute","m");if(t.separateMilliseconds||t.formatSubMilliseconds||!t.colonNotation&&e<1e3){add(s.seconds,"second","s");if(t.formatSubMilliseconds){add(s.milliseconds,"millisecond","ms");add(s.microseconds,"microsecond","µs");add(s.nanoseconds,"nanosecond","ns")}else{const e=s.milliseconds+s.microseconds/1e3+s.nanoseconds/1e6;const r=typeof t.millisecondsDecimalDigits==="number"?t.millisecondsDecimalDigits:0;const n=e>=1?Math.round(e):Math.ceil(e);const o=r?e.toFixed(r):n;add(Number.parseFloat(o,10),"millisecond","ms",o)}}else{const r=e/1e3%60;const n=typeof t.secondsDecimalDigits==="number"?t.secondsDecimalDigits:1;const o=floorDecimals(r,n);const s=t.keepDecimalsOnWholeSeconds?o:o.replace(/\.0+$/,"");add(Number.parseFloat(s,10),"second","s",s)}if(r.length===0){return"0"+(t.verbose?" milliseconds":"ms")}if(t.compact){return r[0]}if(typeof t.unitCount==="number"){const e=t.colonNotation?"":" ";return r.slice(0,Math.max(t.unitCount,1)).join(e)}return t.colonNotation?r.join(""):r.join(" ")}},504:(e,t,r)=>{"use strict";const n=r(734);e.exports=()=>{const e=process.hrtime();const end=t=>n(process.hrtime(e))[t];const returnValue=()=>end("milliseconds");returnValue.rounded=()=>Math.round(end("milliseconds"));returnValue.seconds=()=>end("seconds");returnValue.nanoseconds=()=>end("nanoseconds");return returnValue}},491:e=>{"use strict";e.exports=require("assert")},300:e=>{"use strict";e.exports=require("buffer")},113:e=>{"use strict";e.exports=require("crypto")},361:e=>{"use strict";e.exports=require("events")},147:e=>{"use strict";e.exports=require("fs")},685:e=>{"use strict";e.exports=require("http")},808:e=>{"use strict";e.exports=require("net")},277:e=>{"use strict";e.exports=require("next/dist/compiled/@edge-runtime/primitives/crypto")},37:e=>{"use strict";e.exports=require("os")},17:e=>{"use strict";e.exports=require("path")},74:e=>{"use strict";e.exports=require("perf_hooks")},282:e=>{"use strict";e.exports=require("process")},477:e=>{"use strict";e.exports=require("punycode")},781:e=>{"use strict";e.exports=require("stream")},404:e=>{"use strict";e.exports=require("tls")},837:e=>{"use strict";e.exports=require("util")},144:e=>{"use strict";e.exports=require("vm")},796:e=>{"use strict";e.exports=require("zlib")}};var __webpack_module_cache__={};function __nccwpck_require__(e){var t=__webpack_module_cache__[e];if(t!==undefined){return t.exports}var r=__webpack_module_cache__[e]={exports:{}};var n=true;try{__webpack_modules__[e].call(r.exports,r,r.exports,__nccwpck_require__);n=false}finally{if(n)delete __webpack_module_cache__[e]}return r.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__={};(()=>{"use strict";var e=__webpack_exports__;Object.defineProperty(e,"__esModule",{value:true});e.EdgeRuntime=e.runServer=e.createHandler=void 0;var t=__nccwpck_require__(57);Object.defineProperty(e,"createHandler",{enumerable:true,get:function(){return t.createHandler}});Object.defineProperty(e,"runServer",{enumerable:true,get:function(){return t.runServer}});var r=__nccwpck_require__(26);Object.defineProperty(e,"EdgeRuntime",{enumerable:true,get:function(){return r.EdgeRuntime}})})();module.exports=__webpack_exports__})(); \ No newline at end of file +(()=>{var __webpack_modules__={605:(__unused_webpack_module,exports,__nccwpck_require__)=>{"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EdgeVM=void 0;const buffer_1=__nccwpck_require__(300);const require_1=__nccwpck_require__(274);const vm_1=__nccwpck_require__(386);class EdgeVM extends vm_1.VM{constructor(e={}){super({...e,extend:t=>e.extend?e.extend(addPrimitives(t)):addPrimitives(t)})}}exports.EdgeVM=EdgeVM;function addPrimitives(context){defineProperty(context,"self",{enumerable:true,value:context});defineProperty(context,"globalThis",{value:context});defineProperty(context,"Symbol",{value:Symbol});defineProperty(context,"clearInterval",{value:clearInterval});defineProperty(context,"clearTimeout",{value:clearTimeout});defineProperty(context,"setInterval",{value:setInterval});defineProperty(context,"setTimeout",{value:setTimeout});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/console"),scopedContext:{console:console}}),nonenumerable:["console"]});const encodings=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/encoding"),scopedContext:{Buffer:buffer_1.Buffer,global:{ArrayBuffer:ArrayBuffer}}});defineProperties(context,{exports:encodings,nonenumerable:["atob","btoa","TextEncoder","TextDecoder"]});const streams=(0,require_1.requireWithCache)({path:require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),context:context});defineProperties(context,{exports:streams,nonenumerable:["ReadableStream","ReadableStreamBYOBReader","ReadableStreamDefaultReader","TransformStream","WritableStream","WritableStreamDefaultWriter"]});const abort=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/abort-controller")});defineProperties(context,{exports:abort,nonenumerable:["AbortController","AbortSignal"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([["punycode",{exports:__nccwpck_require__(477)}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/url"),scopedContext:{TextEncoder:encodings.TextEncoder,TextDecoder:encodings.TextDecoder}}),nonenumerable:["URL","URLSearchParams","URLPattern"]});const blob=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/blob")});defineProperties(context,{exports:blob,nonenumerable:["Blob"]});const webFetch=(0,require_1.requireWithCache)({context:context,cache:new Map([["abort-controller",{exports:abort}],["assert",{exports:__nccwpck_require__(491)}],["buffer",{exports:__nccwpck_require__(300)}],["events",{exports:__nccwpck_require__(361)}],["http",{exports:__nccwpck_require__(685)}],["net",{exports:__nccwpck_require__(808)}],["perf_hooks",{exports:__nccwpck_require__(74)}],["stream",{exports:__nccwpck_require__(781)}],["tls",{exports:__nccwpck_require__(404)}],["util",{exports:__nccwpck_require__(837)}],["zlib",{exports:__nccwpck_require__(796)}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),{exports:streams}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/blob"),{exports:blob}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),scopedContext:{Buffer:buffer_1.Buffer,FinalizationRegistry:function(){return{register:function(){}}},global:{},queueMicrotask:queueMicrotask,setImmediate:setImmediate}});defineProperties(context,{exports:webFetch,nonenumerable:["fetch","File","FormData","Headers","Request","Response"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([[require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),{exports:webFetch}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/cache"),scopedContext:{global:{}}}),enumerable:["caches"],nonenumerable:["Cache","CacheStorage"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,cache:new Map([["crypto",{exports:__nccwpck_require__(113)}],["process",{exports:__nccwpck_require__(282)}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/crypto"),scopedContext:{Buffer:buffer_1.Buffer}}),enumerable:["crypto"],nonenumerable:["Crypto","CryptoKey","SubtleCrypto"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/events")}),nonenumerable:["Event","EventTarget","FetchEvent","PromiseRejectionEvent"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/structured-clone")}),nonenumerable:["structuredClone"]});return context}function defineProperty(e,t,r){var n,o,s;Object.defineProperty(e,t,{configurable:(n=r.configurable)!==null&&n!==void 0?n:false,enumerable:(o=r.enumerable)!==null&&o!==void 0?o:false,value:r.value,writable:(s=r.writable)!==null&&s!==void 0?s:true})}function defineProperties(e,t){var r,n;for(const n of(r=t.enumerable)!==null&&r!==void 0?r:[]){if(!t.exports[n]){throw new Error(`Attempt to export a nullable value for "${n}"`)}defineProperty(e,n,{enumerable:true,value:t.exports[n]})}for(const r of(n=t.nonenumerable)!==null&&n!==void 0?n:[]){if(!t.exports[r]){throw new Error(`Attempt to export a nullable value for "${r}"`)}defineProperty(e,r,{value:t.exports[r]})}}},844:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=t.EdgeVM=void 0;var n=r(605);Object.defineProperty(t,"EdgeVM",{enumerable:true,get:function(){return n.EdgeVM}});var o=r(386);Object.defineProperty(t,"VM",{enumerable:true,get:function(){return o.VM}})},274:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.requireWithCache=t.createRequire=t.requireDependencies=void 0;const n=r(147);const o=r(144);const s=r(17);function requireDependencies(e){const{context:t,requireCache:r,dependencies:n}=e;const o=createRequire(t,r);for(const{path:e,mapExports:r}of n){const n=o(e,e);for(const e of Object.keys(r)){t[r[e]]=n[e]}}}t.requireDependencies=requireDependencies;function createRequire(e,t,r,i={}){return function requireFn(a,E){const c=require.resolve(E,{paths:[(0,s.dirname)(a)]});const u=t.get(E)||t.get(c);if(u!==undefined&&u!==null){return u.exports}const _={exports:{},loaded:false,id:c};t.set(c,_);r===null||r===void 0?void 0:r.add(c);const d=(0,o.runInContext)(`(function(module,exports,require,__dirname,__filename,${Object.keys(i).join(",")}) {${(0,n.readFileSync)(c,"utf-8")}\n})`,e);try{d(_,_.exports,requireFn.bind(null,c),(0,s.dirname)(c),c,...Object.values(i))}catch(e){t.delete(c);throw e}_.loaded=true;return _.exports}}t.createRequire=createRequire;function requireWithCache(e){var t;return createRequire(e.context,(t=e.cache)!==null&&t!==void 0?t:new Map,e.references,e.scopedContext).call(null,e.path,e.path)}t.requireWithCache=requireWithCache},370:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.tempFile=void 0;const o=r(277);const s=n(r(17));const i=n(r(147));const a=n(r(37));function tempFile(e){const t=s.default.join(a.default.tmpdir(),o.crypto.randomUUID());i.default.writeFileSync(t,e);return{path:t,remove:()=>i.default.unlinkSync(t)}}t.tempFile=tempFile},386:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=void 0;const n=r(144);const o=r(274);const s=r(370);class VM{constructor(e={}){var t,r,s,i;const a=(0,n.createContext)({},{name:"Edge Runtime",codeGeneration:(t=e.codeGeneration)!==null&&t!==void 0?t:{strings:false,wasm:true}});this.requireCache=(r=e.requireCache)!==null&&r!==void 0?r:new Map;this.context=(i=(s=e.extend)===null||s===void 0?void 0:s.call(e,a))!==null&&i!==void 0?i:a;this.requireFn=(0,o.createRequire)(this.context,this.requireCache)}evaluate(e){return(0,n.runInContext)(e,this.context)}require(e){return this.requireFn(e,e)}requireInContext(e){const t=this.require(e);for(const[e,r]of Object.entries(t)){this.context[e]=r}}requireInlineInContext(e){const t=(0,s.tempFile)(e);this.requireInContext(t.path);t.remove()}}t.VM=VM},734:e=>{"use strict";e.exports=e=>{const t=e[0]*1e9+e[1];const r=t/1e6;const n=t/1e9;return{seconds:n,milliseconds:r,nanoseconds:t}}},663:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.EdgeRuntime=void 0;const n=r(844);let o;let s;class EdgeRuntime extends n.EdgeVM{constructor(e){super({...e,extend:t=>{var r,n;return(n=(r=e===null||e===void 0?void 0:e.extend)===null||r===void 0?void 0:r.call(e,t))!==null&&n!==void 0?n:t}});defineHandlerProps({object:this,setterName:"__onUnhandledRejectionHandler",setter:e=>o=e,getterName:"__rejectionHandlers",getter:()=>o});defineHandlerProps({object:this,setterName:"__onErrorHandler",setter:e=>s=e,getterName:"__errorHandlers",getter:()=>s});Object.defineProperty(this.context,"EdgeRuntime",{configurable:false,enumerable:false,writable:false,value:"edge-runtime"});this.evaluate(getDefineEventListenersCode());this.dispatchFetch=this.evaluate(getDispatchFetchCode());if(e===null||e===void 0?void 0:e.initialCode){this.evaluate(e.initialCode)}}}t.EdgeRuntime=EdgeRuntime;process.on("unhandledRejection",(function invokeRejectionHandlers(e,t){o===null||o===void 0?void 0:o.forEach((r=>r(e,t)))}));process.on("uncaughtException",(function invokeErrorHandlers(e){s===null||s===void 0?void 0:s.forEach((t=>t(e)))}));function getDefineEventListenersCode(){return`\n Object.defineProperty(self, '__listeners', {\n configurable: false,\n enumerable: false,\n value: {},\n writable: true,\n })\n\n function __conditionallyUpdatesHandlerList(eventType) {\n if (eventType === 'unhandledrejection') {\n self.__onUnhandledRejectionHandler = self.__listeners[eventType];\n } else if (eventType === 'error') {\n self.__onErrorHandler = self.__listeners[eventType];\n }\n }\n\n function addEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (eventType === 'fetch' && self.__listeners.fetch) {\n throw new TypeError('You can register just one "fetch" event listener');\n }\n\n self.__listeners[eventType] = self.__listeners[eventType] || [];\n self.__listeners[eventType].push(handler);\n __conditionallyUpdatesHandlerList(eventType);\n }\n\n function removeEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (self.__listeners[eventType]) {\n self.__listeners[eventType] = self.__listeners[eventType].filter(item => {\n return item !== handler;\n });\n\n if (self.__listeners[eventType].length === 0) {\n delete self.__listeners[eventType];\n }\n }\n __conditionallyUpdatesHandlerList(eventType);\n }\n `}function getDispatchFetchCode(){return`(async function dispatchFetch(input, init) {\n const request = new Request(input, init);\n const event = new FetchEvent(request);\n if (!self.__listeners.fetch) {\n throw new Error("No fetch event listeners found");\n }\n\n const getResponse = ({ response, error }) => {\n if (error || !response || !(response instanceof Response)) {\n console.error(error ? error : 'The event listener did not respond')\n response = new Response(null, {\n statusText: 'Internal Server Error',\n status: 500\n })\n }\n\n response.waitUntil = () => Promise.all(event.awaiting);\n return response;\n }\n\n try {\n await self.__listeners.fetch[0].call(event, event)\n } catch (error) {\n return getResponse({ error })\n }\n\n return Promise.resolve(event.response)\n .then(response => getResponse({ response }))\n .catch(error => getResponse({ error }))\n })`}function defineHandlerProps({object:e,setterName:t,setter:r,getterName:n,getter:o}){Object.defineProperty(e.context,t,{set:r,configurable:false,enumerable:false});Object.defineProperty(e,n,{get:o,configurable:false,enumerable:false})}},353:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.getClonableBodyStream=void 0;const n=r(781);function getClonableBodyStream(e,t){let r=null;return{finalize(){if(r){replaceRequestBody(e,bodyStreamToNodeStream(r))}},cloneBodyStream(){const n=r!==null&&r!==void 0?r:requestToBodyStream(e,t);const[o,s]=n.tee();r=o;return s}}}t.getClonableBodyStream=getClonableBodyStream;function requestToBodyStream(e,t){const r=new t({start(t){e.on("data",(e=>t.enqueue(e)));e.on("end",(()=>t.terminate()));e.on("error",(e=>t.error(e)))}});return r.readable}function bodyStreamToNodeStream(e){const t=e.getReader();return n.Readable.from(async function*(){while(true){const{done:e,value:r}=await t.read();if(e){return}yield r}}())}function replaceRequestBody(e,t){for(const r in t){let n=t[r];if(typeof n==="function"){n=n.bind(t)}e[r]=n}return e}},564:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.createHandler=void 0;const o=r(353);const s=n(r(720));const i=n(r(242));const a=n(r(504));function createHandler(e){const t=new Set;return{handler:async(r,n)=>{var E,c;const u=(0,a.default)();const _=r.method!=="GET"&&r.method!=="HEAD"?(0,o.getClonableBodyStream)(r,e.runtime.context.TransformStream):undefined;const d=await e.runtime.dispatchFetch(String(getURL(r)),{headers:toRequestInitHeaders(r),method:r.method,body:_===null||_===void 0?void 0:_.cloneBodyStream()});const l=d.waitUntil();t.add(l);l.finally((()=>t.delete(l)));n.statusCode=d.status;n.statusMessage=d.statusText;for(const[e,t]of Object.entries(toNodeHeaders(d.headers))){if(e!=="content-encoding"&&t!==undefined){n.setHeader(e,t)}}if(d.body){for await(const e of d.body){n.write(e)}}const S=`${r.socket.remoteAddress} ${r.method} ${r.url}`;const R=`${(E=(0,s.default)(u()).match(/[a-zA-Z]+|[0-9]+/g))===null||E===void 0?void 0:E.join(" ")}`;const h=`${n.statusCode} ${i.default[n.statusCode]}`;(c=e.logger)===null||c===void 0?void 0:c.debug(`${S} → ${h} in ${R}`);n.end()},waitUntil:()=>Promise.all(t)}}t.createHandler=createHandler;function getURL(e){var t;const r=((t=e.socket)===null||t===void 0?void 0:t.encrypted)?"https":"http";return new URL(String(e.url),`${r}://${String(e.headers.host)}`)}function toRequestInitHeaders(e){return Object.keys(e.headers).map((t=>{const r=e.headers[t];return[t,Array.isArray(r)?r.join(", "):r!==null&&r!==void 0?r:""]}))}function toNodeHeaders(e){const t={};if(e){for(const[r,n]of e.entries()){t[r]=r.toLowerCase()==="set-cookie"?e.getAll("set-cookie"):n}}return t}},179:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.runServer=t.createHandler=void 0;var n=r(564);Object.defineProperty(t,"createHandler",{enumerable:true,get:function(){return n.createHandler}});var o=r(82);Object.defineProperty(t,"runServer",{enumerable:true,get:function(){return o.runServer}})},82:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.runServer=void 0;const o=r(564);const s=r(361);const i=n(r(685));async function runServer(e){const{handler:t,waitUntil:r}=(0,o.createHandler)(e);const n=i.default.createServer(t);n.listen(e.port);try{await(0,s.once)(n,"listening")}catch(t){if((t===null||t===void 0?void 0:t.code)==="EADDRINUSE"){return runServer({...e,port:undefined})}throw t}const a=n.address();const E=typeof a==="string"||a==null?String(a):`http://localhost:${a.port}`;return{url:E,close:async()=>{await r();await new Promise(((e,t)=>n.close((r=>{if(r)t(r);e()}))))},waitUntil:r}}t.runServer=runServer},242:e=>{var t;t={"1xx":"Informational","1xx_NAME":"INFORMATIONAL","1xx_MESSAGE":"Indicates an interim response for communicating connection status or request progress prior to completing the requested action and sending a final response.",INFORMATIONAL:"1xx","2xx":"Successful","2xx_NAME":"SUCCESSFUL","2xx_MESSAGE":"Indicates that the client's request was successfully received, understood, and accepted.",SUCCESSFUL:"2xx","3xx":"Redirection","3xx_NAME":"REDIRECTION","3xx_MESSAGE":"Indicates that further action needs to be taken by the user agent in order to fulfill the request.",REDIRECTION:"3xx","4xx":"Client Error","4xx_NAME":"CLIENT_ERROR","4xx_MESSAGE":"Indicates that the client seems to have erred.",CLIENT_ERROR:"4xx","5xx":"Server Error","5xx_NAME":"SERVER_ERROR","5xx_MESSAGE":"Indicates that the server is aware that it has erred or is incapable of performing the requested method.",SERVER_ERROR:"5xx"};e.exports={classes:t,100:"Continue","100_NAME":"CONTINUE","100_MESSAGE":"The server has received the request headers and the client should proceed to send the request body.","100_CLASS":t.INFORMATIONAL,CONTINUE:100,101:"Switching Protocols","101_NAME":"SWITCHING_PROTOCOLS","101_MESSAGE":"The requester has asked the server to switch protocols and the server has agreed to do so.","101_CLASS":t.INFORMATIONAL,SWITCHING_PROTOCOLS:101,102:"Processing","102_NAME":"PROCESSING","102_MESSAGE":"A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. This code indicates that the server has received and is processing the request, but no response is available yet.[7] This prevents the client from timing out and assuming the request was lost.","102_CLASS":t.INFORMATIONAL,PROCESSING:102,103:"Early Hints","103_NAME":"EARLY_HINTS","103_MESSAGE":"Used to return some response headers before final HTTP message.","103_CLASS":t.INFORMATIONAL,EARLY_HINTS:103,200:"OK","200_NAME":"OK","200_MESSAGE":"Standard response for successful HTTP requests.","200_CLASS":t.SUCCESSFUL,OK:200,201:"Created","201_NAME":"CREATED","201_MESSAGE":"The request has been fulfilled, resulting in the creation of a new resource.","201_CLASS":t.SUCCESSFUL,CREATED:201,202:"Accepted","202_NAME":"ACCEPTED","202_MESSAGE":"The request has been accepted for processing, but the processing has not been completed.","202_CLASS":t.SUCCESSFUL,ACCEPTED:202,203:"Non-Authoritative Information","203_NAME":"NON_AUTHORITATIVE_INFORMATION","203_MESSAGE":"The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response.","203_CLASS":t.SUCCESSFUL,NON_AUTHORITATIVE_INFORMATION:203,204:"No Content","204_NAME":"NO_CONTENT","204_MESSAGE":"The server successfully processed the request and is not returning any content.","204_CLASS":t.SUCCESSFUL,NO_CONTENT:204,205:"Reset Content","205_NAME":"RESET_CONTENT","205_MESSAGE":"The server successfully processed the request, but is not returning any content. Unlike a 204 response, this response requires that the requester reset the document view.","205_CLASS":t.SUCCESSFUL,RESET_CONTENT:205,206:"Partial Content","206_NAME":"PARTIAL_CONTENT","206_MESSAGE":"The server is delivering only part of the resource (byte serving) due to a range header sent by the client.","206_CLASS":t.SUCCESSFUL,PARTIAL_CONTENT:206,207:"Multi Status","207_NAME":"MULTI_STATUS","207_MESSAGE":"The message body that follows is by default an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.","207_CLASS":t.SUCCESSFUL,MULTI_STATUS:207,208:"Already Reported","208_NAME":"ALREADY_REPORTED","208_MESSAGE":"The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, and are not being included again.","208_CLASS":t.SUCCESSFUL,ALREADY_REPORTED:208,226:"IM Used","226_NAME":"IM_USED","226_MESSAGE":"The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.","226_CLASS":t.SUCCESSFUL,IM_USED:226,300:"Multiple Choices","300_NAME":"MULTIPLE_CHOICES","300_MESSAGE":"Indicates multiple options for the resource from which the client may choose.","300_CLASS":t.REDIRECTION,MULTIPLE_CHOICES:300,301:"Moved Permanently","301_NAME":"MOVED_PERMANENTLY","301_MESSAGE":"This and all future requests should be directed to the given URI.","301_CLASS":t.REDIRECTION,MOVED_PERMANENTLY:301,302:"Found","302_NAME":"FOUND","302_MESSAGE":'This is an example of industry practice contradicting the standard. The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours.',"302_CLASS":t.REDIRECTION,FOUND:302,303:"See Other","303_NAME":"SEE_OTHER","303_MESSAGE":"The response to the request can be found under another URI using the GET method.","303_CLASS":t.REDIRECTION,SEE_OTHER:303,304:"Not Modified","304_NAME":"NOT_MODIFIED","304_MESSAGE":"Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.","304_CLASS":t.REDIRECTION,NOT_MODIFIED:304,305:"Use Proxy","305_NAME":"USE_PROXY","305_MESSAGE":"The requested resource is available only through a proxy, the address for which is provided in the response.","305_CLASS":t.REDIRECTION,USE_PROXY:305,306:"Switch Proxy","306_NAME":"SWITCH_PROXY","306_MESSAGE":'No longer used. Originally meant "Subsequent requests should use the specified proxy.',"306_CLASS":t.REDIRECTION,SWITCH_PROXY:306,307:"Temporary Redirect","307_NAME":"TEMPORARY_REDIRECT","307_MESSAGE":"In this case, the request should be repeated with another URI; however, future requests should still use the original URI.","307_CLASS":t.REDIRECTION,TEMPORARY_REDIRECT:307,308:"Permanent Redirect","308_NAME":"PERMANENT_REDIRECT","308_MESSAGE":"The request and all future requests should be repeated using another URI.","308_CLASS":t.REDIRECTION,PERMANENT_REDIRECT:308,400:"Bad Request","400_NAME":"BAD_REQUEST","400_MESSAGE":"The server cannot or will not process the request due to an apparent client error.","400_CLASS":t.CLIENT_ERROR,BAD_REQUEST:400,401:"Unauthorized","401_NAME":"UNAUTHORIZED","401_MESSAGE":"Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.","401_CLASS":t.CLIENT_ERROR,UNAUTHORIZED:401,402:"Payment Required","402_NAME":"PAYMENT_REQUIRED","402_MESSAGE":"Reserved for future use. The original intention was that this code might be used as part of some form of digital cash or micropayment scheme, as proposed for example by GNU Taler, but that has not yet happened, and this code is not usually used.","402_CLASS":t.CLIENT_ERROR,PAYMENT_REQUIRED:402,403:"Forbidden","403_NAME":"FORBIDDEN","403_MESSAGE":"The request was valid, but the server is refusing action.","403_CLASS":t.CLIENT_ERROR,FORBIDDEN:403,404:"Not Found","404_NAME":"NOT_FOUND","404_MESSAGE":"The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.","404_CLASS":t.CLIENT_ERROR,NOT_FOUND:404,405:"Method Not Allowed","405_NAME":"METHOD_NOT_ALLOWED","405_MESSAGE":"A request method is not supported for the requested resource.","405_CLASS":t.CLIENT_ERROR,METHOD_NOT_ALLOWED:405,406:"Not Acceptable","406_NAME":"NOT_ACCEPTABLE","406_MESSAGE":"The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.","406_CLASS":t.CLIENT_ERROR,NOT_ACCEPTABLE:406,407:"Proxy Authentication Required","407_NAME":"PROXY_AUTHENTICATION_REQUIRED","407_MESSAGE":"The client must first authenticate itself with the proxy.","407_CLASS":t.CLIENT_ERROR,PROXY_AUTHENTICATION_REQUIRED:407,408:"Request Time-out","408_NAME":"REQUEST_TIMEOUT","408_MESSAGE":"The server timed out waiting for the request.","408_CLASS":t.CLIENT_ERROR,REQUEST_TIMEOUT:408,409:"Conflict","409_NAME":"CONFLICT","409_MESSAGE":"Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates.","409_CLASS":t.CLIENT_ERROR,CONFLICT:409,410:"Gone","410_NAME":"GONE","410_MESSAGE":"Indicates that the resource requested is no longer available and will not be available again.","410_CLASS":t.CLIENT_ERROR,GONE:410,411:"Length Required","411_NAME":"LENGTH_REQUIRED","411_MESSAGE":"The request did not specify the length of its content, which is required by the requested resource.","411_CLASS":t.CLIENT_ERROR,LENGTH_REQUIRED:411,412:"Precondition Failed","412_NAME":"PRECONDITION_FAILED","412_MESSAGE":"The server does not meet one of the preconditions that the requester put on the request.","412_CLASS":t.CLIENT_ERROR,PRECONDITION_FAILED:412,413:"Request Entity Too Large","413_NAME":"REQUEST_ENTITY_TOO_LARGE","413_MESSAGE":'The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".',"413_CLASS":t.CLIENT_ERROR,REQUEST_ENTITY_TOO_LARGE:413,414:"Request-URI Too Large","414_NAME":"REQUEST_URI_TOO_LONG","414_MESSAGE":"The URI provided was too long for the server to process.","414_CLASS":t.CLIENT_ERROR,REQUEST_URI_TOO_LONG:414,415:"Unsupported Media Type","415_NAME":"UNSUPPORTED_MEDIA_TYPE","415_MESSAGE":"The request entity has a media type which the server or resource does not support.","415_CLASS":t.CLIENT_ERROR,UNSUPPORTED_MEDIA_TYPE:415,416:"Requested Range not Satisfiable","416_NAME":"REQUESTED_RANGE_NOT_SATISFIABLE","416_MESSAGE":"The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.","416_CLASS":t.CLIENT_ERROR,REQUESTED_RANGE_NOT_SATISFIABLE:416,417:"Expectation Failed","417_NAME":"EXPECTATION_FAILED","417_MESSAGE":"The server cannot meet the requirements of the Expect request-header field.","417_CLASS":t.CLIENT_ERROR,EXPECTATION_FAILED:417,418:"I'm a teapot","418_NAME":"IM_A_TEAPOT","418_MESSAGE":'Any attempt to brew coffee with a teapot should result in the error code "418 I\'m a teapot". The resulting entity body MAY be short and stout.',"418_CLASS":t.CLIENT_ERROR,IM_A_TEAPOT:418,421:"Misdirected Request","421_NAME":"MISDIRECTED_REQUEST","421_MESSAGE":"The request was directed at a server that is not able to produce a response.","421_CLASS":t.CLIENT_ERROR,MISDIRECTED_REQUEST:421,422:"Unprocessable Entity","422_NAME":"UNPROCESSABLE_ENTITY","422_MESSAGE":"The request was well-formed but was unable to be followed due to semantic errors.","422_CLASS":t.CLIENT_ERROR,UNPROCESSABLE_ENTITY:422,423:"Locked","423_NAME":"LOCKED","423_MESSAGE":"The resource that is being accessed is locked.","423_CLASS":t.CLIENT_ERROR,LOCKED:423,424:"Failed Dependency","424_NAME":"FAILED_DEPENDENCY","424_MESSAGE":"The request failed because it depended on another request and that request failed.","424_CLASS":t.CLIENT_ERROR,FAILED_DEPENDENCY:424,426:"Upgrade Required","426_NAME":"UPGRADE_REQUIRED","426_MESSAGE":"The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.","426_CLASS":t.CLIENT_ERROR,UPGRADE_REQUIRED:426,428:"Precondition Required","428_NAME":"PRECONDITION_REQUIRED","428_MESSAGE":"The origin server requires the request to be conditional.","428_CLASS":t.CLIENT_ERROR,PRECONDITION_REQUIRED:428,429:"Too Many Requests","429_NAME":"TOO_MANY_REQUESTS","429_MESSAGE":"The user has sent too many requests in a given amount of time.","429_CLASS":t.CLIENT_ERROR,TOO_MANY_REQUESTS:429,431:"Request Header Fields Too Large","431_NAME":"REQUEST_HEADER_FIELDS_TOO_LARGE","431_MESSAGE":"The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.","431_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_FIELDS_TOO_LARGE:431,451:"Unavailable For Legal Reasons","451_NAME":"UNAVAILABLE_FOR_LEGAL_REASONS","451_MESSAGE":"A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.","451_CLASS":t.CLIENT_ERROR,UNAVAILABLE_FOR_LEGAL_REASONS:451,500:"Internal Server Error","500_NAME":"INTERNAL_SERVER_ERROR","500_MESSAGE":"A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.","500_CLASS":t.SERVER_ERROR,INTERNAL_SERVER_ERROR:500,501:"Not Implemented","501_NAME":"NOT_IMPLEMENTED","501_MESSAGE":"The server either does not recognize the request method, or it lacks the ability to fulfil the request. Usually this implies future availability.","501_CLASS":t.SERVER_ERROR,NOT_IMPLEMENTED:501,502:"Bad Gateway","502_NAME":"BAD_GATEWAY","502_MESSAGE":"The server was acting as a gateway or proxy and received an invalid response from the upstream server.","502_CLASS":t.SERVER_ERROR,BAD_GATEWAY:502,503:"Service Unavailable","503_NAME":"SERVICE_UNAVAILABLE","503_MESSAGE":"The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.","503_CLASS":t.SERVER_ERROR,SERVICE_UNAVAILABLE:503,504:"Gateway Time-out","504_NAME":"GATEWAY_TIMEOUT","504_MESSAGE":"The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.","504_CLASS":t.SERVER_ERROR,GATEWAY_TIMEOUT:504,505:"HTTP Version not Supported","505_NAME":"HTTP_VERSION_NOT_SUPPORTED","505_MESSAGE":"The server does not support the HTTP protocol version used in the request.","505_CLASS":t.SERVER_ERROR,HTTP_VERSION_NOT_SUPPORTED:505,506:"Variant Also Negotiates","506_NAME":"VARIANT_ALSO_NEGOTIATES","506_MESSAGE":"Transparent content negotiation for the request results in a circular reference.","506_CLASS":t.SERVER_ERROR,VARIANT_ALSO_NEGOTIATES:506,507:"Insufficient Storage","507_NAME":"INSUFFICIENT_STORAGE","507_MESSAGE":"The server is unable to store the representation needed to complete the request.","507_CLASS":t.SERVER_ERROR,INSUFFICIENT_STORAGE:507,508:"Loop Detected","508_NAME":"LOOP_DETECTED","508_MESSAGE":"The server detected an infinite loop while processing the request.","508_CLASS":t.SERVER_ERROR,LOOP_DETECTED:508,510:"Not Extended","510_NAME":"NOT_EXTENDED","510_MESSAGE":"Further extensions to the request are required for the server to fulfil it.","510_CLASS":t.SERVER_ERROR,NOT_EXTENDED:510,511:"Network Authentication Required","511_NAME":"NETWORK_AUTHENTICATION_REQUIRED","511_MESSAGE":"The client needs to authenticate to gain network access. Intended for use by intercepting proxies used to control access to the network.","511_CLASS":t.SERVER_ERROR,NETWORK_AUTHENTICATION_REQUIRED:511,extra:{unofficial:{103:"Checkpoint","103_NAME":"CHECKPOINT","103_MESSAGE":"Used in the resumable requests proposal to resume aborted PUT or POST requests.","103_CLASS":t.INFORMATIONAL,CHECKPOINT:103,419:"Page Expired","419_NAME":"PAGE_EXPIRED","419_MESSAGE":"Used by the Laravel Framework when a CSRF Token is missing or expired.","419_CLASS":t.CLIENT_ERROR,PAGE_EXPIRED:419,218:"This is fine","218_NAME":"THIS_IS_FINE","218_MESSAGE":"Used as a catch-all error condition for allowing response bodies to flow through Apache when ProxyErrorOverride is enabled. When ProxyErrorOverride is enabled in Apache, response bodies that contain a status code of 4xx or 5xx are automatically discarded by Apache in favor of a generic response or a custom response specified by the ErrorDocument directive.","218_CLASS":t.SUCCESSFUL,THIS_IS_FINE:218,420:"Enhance Your Calm","420_NAME":"ENHANCE_YOUR_CALM","420_MESSAGE":"Returned by version 1 of the Twitter Search and Trends API when the client is being rate limited; versions 1.1 and later use the 429 Too Many Requests response code instead.","420_CLASS":t.CLIENT_ERROR,ENHANCE_YOUR_CALM:420,450:"Blocked by Windows Parental Controls","450_NAME":"BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS","450_MESSAGE":"The Microsoft extension code indicated when Windows Parental Controls are turned on and are blocking access to the requested webpage.","450_CLASS":t.CLIENT_ERROR,BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS:450,498:"Invalid Token","498_NAME":"INVALID_TOKEN","498_MESSAGE":"Returned by ArcGIS for Server. Code 498 indicates an expired or otherwise invalid token.","498_CLASS":t.CLIENT_ERROR,INVALID_TOKEN:498,499:"Token Required","499_NAME":"TOKEN_REQUIRED","499_MESSAGE":"Returned by ArcGIS for Server. Code 499 indicates that a token is required but was not submitted.","499_CLASS":t.CLIENT_ERROR,TOKEN_REQUIRED:499,509:"Bandwidth Limit Exceeded","509_NAME":"BANDWIDTH_LIMIT_EXCEEDED","509_MESSAGE":"The server has exceeded the bandwidth specified by the server administrator.","509_CLASS":t.SERVER_ERROR,BANDWIDTH_LIMIT_EXCEEDED:509,530:"Site is frozen","530_NAME":"SITE_IS_FROZEN","530_MESSAGE":"Used by the Pantheon web platform to indicate a site that has been frozen due to inactivity.","530_CLASS":t.SERVER_ERROR,SITE_IS_FROZEN:530,598:"Network read timeout error","598_NAME":"NETWORK_READ_TIMEOUT_ERROR","598_MESSAGE":"Used by some HTTP proxies to signal a network read timeout behind the proxy to a client in front of the proxy.","598_CLASS":t.SERVER_ERROR,NETWORK_READ_TIMEOUT_ERROR:598},iis:{440:"Login Time-out","440_NAME":"LOGIN_TIME_OUT","440_MESSAGE":"The client's session has expired and must log in again.","440_CLASS":t.CLIENT_ERROR,LOGIN_TIME_OUT:440,449:"Retry With","449_NAME":"RETRY_WITH","449_MESSAGE":"The server cannot honour the request because the user has not provided the required information.","449_CLASS":t.CLIENT_ERROR,RETRY_WITH:449,451:"Redirect","451_NAME":"REDIRECT","451_MESSAGE":"Used in Exchange ActiveSync when either a more efficient server is available or the server cannot access the users' mailbox.","451_CLASS":t.CLIENT_ERROR,REDIRECT:451},nginx:{444:"No Response","444_NAME":"NO_RESPONSE","444_MESSAGE":"Used internally to instruct the server to return no information to the client and close the connection immediately.","444_CLASS":t.CLIENT_ERROR,NO_RESPONSE:444,494:"Request header too large","494_NAME":"REQUEST_HEADER_TOO_LARGE","494_MESSAGE":"Client sent too large request or too long header line.","494_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_TOO_LARGE:494,495:"SSL Certificate Error","495_NAME":"SSL_CERTIFICATE_ERROR","495_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has provided an invalid client certificate.","495_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_ERROR:495,496:"SSL Certificate Required","496_NAME":"SSL_CERTIFICATE_REQUIRED","496_MESSAGE":"An expansion of the 400 Bad Request response code, used when a client certificate is required but not provided.","496_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_REQUIRED:496,497:"HTTP Request Sent to HTTPS Port","497_NAME":"HTTP_REQUEST_SENT_TO_HTTPS_PORT","497_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests.","497_CLASS":t.CLIENT_ERROR,HTTP_REQUEST_SENT_TO_HTTPS_PORT:497,499:"Client Closed Request","499_NAME":"CLIENT_CLOSED_REQUEST","499_MESSAGE":"Used when the client has closed the request before the server could send a response.","499_CLASS":t.CLIENT_ERROR,CLIENT_CLOSED_REQUEST:499},cloudflare:{520:"Unknown Error","520_NAME":"UNKNOWN_ERROR","520_MESSAGE":'The 520 error is used as a "catch-all response for when the origin server returns something unexpected", listing connection resets, large headers, and empty or invalid responses as common triggers.',"520_CLASS":t.SERVER_ERROR,UNKNOWN_ERROR:520,521:"Web Server Is Down","521_NAME":"WEB_SERVER_IS_DOWN","521_MESSAGE":"The origin server has refused the connection from Cloudflare.","521_CLASS":t.SERVER_ERROR,WEB_SERVER_IS_DOWN:521,522:"Connection Timed Out","522_NAME":"CONNECTION_TIMED_OUT","522_MESSAGE":"Cloudflare could not negotiate a TCP handshake with the origin server.","522_CLASS":t.SERVER_ERROR,CONNECTION_TIMED_OUT:522,523:"Origin Is Unreachable","523_NAME":"ORIGIN_IS_UNREACHABLE","523_MESSAGE":"Cloudflare could not reach the origin server.","523_CLASS":t.SERVER_ERROR,ORIGIN_IS_UNREACHABLE:523,524:"A Timeout Occurred","524_NAME":"A_TIMEOUT_OCCURRED","524_MESSAGE":"Cloudflare was able to complete a TCP connection to the origin server, but did not receive a timely HTTP response.","524_CLASS":t.SERVER_ERROR,A_TIMEOUT_OCCURRED:524,525:"SSL Handshake Failed","525_NAME":"SSL_HANDSHAKE_FAILED","525_MESSAGE":"Cloudflare could not negotiate a SSL/TLS handshake with the origin server.","525_CLASS":t.SERVER_ERROR,SSL_HANDSHAKE_FAILED:525,526:"Invalid SSL Certificate","526_NAME":"INVALID_SSL_CERTIFICATE","526_MESSAGE":"Cloudflare could not validate the SSL/TLS certificate that the origin server presented.","526_CLASS":t.SERVER_ERROR,INVALID_SSL_CERTIFICATE:526,527:"Railgun Error","527_NAME":"RAILGUN_ERROR","527_MESSAGE":"Error 527 indicates that the request timed out or failed after the WAN connection had been established.","527_CLASS":t.SERVER_ERROR,RAILGUN_ERROR:527}}}},672:e=>{"use strict";e.exports=e=>{if(typeof e!=="number"){throw new TypeError("Expected a number")}const t=e>0?Math.floor:Math.ceil;return{days:t(e/864e5),hours:t(e/36e5)%24,minutes:t(e/6e4)%60,seconds:t(e/1e3)%60,milliseconds:t(e)%1e3,microseconds:t(e*1e3)%1e3,nanoseconds:t(e*1e6)%1e3}}},720:(e,t,r)=>{"use strict";const n=r(672);const pluralize=(e,t)=>t===1?e:`${e}s`;const o=1e-7;e.exports=(e,t={})=>{if(!Number.isFinite(e)){throw new TypeError("Expected a finite number")}if(t.colonNotation){t.compact=false;t.formatSubMilliseconds=false;t.separateMilliseconds=false;t.verbose=false}if(t.compact){t.secondsDecimalDigits=0;t.millisecondsDecimalDigits=0}const r=[];const floorDecimals=(e,t)=>{const r=Math.floor(e*10**t+o);const n=Math.round(r)/10**t;return n.toFixed(t)};const add=(e,n,o,s)=>{if((r.length===0||!t.colonNotation)&&e===0&&!(t.colonNotation&&o==="m")){return}s=(s||e||"0").toString();let i;let a;if(t.colonNotation){i=r.length>0?":":"";a="";const e=s.includes(".")?s.split(".")[0].length:s.length;const t=r.length>0?2:1;s="0".repeat(Math.max(0,t-e))+s}else{i="";a=t.verbose?" "+pluralize(n,e):o}r.push(i+s+a)};const s=n(e);add(Math.trunc(s.days/365),"year","y");add(s.days%365,"day","d");add(s.hours,"hour","h");add(s.minutes,"minute","m");if(t.separateMilliseconds||t.formatSubMilliseconds||!t.colonNotation&&e<1e3){add(s.seconds,"second","s");if(t.formatSubMilliseconds){add(s.milliseconds,"millisecond","ms");add(s.microseconds,"microsecond","µs");add(s.nanoseconds,"nanosecond","ns")}else{const e=s.milliseconds+s.microseconds/1e3+s.nanoseconds/1e6;const r=typeof t.millisecondsDecimalDigits==="number"?t.millisecondsDecimalDigits:0;const n=e>=1?Math.round(e):Math.ceil(e);const o=r?e.toFixed(r):n;add(Number.parseFloat(o,10),"millisecond","ms",o)}}else{const r=e/1e3%60;const n=typeof t.secondsDecimalDigits==="number"?t.secondsDecimalDigits:1;const o=floorDecimals(r,n);const s=t.keepDecimalsOnWholeSeconds?o:o.replace(/\.0+$/,"");add(Number.parseFloat(s,10),"second","s",s)}if(r.length===0){return"0"+(t.verbose?" milliseconds":"ms")}if(t.compact){return r[0]}if(typeof t.unitCount==="number"){const e=t.colonNotation?"":" ";return r.slice(0,Math.max(t.unitCount,1)).join(e)}return t.colonNotation?r.join(""):r.join(" ")}},504:(e,t,r)=>{"use strict";const n=r(734);e.exports=()=>{const e=process.hrtime();const end=t=>n(process.hrtime(e))[t];const returnValue=()=>end("milliseconds");returnValue.rounded=()=>Math.round(end("milliseconds"));returnValue.seconds=()=>end("seconds");returnValue.nanoseconds=()=>end("nanoseconds");return returnValue}},491:e=>{"use strict";e.exports=require("assert")},300:e=>{"use strict";e.exports=require("buffer")},113:e=>{"use strict";e.exports=require("crypto")},361:e=>{"use strict";e.exports=require("events")},147:e=>{"use strict";e.exports=require("fs")},685:e=>{"use strict";e.exports=require("http")},808:e=>{"use strict";e.exports=require("net")},277:e=>{"use strict";e.exports=require("next/dist/compiled/@edge-runtime/primitives/crypto")},37:e=>{"use strict";e.exports=require("os")},17:e=>{"use strict";e.exports=require("path")},74:e=>{"use strict";e.exports=require("perf_hooks")},282:e=>{"use strict";e.exports=require("process")},477:e=>{"use strict";e.exports=require("punycode")},781:e=>{"use strict";e.exports=require("stream")},404:e=>{"use strict";e.exports=require("tls")},837:e=>{"use strict";e.exports=require("util")},144:e=>{"use strict";e.exports=require("vm")},796:e=>{"use strict";e.exports=require("zlib")}};var __webpack_module_cache__={};function __nccwpck_require__(e){var t=__webpack_module_cache__[e];if(t!==undefined){return t.exports}var r=__webpack_module_cache__[e]={exports:{}};var n=true;try{__webpack_modules__[e].call(r.exports,r,r.exports,__nccwpck_require__);n=false}finally{if(n)delete __webpack_module_cache__[e]}return r.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__={};(()=>{"use strict";var e=__webpack_exports__;Object.defineProperty(e,"__esModule",{value:true});e.EdgeRuntime=e.runServer=e.createHandler=void 0;var t=__nccwpck_require__(179);Object.defineProperty(e,"createHandler",{enumerable:true,get:function(){return t.createHandler}});Object.defineProperty(e,"runServer",{enumerable:true,get:function(){return t.runServer}});var r=__nccwpck_require__(663);Object.defineProperty(e,"EdgeRuntime",{enumerable:true,get:function(){return r.EdgeRuntime}})})();module.exports=__webpack_exports__})(); \ No newline at end of file diff --git a/packages/next/package.json b/packages/next/package.json index 8f92e4877b2d..8739355e7a77 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -117,7 +117,7 @@ "@babel/runtime": "7.15.4", "@babel/traverse": "7.18.0", "@babel/types": "7.18.0", - "@edge-runtime/primitives": "1.1.0-beta.23", + "@edge-runtime/primitives": "1.1.0-beta.25", "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.7.0", "@napi-rs/triples": "1.1.0", @@ -194,7 +194,7 @@ "debug": "4.1.1", "devalue": "2.0.1", "domain-browser": "4.19.0", - "edge-runtime": "1.1.0-beta.23", + "edge-runtime": "1.1.0-beta.25", "events": "3.3.0", "find-cache-dir": "3.3.1", "find-up": "4.1.0", diff --git a/packages/next/server.d.ts b/packages/next/server.d.ts index b0d89eabfa89..c9b6d82e3229 100644 --- a/packages/next/server.d.ts +++ b/packages/next/server.d.ts @@ -4,3 +4,4 @@ export { NextResponse } from 'next/dist/server/web/spec-extension/response' export { NextMiddleware } from 'next/dist/server/web/types' export { userAgentFromString } from 'next/dist/server/web/spec-extension/user-agent' export { userAgent } from 'next/dist/server/web/spec-extension/user-agent' +export { URLPattern } from 'next/dist/compiled/@edge-runtime/primitives/url' diff --git a/packages/next/server.js b/packages/next/server.js index 6e0bd09bcbac..904315dc9f7b 100644 --- a/packages/next/server.js +++ b/packages/next/server.js @@ -7,4 +7,5 @@ module.exports = { .userAgentFromString, userAgent: require('next/dist/server/web/spec-extension/user-agent') .userAgent, + URLPattern: global.URLPattern, } diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index a0e73d1b5d2b..36c5ebf0404a 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -254,12 +254,17 @@ export async function ncc_edge_runtime_primitives() { const dest2 = `compiled/@edge-runtime/primitives/${file}` await fs.outputJson(join(dest2, 'package.json'), { main: `../${file}.js`, + types: `../${file}.d.ts`, }) await fs.copy( require.resolve(`@edge-runtime/primitives/${file}`), join(dest, `${file}.js`) ) + await fs.copy( + require.resolve(`@edge-runtime/primitives/types/${file}.d.ts`), + join(dest, `${file}.d.ts`) + ) } await fs.outputJson(join(dest, 'package.json'), { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d3834857514..9ba66f9e3a14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: '@babel/plugin-proposal-object-rest-spread': 7.14.7 '@babel/preset-flow': 7.14.5 '@babel/preset-react': 7.14.5 - '@edge-runtime/jest-environment': 1.1.0-beta.23 + '@edge-runtime/jest-environment': 1.1.0-beta.25 '@fullhuman/postcss-purgecss': 1.3.0 '@mdx-js/loader': 0.18.0 '@next/bundle-analyzer': workspace:* @@ -172,7 +172,7 @@ importers: '@babel/plugin-proposal-object-rest-spread': 7.14.7_@babel+core@7.18.0 '@babel/preset-flow': 7.14.5_@babel+core@7.18.0 '@babel/preset-react': 7.14.5_@babel+core@7.18.0 - '@edge-runtime/jest-environment': 1.1.0-beta.23 + '@edge-runtime/jest-environment': 1.1.0-beta.25 '@fullhuman/postcss-purgecss': 1.3.0 '@mdx-js/loader': 0.18.0_uuaxwgga6hqycsez5ok7v2wg4i '@next/bundle-analyzer': link:packages/next-bundle-analyzer @@ -414,7 +414,7 @@ importers: '@babel/runtime': 7.15.4 '@babel/traverse': 7.18.0 '@babel/types': 7.18.0 - '@edge-runtime/primitives': 1.1.0-beta.23 + '@edge-runtime/primitives': 1.1.0-beta.25 '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.7.0 '@napi-rs/triples': 1.1.0 @@ -494,7 +494,7 @@ importers: debug: 4.1.1 devalue: 2.0.1 domain-browser: 4.19.0 - edge-runtime: 1.1.0-beta.23 + edge-runtime: 1.1.0-beta.25 events: 3.3.0 find-cache-dir: 3.3.1 find-up: 4.1.0 @@ -607,7 +607,7 @@ importers: '@babel/runtime': 7.15.4 '@babel/traverse': 7.18.0 '@babel/types': 7.18.0 - '@edge-runtime/primitives': 1.1.0-beta.23 + '@edge-runtime/primitives': 1.1.0-beta.25 '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.7.0 '@napi-rs/triples': 1.1.0 @@ -684,7 +684,7 @@ importers: debug: 4.1.1 devalue: 2.0.1 domain-browser: 4.19.0 - edge-runtime: 1.1.0-beta.23 + edge-runtime: 1.1.0-beta.25 events: 3.3.0 find-cache-dir: 3.3.1 find-up: 4.1.0 @@ -3214,14 +3214,14 @@ packages: protobufjs: 6.11.2 dev: true - /@edge-runtime/format/1.1.0-beta.23: - resolution: {integrity: sha512-TuY7ywLzp2XQQZpM8cX1dzm2QMK2juvtpMVR8K61utL2qvokzJ4gBYLcPSKhH0EWAt4WwgymNVRX0cpdSLAqAg==} + /@edge-runtime/format/1.1.0-beta.25: + resolution: {integrity: sha512-7fzTXgfVzcWs6T8ePVyogbVU67TbXpDHhgop9yP9stsRlejJjD2bDm/jDwX9XAfQdET5gVaKDiYc0wkp9E4gig==} dev: true - /@edge-runtime/jest-environment/1.1.0-beta.23: - resolution: {integrity: sha512-mQ5Vst9k44umfsR90mLy6DqatEPBOiU25T1mtKdmyuQVXhvcH2+3oevTu6/aPW/5J489ZXp60BDWfye4lQD7ew==} + /@edge-runtime/jest-environment/1.1.0-beta.25: + resolution: {integrity: sha512-tOjxI0/8m0beqEiOXmDzPCqpycUiMLvIV1PoQ9zb5qcxJqF8AcmZMRqICfdqnWo+P3qJIr8YeZSG31lz5EaJfw==} dependencies: - '@edge-runtime/vm': 1.1.0-beta.23 + '@edge-runtime/vm': 1.1.0-beta.25 '@jest/environment': 28.1.3 '@jest/fake-timers': 28.1.3 '@jest/types': 28.1.3 @@ -3229,14 +3229,14 @@ packages: jest-util: 28.1.3 dev: true - /@edge-runtime/primitives/1.1.0-beta.23: - resolution: {integrity: sha512-0vHcZZwyxjmw/so9irYtA82/+nAlJRs+1WpRYBx7iae1FOGCPM4BIKEmboWmwTuj7c6avz9kIbptokdMUPgV9A==} + /@edge-runtime/primitives/1.1.0-beta.25: + resolution: {integrity: sha512-+lKore2sAuGD2AMa2GZviLMHKfFmw5k2BzhvyatKPuJ/frIFpb1OdluxGHfqmVBtNIJwnn7IJoJd9jm8r/6Flg==} dev: true - /@edge-runtime/vm/1.1.0-beta.23: - resolution: {integrity: sha512-XBp3rCuX4scJVOo2KconAotL5XGX3zdd8IkfDNr5VVSQ/B6HkiTNuf+EvzSQTpplF+fiyLTpfcP9EbNLibwLTA==} + /@edge-runtime/vm/1.1.0-beta.25: + resolution: {integrity: sha512-xDre6a3L0KXsxQxuZaXw5ukyGh5v7k1E7XL5ooxBbaHz+5GtvCidSZ3sPvFDzetlKq7eBT4ztg4RTkHbCUclDA==} dependencies: - '@edge-runtime/primitives': 1.1.0-beta.23 + '@edge-runtime/primitives': 1.1.0-beta.25 dev: true /@emotion/is-prop-valid/0.8.8: @@ -9968,12 +9968,12 @@ packages: safe-buffer: 5.2.0 dev: true - /edge-runtime/1.1.0-beta.23: - resolution: {integrity: sha512-A7dO/Y+4UJnaxFcdz6pepL+0GcvvViWvf201oFQXepgdSxPDKiqxaayCag0eiirQ6OfF+cSTmPD3xrfEoAIjiQ==} + /edge-runtime/1.1.0-beta.25: + resolution: {integrity: sha512-2yC0hqnMis0t2zIFCWsV7uu8hEhUZDgVszZyXS/C4719r1km2FkL5kWk+6ktVD4A6fUTnzTrYPq+fzTxIt7X2A==} hasBin: true dependencies: - '@edge-runtime/format': 1.1.0-beta.23 - '@edge-runtime/vm': 1.1.0-beta.23 + '@edge-runtime/format': 1.1.0-beta.25 + '@edge-runtime/vm': 1.1.0-beta.25 exit-hook: 2.2.1 http-status: 1.5.2 mri: 1.2.0 @@ -10431,6 +10431,7 @@ packages: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: eslint: 7.32.0 + dev: false /eslint-plugin-react/7.23.2_eslint@7.24.0: resolution: {integrity: sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==} diff --git a/test/e2e/middleware-general/app/middleware.js b/test/e2e/middleware-general/app/middleware.js index 058041e03414..83ba6722ff61 100644 --- a/test/e2e/middleware-general/app/middleware.js +++ b/test/e2e/middleware-general/app/middleware.js @@ -1,5 +1,5 @@ -/* global globalThis, URLPattern */ -import { NextRequest, NextResponse } from 'next/server' +/* global globalThis */ +import { NextRequest, NextResponse, URLPattern } from 'next/server' import magicValue from 'shared-package' const PATTERNS = [ diff --git a/test/e2e/middleware-trailing-slash/app/middleware.js b/test/e2e/middleware-trailing-slash/app/middleware.js index 409ef88c3527..4d6d5b320420 100644 --- a/test/e2e/middleware-trailing-slash/app/middleware.js +++ b/test/e2e/middleware-trailing-slash/app/middleware.js @@ -1,5 +1,4 @@ -/* global URLPattern */ -import { NextResponse } from 'next/server' +import { NextResponse, URLPattern } from 'next/server' export async function middleware(request) { const url = request.nextUrl diff --git a/test/production/middleware-typescript/app/middleware.ts b/test/production/middleware-typescript/app/middleware.ts index 44d2ec9639e7..5010b2695279 100644 --- a/test/production/middleware-typescript/app/middleware.ts +++ b/test/production/middleware-typescript/app/middleware.ts @@ -1,6 +1,11 @@ -import { NextMiddleware, NextResponse } from 'next/server' +import { NextMiddleware, NextResponse, URLPattern } from 'next/server' export const middleware: NextMiddleware = function (request) { + const pattern = new URLPattern({ + pathname: '/:path', + }) + console.log(pattern.test(request.nextUrl.pathname)) + if (request.nextUrl.pathname === '/static') { return new NextResponse(null, { headers: { From 0e5a0ec4e0951fafe0a81da193f89920113e24f4 Mon Sep 17 00:00:00 2001 From: Matt DuLeone Date: Mon, 1 Aug 2022 22:18:21 -0400 Subject: [PATCH 06/10] Clear up rewrites documentation (#39238) ## Documentation / Examples This PR specifically calls out in more clear language how the rewrites function behaves. This tripped me up recently as I recently came looking for more information, and the documentation left me with more questions than answers. This is an attempt to clear up the confusion I walked away with for anyone who comes in after me. - [x] Make sure the linting passes by running `pnpm lint` - [x] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) --- docs/api-reference/next.config.js/rewrites.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-reference/next.config.js/rewrites.md b/docs/api-reference/next.config.js/rewrites.md index 8caba442c54c..d83621a7ca8f 100644 --- a/docs/api-reference/next.config.js/rewrites.md +++ b/docs/api-reference/next.config.js/rewrites.md @@ -42,7 +42,7 @@ module.exports = { Rewrites are applied to client-side routing, a `` will have the rewrite applied in the above example. -`rewrites` is an async function that expects an array to be returned holding objects with `source` and `destination` properties: +`rewrites` is an async function that expects to return either an array or an object of arrays (see below) holding objects with `source` and `destination` properties: - `source`: `String` - is the incoming request path pattern. - `destination`: `String` is the path you want to route to. @@ -50,7 +50,7 @@ Rewrites are applied to client-side routing, a `` will have - `locale`: `false` or `undefined` - whether the locale should not be included when matching. - `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. -Rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes by default. This behavior can be changed by returning an object instead of an array from the `rewrites` function since `v10.1` of Next.js: +When the `rewrites` function returns an array, rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes. When the `rewrites` function returns an object of arrays with a specific shape, this behavior can be changed and more finely controlled, as of `v10.1` of Next.js: ```js module.exports = { From c4e8d7d7a32d75603c5d3e5f6a84a5154be7331f Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Mon, 1 Aug 2022 19:31:04 -0700 Subject: [PATCH 07/10] Update edgedb.toml (#39243) Update `with-edgedb` to use `2.0`. --- examples/with-edgedb/edgedb.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-edgedb/edgedb.toml b/examples/with-edgedb/edgedb.toml index b4968c856040..6fe27091caf7 100644 --- a/examples/with-edgedb/edgedb.toml +++ b/examples/with-edgedb/edgedb.toml @@ -1,2 +1,2 @@ [edgedb] -server-version = "1.2" +server-version = "2" From cd3e054f14ce38f4ff57c727a997da2a6e1d05dd Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 1 Aug 2022 21:39:58 -0500 Subject: [PATCH 08/10] Ensure middleware has single data fetch on query hydration with valid props (#39210) * Ensure middleware has single data fetch on query hydration with valid props * re-add check * fix redirect case --- packages/next/shared/lib/router/router.ts | 42 +++++++++---------- .../middleware-rewrites/test/index.test.ts | 21 ++++++++++ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index 8a2b1b57cf6e..72b7c98bd86c 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -427,7 +427,7 @@ const backgroundCache: Record> = {} interface FetchDataOutput { dataHref: string - json: Record + json: Record | null response: Response text: string } @@ -508,7 +508,7 @@ function fetchNextData({ return { dataHref, - json: parseJSON ? tryToParseAsJSON(text) : {}, + json: parseJSON ? tryToParseAsJSON(text) : null, response, text, } @@ -552,7 +552,7 @@ function tryToParseAsJSON(text: string) { try { return JSON.parse(text) } catch (error) { - return {} + return null } } @@ -1807,28 +1807,24 @@ export default class Router implements BaseRouter { const { props } = await this._getData(async () => { if (shouldFetchData && !useStreamedFlightData) { - const { json } = - data?.json && - data?.response.headers - .get('content-type') - ?.includes('application/json') - ? data - : await fetchNextData({ - dataHref: this.pageLoader.getDataHref({ - href: formatWithValidation({ pathname, query }), - asPath: resolvedAs, - locale, - }), - isServerRender: this.isSsr, - parseJSON: true, - inflightCache: this.sdc, - persistCache: !isPreview, - isPrefetch: false, - unstable_skipClientCache, - }) + const { json } = data?.json + ? data + : await fetchNextData({ + dataHref: this.pageLoader.getDataHref({ + href: formatWithValidation({ pathname, query }), + asPath: resolvedAs, + locale, + }), + isServerRender: this.isSsr, + parseJSON: true, + inflightCache: this.sdc, + persistCache: !isPreview, + isPrefetch: false, + unstable_skipClientCache, + }) return { - props: json, + props: json || {}, } } diff --git a/test/e2e/middleware-rewrites/test/index.test.ts b/test/e2e/middleware-rewrites/test/index.test.ts index 8af533562049..131282868802 100644 --- a/test/e2e/middleware-rewrites/test/index.test.ts +++ b/test/e2e/middleware-rewrites/test/index.test.ts @@ -27,6 +27,27 @@ describe('Middleware Rewrite', () => { testsWithLocale('/fr') function tests() { + it('should not have un-necessary data request on rewrite', async () => { + const browser = await webdriver(next.url, '/to-blog/first', { + waitHydration: false, + }) + let requests = [] + + browser.on('request', (req) => { + requests.push(new URL(req.url()).pathname) + }) + + await check( + () => browser.eval(`next.router.isReady ? "yup" : "nope"`), + 'yup' + ) + + expect( + requests.filter((url) => url === '/fallback-true-blog/first.json') + .length + ).toBeLessThan(2) + }) + it('should not mix component cache when navigating between dynamic routes', async () => { const browser = await webdriver(next.url, '/param-1') From b7997105a024fb063c2ab4cde533440408e43a9c Mon Sep 17 00:00:00 2001 From: Duc Tran Date: Wed, 3 Aug 2022 05:01:24 +0700 Subject: [PATCH 09/10] fix: use `if...else` in dockerfile (#39263) * fix: use if-else in dockerfile * fix: remove typo --- examples/with-docker-compose/next-app/dev.Dockerfile | 8 +++++--- .../next-app/prod-without-multistage.Dockerfile | 2 +- examples/with-docker-compose/next-app/prod.Dockerfile | 4 ++-- .../docker/development/Dockerfile | 10 +++++----- .../with-docker-multi-env/docker/production/Dockerfile | 2 +- .../with-docker-multi-env/docker/staging/Dockerfile | 2 +- examples/with-docker/Dockerfile | 2 +- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/examples/with-docker-compose/next-app/dev.Dockerfile b/examples/with-docker-compose/next-app/dev.Dockerfile index 37db8a878ff8..fbba79e67541 100644 --- a/examples/with-docker-compose/next-app/dev.Dockerfile +++ b/examples/with-docker-compose/next-app/dev.Dockerfile @@ -5,9 +5,11 @@ WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ RUN \ - [ -f package-lock.json ] && npm install || \ - [ -f pnpm-lock.yaml ] && yarn global add pnpm && pnpm install || \ - yarn install + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ + else echo "Lockfile not found." && exit 1; \ + fi COPY src ./src COPY public ./public diff --git a/examples/with-docker-compose/next-app/prod-without-multistage.Dockerfile b/examples/with-docker-compose/next-app/prod-without-multistage.Dockerfile index 546e0800ebab..54a9fde2f118 100644 --- a/examples/with-docker-compose/next-app/prod-without-multistage.Dockerfile +++ b/examples/with-docker-compose/next-app/prod-without-multistage.Dockerfile @@ -6,7 +6,7 @@ WORKDIR /app COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ # Omit --production flag for TypeScript devDependencies RUN \ - if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ else echo "Lockfile not found." && exit 1; \ diff --git a/examples/with-docker-compose/next-app/prod.Dockerfile b/examples/with-docker-compose/next-app/prod.Dockerfile index f68877b1f508..c556f05b2c79 100644 --- a/examples/with-docker-compose/next-app/prod.Dockerfile +++ b/examples/with-docker-compose/next-app/prod.Dockerfile @@ -7,7 +7,7 @@ WORKDIR /app COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ # Omit --production flag for TypeScript devDependencies RUN \ - if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ else echo "Lockfile not found." && exit 1; \ @@ -45,7 +45,7 @@ COPY --from=builder /app/public ./public COPY --from=builder /app/next.config.js . COPY --from=builder /app/package.json . -# Automatically leverage output traces to reduce image size +# Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static diff --git a/examples/with-docker-multi-env/docker/development/Dockerfile b/examples/with-docker-multi-env/docker/development/Dockerfile index eb8614758767..90f7136866f5 100644 --- a/examples/with-docker-multi-env/docker/development/Dockerfile +++ b/examples/with-docker-multi-env/docker/development/Dockerfile @@ -8,10 +8,10 @@ WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ RUN \ - if [ -f yarn.lock ]; then yarn --frozen-lockfile;\ - elif [ -f package-lock.json ]; then npm ci;\ - elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i;\ - else echo "Lockfile not found." && exit 1;\ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ + else echo "Lockfile not found." && exit 1; \ fi # 2. Rebuild the source code only when needed @@ -37,7 +37,7 @@ RUN adduser -S nextjs -u 1001 COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json -# Automatically leverage output traces to reduce image size +# Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static diff --git a/examples/with-docker-multi-env/docker/production/Dockerfile b/examples/with-docker-multi-env/docker/production/Dockerfile index 34771b1b52cf..473fc55109dd 100644 --- a/examples/with-docker-multi-env/docker/production/Dockerfile +++ b/examples/with-docker-multi-env/docker/production/Dockerfile @@ -38,7 +38,7 @@ RUN adduser -S nextjs -u 1001 COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json -# Automatically leverage output traces to reduce image size +# Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static diff --git a/examples/with-docker-multi-env/docker/staging/Dockerfile b/examples/with-docker-multi-env/docker/staging/Dockerfile index 26cfdc8efb90..e085fa7bef9b 100644 --- a/examples/with-docker-multi-env/docker/staging/Dockerfile +++ b/examples/with-docker-multi-env/docker/staging/Dockerfile @@ -38,7 +38,7 @@ RUN adduser -S nextjs -u 1001 COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json -# Automatically leverage output traces to reduce image size +# Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static diff --git a/examples/with-docker/Dockerfile b/examples/with-docker/Dockerfile index 91da36d1dc62..6d15a205dac9 100644 --- a/examples/with-docker/Dockerfile +++ b/examples/with-docker/Dockerfile @@ -46,7 +46,7 @@ RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json -# Automatically leverage output traces to reduce image size +# Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static From 213c42f446874d29d07fa2cca6e6b11fc9c3b711 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 2 Aug 2022 19:23:10 -0400 Subject: [PATCH 10/10] Add section about migrating from `next/image` to `next/future/image` (#39270) This expands on the previous PR #38978 with a section on migrating --- docs/api-reference/next/future/image.md | 131 +++++++++++++++++++++++- 1 file changed, 127 insertions(+), 4 deletions(-) diff --git a/docs/api-reference/next/future/image.md b/docs/api-reference/next/future/image.md index 940ea805cb88..c4d249e30f08 100644 --- a/docs/api-reference/next/future/image.md +++ b/docs/api-reference/next/future/image.md @@ -30,6 +30,8 @@ module.exports = { } ``` +## Comparison + Compared to `next/image`, the new `next/future/image` component has the following changes: - Renders a single `` without `
` or `` wrappers @@ -39,13 +41,134 @@ Compared to `next/image`, the new `next/future/image` component has the followin - Removes `loader` config in favor of [`loader`](#loader) prop - Note: the [`onError`](#onerror) prop might behave differently -The default layout for `next/image` was `intrinsic`, which would shrink the `width` if the image was larger than it's container. Since no styles are automatically applied to `next/future/image`, you'll need to add the following CSS to achieve the same behavior: +## Migration + +Although `layout` is not available, you can migrate `next/image` to `next/future/image` using a few props. The following is a comparison of the two components: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
next/imagenext/future/image
+ +```jsx +import Image from 'next/image' +import img from '../img.png' + +function Page() { + return +} +``` + + + +```jsx +import Image from 'next/future/image' +import img from '../img.png' + +const css = { maxWidth: '100%', height: 'auto' } +function Page() { + return +} +``` + +
+ +```jsx +import Image from 'next/image' +import img from '../img.png' -```css -max-width: 100%; -height: auto; +function Page() { + return +} ``` + + +```jsx +import Image from 'next/future/image' +import img from '../img.png' + +const css = { width: '100%', height: 'auto' } +function Page() { + return +} +``` + +
+ +```jsx +import Image from 'next/image' +import img from '../img.png' + +function Page() { + return +} +``` + + + +```jsx +import Image from 'next/future/image' +import img from '../img.png' + +function Page() { + return +} +``` + +
+ +```jsx +import Image from 'next/image' +import img from '../img.png' + +function Page() { + return +} +``` + + + +```jsx +import Image from 'next/future/image' +import img from '../img.png' + +function Page() { + return +} +``` + +
+ +You can also use `className` instead of `style`. + ## Required Props The `` component requires the following properties.