From bd17872d3f3a0276ad309be38be057ca6812b7ce Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 1 Sep 2020 01:30:49 -0500 Subject: [PATCH 01/15] Add automatic reloading when editing GS(S)P methods --- packages/next/build/utils.ts | 2 +- packages/next/client/next-dev.js | 7 + packages/next/server/hot-reloader.ts | 57 +++++++- .../pages/gsp-blog/[post].js | 36 +++++ .../pages/gssp-blog/[post].js | 20 +++ .../test/index.test.js | 129 ++++++++++++++++++ yarn.lock | 7 +- 7 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 test/integration/gssp-ssr-change-reloading/pages/gsp-blog/[post].js create mode 100644 test/integration/gssp-ssr-change-reloading/pages/gssp-blog/[post].js create mode 100644 test/integration/gssp-ssr-change-reloading/test/index.test.js diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index c0a5dba6e291030..6c5e894c59ac497 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -475,7 +475,7 @@ async function computeFromManifest( return lastCompute! } -function difference(main: T[], sub: T[]): T[] { +export function difference(main: T[] | Set, sub: T[] | Set): T[] { const a = new Set(main) const b = new Set(sub) return [...a].filter((x) => !b.has(x)) diff --git a/packages/next/client/next-dev.js b/packages/next/client/next-dev.js index 2475c984fd0ad76..15454fd224fc19b 100644 --- a/packages/next/client/next-dev.js +++ b/packages/next/client/next-dev.js @@ -42,6 +42,13 @@ initNext({ webpackHMR }) .catch((err) => { console.log(`Failed to fetch devPagesManifest`, err) }) + } else if (event.data.indexOf('serverOnlyChanges') !== -1) { + const { pages } = JSON.parse(event.data) + + if (pages.includes(window.next.router.pathname)) { + console.log('Reloading page due to server-side page change') + window.location.reload() + } } } devPagesManifestListener.unfiltered = true diff --git a/packages/next/server/hot-reloader.ts b/packages/next/server/hot-reloader.ts index ba14fcb18e0e83b..c7fc19ff00fdfbe 100644 --- a/packages/next/server/hot-reloader.ts +++ b/packages/next/server/hot-reloader.ts @@ -31,6 +31,7 @@ import { isWriteable } from '../build/is-writeable' import { ClientPagesLoaderOptions } from '../build/webpack/loaders/next-client-pages-loader' import { stringify } from 'querystring' import { Rewrite } from '../lib/load-custom-routes' +import { difference } from '../build/utils' export async function renderScriptError( res: ServerResponse, @@ -356,6 +357,42 @@ export default class HotReloader { watchCompilers(multiCompiler.compilers[0], multiCompiler.compilers[1]) + // Watch for changes to client/server page files so we can tell when just + // the server file changes and trigger a reload for GS(S)P pages + const changedClientPages = new Set() + const changedServerPages = new Set() + const prevClientPageHashes = new Map() + const prevServerPageHashes = new Map() + + const trackPageChanges = ( + pageHashMap: Map, + changedItems: Set + ) => (stats: webpack.compilation.Compilation) => { + stats.entrypoints.forEach((entry, key) => { + if (key.startsWith('pages/')) { + entry.chunks.forEach((chunk: any) => { + if (chunk.id === key) { + const prevHash = pageHashMap.get(key) + + if (prevHash && prevHash !== chunk.hash) { + changedItems.add(key) + } + pageHashMap.set(key, chunk.hash) + } + }) + } + }) + } + + multiCompiler.compilers[0].hooks.emit.tap( + 'NextjsHotReloaderForClient', + trackPageChanges(prevClientPageHashes, changedClientPages) + ) + multiCompiler.compilers[1].hooks.emit.tap( + 'NextjsHotReloaderForServer', + trackPageChanges(prevServerPageHashes, changedServerPages) + ) + // This plugin watches for changes to _document.js and notifies the client side that it should reload the page multiCompiler.compilers[1].hooks.failed.tap( 'NextjsHotReloaderForServer', @@ -370,6 +407,20 @@ export default class HotReloader { this.serverError = null this.serverStats = stats + const serverOnlyChanges = difference( + changedServerPages, + changedClientPages + ) + changedClientPages.clear() + changedServerPages.clear() + + if (serverOnlyChanges.length > 0) { + this.send({ + event: 'serverOnlyChanges', + pages: serverOnlyChanges.map((pg) => pg.substr('pages'.length)), + }) + } + const { compilation } = stats // We only watch `_document` for changes on the server compilation @@ -514,8 +565,10 @@ export default class HotReloader { return [] } - public send(action?: string, ...args: any[]): void { - this.webpackHotMiddleware!.publish({ action, data: args }) + public send(action?: string | any, ...args: any[]): void { + this.webpackHotMiddleware!.publish( + action && typeof action === 'object' ? action : { action, data: args } + ) } public async ensurePage(page: string) { diff --git a/test/integration/gssp-ssr-change-reloading/pages/gsp-blog/[post].js b/test/integration/gssp-ssr-change-reloading/pages/gsp-blog/[post].js new file mode 100644 index 000000000000000..187308da7565490 --- /dev/null +++ b/test/integration/gssp-ssr-change-reloading/pages/gsp-blog/[post].js @@ -0,0 +1,36 @@ +import { useRouter } from 'next/router' + +export default function Gsp(props) { + if (useRouter().isFallback) { + return 'Loading...' + } + + return ( + <> +

change me

+

{JSON.stringify(props)}

+ + ) +} + +export const getStaticProps = ({ params }) => { + const count = 1 + + return { + props: { + count, + params, + random: Math.random(), + }, + } +} + +export const getStaticPaths = () => { + /* eslint-disable-next-line no-unused-vars */ + const paths = 1 + + return { + paths: [{ params: { post: 'first' } }, { params: { post: 'second' } }], + fallback: true, + } +} diff --git a/test/integration/gssp-ssr-change-reloading/pages/gssp-blog/[post].js b/test/integration/gssp-ssr-change-reloading/pages/gssp-blog/[post].js new file mode 100644 index 000000000000000..d6a6ef8d184043c --- /dev/null +++ b/test/integration/gssp-ssr-change-reloading/pages/gssp-blog/[post].js @@ -0,0 +1,20 @@ +export default function Gssp(props) { + return ( + <> +

change me

+

{JSON.stringify(props)}

+ + ) +} + +export const getServerSideProps = ({ params }) => { + const count = 1 + + return { + props: { + count, + params, + random: Math.random(), + }, + } +} diff --git a/test/integration/gssp-ssr-change-reloading/test/index.test.js b/test/integration/gssp-ssr-change-reloading/test/index.test.js new file mode 100644 index 000000000000000..aed30398c7681ed --- /dev/null +++ b/test/integration/gssp-ssr-change-reloading/test/index.test.js @@ -0,0 +1,129 @@ +/* eslint-env jest */ + +import { join } from 'path' +import webdriver from 'next-webdriver' +import { killApp, findPort, launchApp, File, check } from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 2) +const appDir = join(__dirname, '..') + +let appPort +let app + +describe('GS(S)P Server-Side Change Reloading', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + + it('should not reload page when client-side is changed too GSP', async () => { + const browser = await webdriver(appPort, '/gsp-blog/first') + await browser.eval(() => (window.beforeChange = 'hi')) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + + const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) + page.replace('change me', 'changed') + + await check(() => browser.elementByCss('#change').text(), 'changed') + expect(await browser.eval(() => window.beforeChange)).toBe('hi') + + const props2 = JSON.parse(await browser.elementByCss('#props').text()) + expect(props).toEqual(props2) + + page.restore() + + await check(() => browser.elementByCss('#change').text(), 'change me') + }) + + it('should reload page when getStaticProps is changed only', async () => { + const browser = await webdriver(appPort, '/gsp-blog/first') + await browser.eval(() => (window.beforeChange = 'hi')) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) + page.replace('count = 1', 'count = 2') + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '2' + ) + expect(await browser.eval(() => window.beforeChange)).toBe(null) + page.restore() + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '1' + ) + }) + + it('should reload page when getStaticPaths is changed only', async () => { + const browser = await webdriver(appPort, '/gsp-blog/first') + await browser.eval(() => (window.beforeChange = 'hi')) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) + page.replace('paths = 1', 'paths = 2') + + await check( + async () => + (await browser.eval(() => window.beforeChange)) === null + ? 'pass' + : 'fail', + 'pass' + ) + page.restore() + }) + + it('should not reload page when client-side is changed too GSSP', async () => { + const browser = await webdriver(appPort, '/gssp-blog/first') + await browser.eval(() => (window.beforeChange = 'hi')) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + + const page = new File(join(appDir, 'pages/gssp-blog/[post].js')) + page.replace('change me', 'changed') + + await check(() => browser.elementByCss('#change').text(), 'changed') + expect(await browser.eval(() => window.beforeChange)).toBe('hi') + + const props2 = JSON.parse(await browser.elementByCss('#props').text()) + expect(props).toEqual(props2) + + page.restore() + + await check(() => browser.elementByCss('#change').text(), 'change me') + }) + + it('should reload page when getServerSideProps is changed only', async () => { + const browser = await webdriver(appPort, '/gssp-blog/first') + await browser.eval(() => (window.beforeChange = 'hi')) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = new File(join(appDir, 'pages/gssp-blog/[post].js')) + page.replace('count = 1', 'count = 2') + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '2' + ) + expect(await browser.eval(() => window.beforeChange)).toBe(null) + page.restore() + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '1' + ) + }) +}) diff --git a/yarn.lock b/yarn.lock index 2267aa0f15a34a3..43baaffaca82d76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7959,9 +7959,10 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -finally-polyfill@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/finally-polyfill/-/finally-polyfill-0.1.0.tgz#2a17b16581d9477db16a703c7b79a898ac0b7d50" +finally-polyfill@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/finally-polyfill/-/finally-polyfill-0.2.0.tgz#1b34c6e555a6c1603d2ae046e2e176d08687bfdb" + integrity sha512-3w46w5Vo4TRtk5jrLT3c8ITGxnPJhMAg3Ogbj4nmgL6thNep9+UgBgk+IRVmRpZDbwNkR7tyGsE3S3J4Qt2zVw== find-cache-dir@3.3.1, find-cache-dir@^3.0.0, find-cache-dir@^3.3.1: version "3.3.1" From 86c6c8b8c2470de18fc769a133475a4aa628fa9d Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 1 Sep 2020 10:20:17 -0700 Subject: [PATCH 02/15] Export return type for GetStaticPaths (#16580) * feat Export return type for GetStaticPaths * add Duplicate generic Co-authored-by: Luis Alvarez D --- packages/next/types/index.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index 7e1a0bab3799de5..e749b4e8872ecf0 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -96,12 +96,14 @@ export type InferGetStaticPropsType = T extends GetStaticProps ? P : never -export type GetStaticPaths< - P extends ParsedUrlQuery = ParsedUrlQuery -> = () => Promise<{ +export type GetStaticPathsResult

= { paths: Array fallback: boolean | 'unstable_blocking' -}> +} + +export type GetStaticPaths< + P extends ParsedUrlQuery = ParsedUrlQuery +> = () => Promise> export type GetServerSidePropsContext< Q extends ParsedUrlQuery = ParsedUrlQuery From 2e0986fee87b2989437518df2d75bc468e202409 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Tue, 1 Sep 2020 13:43:44 -0400 Subject: [PATCH 03/15] [test] Update hydration marker for React 17 (#16756) --- packages/next/client/index.tsx | 18 ++++++++++-------- test/acceptance/helpers.js | 4 ---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 128e49063e9b5a1..3bf5a3b3f62c26d 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -151,14 +151,6 @@ class Container extends React.Component<{ } ) } - - if (process.env.__NEXT_TEST_MODE) { - window.__NEXT_HYDRATED = true - - if (window.__NEXT_HYDRATED_CB) { - window.__NEXT_HYDRATED_CB() - } - } } componentDidUpdate() { @@ -719,5 +711,15 @@ function Root({ // We use `useLayoutEffect` to guarantee the callback is executed // as soon as React flushes the update. React.useLayoutEffect(() => callback(), [callback]) + if (process.env.__NEXT_TEST_MODE) { + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + window.__NEXT_HYDRATED = true + + if (window.__NEXT_HYDRATED_CB) { + window.__NEXT_HYDRATED_CB() + } + }, []) + } return children as React.ReactElement } diff --git a/test/acceptance/helpers.js b/test/acceptance/helpers.js index 7379af1c2fae90c..dd30e780f1b0b34 100644 --- a/test/acceptance/helpers.js +++ b/test/acceptance/helpers.js @@ -37,10 +37,6 @@ export async function sandbox( env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, }) const browser = await webdriver(appPort, '/') - - // Slow down tests a bit to ensure application is ready: - await new Promise((resolve) => setTimeout(resolve, 750)) - return [ { sandboxDirectory, From f3c93e0b575046259efed18ed223ebca6d78a0b6 Mon Sep 17 00:00:00 2001 From: Joris Date: Tue, 1 Sep 2020 20:03:41 +0200 Subject: [PATCH 04/15] [EXAMPLE] with-framer-motion: fix broken images (#16714) * refactor: remove useless console.log * fix: replace broken images * Updated title Co-authored-by: Luis Alvarez Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- examples/with-framer-motion/components/Gallery.js | 8 ++++---- .../with-framer-motion/components/SingleImage.js | 2 +- examples/with-framer-motion/constants.js | 12 ++++++------ examples/with-framer-motion/pages/image/[index].js | 1 - 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/examples/with-framer-motion/components/Gallery.js b/examples/with-framer-motion/components/Gallery.js index b0e2e770d5463f1..bf7d8c090151876 100644 --- a/examples/with-framer-motion/components/Gallery.js +++ b/examples/with-framer-motion/components/Gallery.js @@ -35,7 +35,7 @@ const Thumbnail = ({ id, i }) => ( > ( const Gallery = () => ( <> -

Barbican

+

Motion

( bottom: -130px; } } - + @media screen and (min-width: 800px) { h1 { font-size: 180px; bottom: -170px; } } - + @media screen and (min-width: 1000px) { h1 { font-size: 220px; diff --git a/examples/with-framer-motion/components/SingleImage.js b/examples/with-framer-motion/components/SingleImage.js index b14569934c8f205..e1e23d53a94f5d7 100644 --- a/examples/with-framer-motion/components/SingleImage.js +++ b/examples/with-framer-motion/components/SingleImage.js @@ -28,7 +28,7 @@ const SingleImage = ({ index }) => ( diff --git a/examples/with-framer-motion/constants.js b/examples/with-framer-motion/constants.js index 6ef78bfd1998b3c..7e48c3ec64b8190 100644 --- a/examples/with-framer-motion/constants.js +++ b/examples/with-framer-motion/constants.js @@ -1,8 +1,8 @@ export const images = [ - '5b5a3938562fa764113169a6/1532639559620/DSCF3338', - '5b5a3628f950b7390fbfc5f8/1532639027872/DSCF3246', - '5b5a3741575d1fccb5ac6b3f/1532639066455/DSCF3268', - '5b5a376b0e2e728eeeaca8e4/1532683586969/DSCF3274', - '5b5c228403ce64f3c80d4d8e/1532764845121/DSCF3348', - '5b5a3b800e2e728eeead9575/1532640158813/DSCF3375', + 'photo-1520517601640-32ec514e4a15', + 'photo-1518780535463-bc357fa46e64', + 'photo-1555068178-89125fb6356d', + 'photo-1553503359-d4ff2537a6ea', + 'photo-1585211751845-37663b4ab614', + 'photo-1496467115032-c504ef76521b', ] diff --git a/examples/with-framer-motion/pages/image/[index].js b/examples/with-framer-motion/pages/image/[index].js index d997d5f94f40f58..e4e64af882922c8 100644 --- a/examples/with-framer-motion/pages/image/[index].js +++ b/examples/with-framer-motion/pages/image/[index].js @@ -17,7 +17,6 @@ export async function getStaticProps({ params }) { export async function getStaticPaths() { return { paths: images.map((_id, index) => { - console.log(index) return { params: { index: `${index}`, From d3cf1cecc7f938d1146a609fbabdf150bf1d5518 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Tue, 1 Sep 2020 14:09:25 -0400 Subject: [PATCH 05/15] Revert #14580 (#16757) --- packages/next/client/page-loader.ts | 31 +++++++++++------------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/next/client/page-loader.ts b/packages/next/client/page-loader.ts index fc4f7b073448c06..a95267189fea139 100644 --- a/packages/next/client/page-loader.ts +++ b/packages/next/client/page-loader.ts @@ -56,12 +56,6 @@ const relPreloadStyle = 'fetch' const hasNoModule = 'noModule' in document.createElement('script') -const requestIdleCallback: (fn: () => void) => void = - (window as any).requestIdleCallback || - function (cb: () => void) { - return setTimeout(cb, 1) - } - function normalizeRoute(route: string) { if (route[0] !== '/') { throw new Error(`Route name should start with a "/", got "${route}"`) @@ -281,20 +275,19 @@ export default class PageLoader { const { pathname: hrefPathname } = parseRelativeUrl(href) const route = normalizeRoute(hrefPathname) return this.promisedSsgManifest!.then( - (s: ClientSsgManifest, _dataHref?: string) => { - requestIdleCallback(() => { - // Check if the route requires a data file - s.has(route) && - // Try to generate data href, noop when falsy - (_dataHref = this.getDataHref(href, asPath, true)) && - // noop when data has already been prefetched (dedupe) - !document.querySelector( - `link[rel="${relPrefetch}"][href^="${_dataHref}"]` - ) && - // Inject the `` tag for above computed `href`. - appendLink(_dataHref, relPrefetch, 'fetch') + (s: ClientSsgManifest, _dataHref?: string) => + // Check if the route requires a data file + s.has(route) && + // Try to generate data href, noop when falsy + (_dataHref = this.getDataHref(href, asPath, true)) && + // noop when data has already been prefetched (dedupe) + !document.querySelector( + `link[rel="${relPrefetch}"][href^="${_dataHref}"]` + ) && + // Inject the `` tag for above computed `href`. + appendLink(_dataHref, relPrefetch, 'fetch').catch(() => { + /* ignore prefetch error */ }) - } ) } From 9c11d2bd45fa2e4d39c854acd8126062353e4896 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Tue, 1 Sep 2020 14:10:34 -0400 Subject: [PATCH 06/15] v9.5.3-canary.27 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-google-analytics/package.json | 2 +- packages/next-plugin-sentry/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next/package.json | 8 ++++---- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lerna.json b/lerna.json index c130272212331fd..10364d5f8230e3f 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "9.5.3-canary.26" + "version": "9.5.3-canary.27" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index c0e0e1280919da6..234423bd97d905a 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "keywords": [ "react", "next", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 6862515220ef465..82e46d09d953fcb 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": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "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 557ef63e4d1b54a..26a8fd88a8840c3 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 6b028f8cb664a0a..d00568c0008432c 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index e328db0604d626c..9e5f1aa80bf6ab2 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-google-analytics/package.json b/packages/next-plugin-google-analytics/package.json index 9453b0eb86bb700..bb7555b01fad936 100644 --- a/packages/next-plugin-google-analytics/package.json +++ b/packages/next-plugin-google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-google-analytics", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-google-analytics" diff --git a/packages/next-plugin-sentry/package.json b/packages/next-plugin-sentry/package.json index 391ed947a062811..c02ff8f6c46e341 100644 --- a/packages/next-plugin-sentry/package.json +++ b/packages/next-plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-sentry", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-sentry" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 85c801b022028e1..c81e4201aa495d2 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 758186f437f9028..4f3bb48a0a6e194 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/package.json b/packages/next/package.json index f8e8799b66b2b13..33d307063c4e187 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -77,8 +77,8 @@ "@babel/preset-typescript": "7.10.4", "@babel/runtime": "7.11.2", "@babel/types": "7.11.5", - "@next/react-dev-overlay": "9.5.3-canary.26", - "@next/react-refresh-utils": "9.5.3-canary.26", + "@next/react-dev-overlay": "9.5.3-canary.27", + "@next/react-refresh-utils": "9.5.3-canary.27", "ast-types": "0.13.2", "babel-plugin-syntax-jsx": "6.18.0", "babel-plugin-transform-define": "2.0.0", @@ -123,7 +123,7 @@ "react-dom": "^16.6.0" }, "devDependencies": { - "@next/polyfill-nomodule": "9.5.3-canary.26", + "@next/polyfill-nomodule": "9.5.3-canary.27", "@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 984ac3610a65af7..159da4ac3279a11 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": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "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 cb6ad0075e6ca25..f649c71b32cc09f 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": "9.5.3-canary.26", + "version": "9.5.3-canary.27", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From d7234713c3f527dae2d3d5ca3968c5441626fa13 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Tue, 1 Sep 2020 16:21:49 -0400 Subject: [PATCH 07/15] v9.5.3 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-google-analytics/package.json | 2 +- packages/next-plugin-sentry/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next/package.json | 8 ++++---- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lerna.json b/lerna.json index 10364d5f8230e3f..ffddc3945ea8d46 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "9.5.3-canary.27" + "version": "9.5.3" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 234423bd97d905a..63fd686b48c05f4 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "9.5.3-canary.27", + "version": "9.5.3", "keywords": [ "react", "next", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 82e46d09d953fcb..48ac721d7ba599e 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": "9.5.3-canary.27", + "version": "9.5.3", "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 26a8fd88a8840c3..a1e18076b5e7de7 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "9.5.3-canary.27", + "version": "9.5.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index d00568c0008432c..a3b67156eb1dc15 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "9.5.3-canary.27", + "version": "9.5.3", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 9e5f1aa80bf6ab2..71768b185213b8b 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "9.5.3-canary.27", + "version": "9.5.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-google-analytics/package.json b/packages/next-plugin-google-analytics/package.json index bb7555b01fad936..cef3e0d0eae72d8 100644 --- a/packages/next-plugin-google-analytics/package.json +++ b/packages/next-plugin-google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-google-analytics", - "version": "9.5.3-canary.27", + "version": "9.5.3", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-google-analytics" diff --git a/packages/next-plugin-sentry/package.json b/packages/next-plugin-sentry/package.json index c02ff8f6c46e341..0c0b1c5d7ec0e73 100644 --- a/packages/next-plugin-sentry/package.json +++ b/packages/next-plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-sentry", - "version": "9.5.3-canary.27", + "version": "9.5.3", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-sentry" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index c81e4201aa495d2..8f0f366aafe6ebc 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "9.5.3-canary.27", + "version": "9.5.3", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 4f3bb48a0a6e194..3db115869447fc6 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "9.5.3-canary.27", + "version": "9.5.3", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/package.json b/packages/next/package.json index 33d307063c4e187..d354d92e4de5490 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "9.5.3-canary.27", + "version": "9.5.3", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -77,8 +77,8 @@ "@babel/preset-typescript": "7.10.4", "@babel/runtime": "7.11.2", "@babel/types": "7.11.5", - "@next/react-dev-overlay": "9.5.3-canary.27", - "@next/react-refresh-utils": "9.5.3-canary.27", + "@next/react-dev-overlay": "9.5.3", + "@next/react-refresh-utils": "9.5.3", "ast-types": "0.13.2", "babel-plugin-syntax-jsx": "6.18.0", "babel-plugin-transform-define": "2.0.0", @@ -123,7 +123,7 @@ "react-dom": "^16.6.0" }, "devDependencies": { - "@next/polyfill-nomodule": "9.5.3-canary.27", + "@next/polyfill-nomodule": "9.5.3", "@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 159da4ac3279a11..213c13e07472f17 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": "9.5.3-canary.27", + "version": "9.5.3", "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 f649c71b32cc09f..3c7a75806069ad5 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": "9.5.3-canary.27", + "version": "9.5.3", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From 936d3f388a2a4b24937277dac8d4018cd71f391c Mon Sep 17 00:00:00 2001 From: Yichi Zhang Date: Tue, 1 Sep 2020 19:27:57 -0400 Subject: [PATCH 08/15] Add Fast Refresh Demo (#16576) Closes #16538 Basically reverts #16497 and some minor changes. Also adds a link in the docs. This reverts commit ec281df70b4d314d41b7d3bc7d0a8ddd42444d7a. --- docs/basic-features/fast-refresh.md | 7 ++ examples/fast-refresh-demo/.gitignore | 34 +++++++++ examples/fast-refresh-demo/README.md | 23 ++++++ .../fast-refresh-demo/components/Button.js | 5 ++ .../components/Button.module.css | 32 +++++++++ .../components/ClickCount.js | 11 +++ examples/fast-refresh-demo/global.css | 14 ++++ examples/fast-refresh-demo/package.json | 15 ++++ examples/fast-refresh-demo/pages/_app.js | 5 ++ examples/fast-refresh-demo/pages/index.js | 71 +++++++++++++++++++ .../fast-refresh-demo/styles/home.module.css | 11 +++ 11 files changed, 228 insertions(+) create mode 100644 examples/fast-refresh-demo/.gitignore create mode 100644 examples/fast-refresh-demo/README.md create mode 100644 examples/fast-refresh-demo/components/Button.js create mode 100644 examples/fast-refresh-demo/components/Button.module.css create mode 100644 examples/fast-refresh-demo/components/ClickCount.js create mode 100644 examples/fast-refresh-demo/global.css create mode 100644 examples/fast-refresh-demo/package.json create mode 100644 examples/fast-refresh-demo/pages/_app.js create mode 100644 examples/fast-refresh-demo/pages/index.js create mode 100644 examples/fast-refresh-demo/styles/home.module.css diff --git a/docs/basic-features/fast-refresh.md b/docs/basic-features/fast-refresh.md index 5df2d35968a98e6..450b19e6db4b3ed 100644 --- a/docs/basic-features/fast-refresh.md +++ b/docs/basic-features/fast-refresh.md @@ -6,6 +6,13 @@ description: # Fast Refresh +
+ Examples + +
+ Fast Refresh is a Next.js feature that gives you instantaneous feedback on edits made to your React components. Fast Refresh is enabled by default in all Next.js applications on **9.4 or newer**. With Next.js Fast Refresh enabled, diff --git a/examples/fast-refresh-demo/.gitignore b/examples/fast-refresh-demo/.gitignore new file mode 100644 index 000000000000000..1437c53f70bc211 --- /dev/null +++ b/examples/fast-refresh-demo/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/examples/fast-refresh-demo/README.md b/examples/fast-refresh-demo/README.md new file mode 100644 index 000000000000000..bde2b03db474b70 --- /dev/null +++ b/examples/fast-refresh-demo/README.md @@ -0,0 +1,23 @@ +# Fast Refresh Demo + +Next.js ships with [Fast Refresh](https://nextjs.org/docs/basic-features/fast-refresh) which gives you instantaneous feedback on edits made to your React components. + +This demos shows how the state of an auto incrementing value and a classic counter is preserved after edits or if there are errors. + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/fast-refresh-demo) + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example fast-refresh-demo fast-refresh-demo +# or +yarn create next-app --example fast-refresh-demo fast-refresh-demo +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/fast-refresh-demo/components/Button.js b/examples/fast-refresh-demo/components/Button.js new file mode 100644 index 000000000000000..fdb433551f56072 --- /dev/null +++ b/examples/fast-refresh-demo/components/Button.js @@ -0,0 +1,5 @@ +import styles from './Button.module.css' + +export default function Button(props) { + return +} diff --git a/examples/fast-refresh-demo/global.css b/examples/fast-refresh-demo/global.css new file mode 100644 index 000000000000000..e203447ed2412ff --- /dev/null +++ b/examples/fast-refresh-demo/global.css @@ -0,0 +1,14 @@ +body { + font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica', + 'Arial', sans-serif; + margin: 0 auto; + font-size: 16px; + line-height: 1.65; + word-break: break-word; + font-kerning: auto; + font-variant: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + hyphens: auto; +} diff --git a/examples/fast-refresh-demo/package.json b/examples/fast-refresh-demo/package.json new file mode 100644 index 000000000000000..50fb57f61e5d698 --- /dev/null +++ b/examples/fast-refresh-demo/package.json @@ -0,0 +1,15 @@ +{ + "name": "fast-refresh-demo", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^16.13.1", + "react-dom": "^16.13.1" + }, + "license": "MIT" +} diff --git a/examples/fast-refresh-demo/pages/_app.js b/examples/fast-refresh-demo/pages/_app.js new file mode 100644 index 000000000000000..d305f97bab7f142 --- /dev/null +++ b/examples/fast-refresh-demo/pages/_app.js @@ -0,0 +1,5 @@ +import '../global.css' + +export default function MyApp({ Component, pageProps }) { + return +} diff --git a/examples/fast-refresh-demo/pages/index.js b/examples/fast-refresh-demo/pages/index.js new file mode 100644 index 000000000000000..588c89a8e612c63 --- /dev/null +++ b/examples/fast-refresh-demo/pages/index.js @@ -0,0 +1,71 @@ +import { useCallback, useEffect, useState } from 'react' +import Button from '../components/Button' +import ClickCount from '../components/ClickCount' +import styles from '../styles/home.module.css' + +function throwError() { + console.log( + // The function body() is not defined + document.body() + ) +} + +function Home() { + const [count, setCount] = useState(0) + const increment = useCallback(() => { + setCount((v) => v + 1) + }, [setCount]) + + useEffect(() => { + const r = setInterval(() => { + increment() + }, 1000) + + return () => { + clearInterval(r) + } + }, [increment]) + + return ( +
+

Fast Refresh Demo

+

+ Fast Refresh is a Next.js feature that gives you instantaneous feedback + on edits made to your React components, without ever losing component + state. +

+
+
+

+ Auto incrementing value. The counter won't reset after edits or if + there are errors. +

+

Current value: {count}

+
+
+
+

Component with state.

+ +
+
+
+

+ The button below will throw 2 errors. You'll see the error overlay to + let you know about the errors but it won't break the page or reset + your state. +

+ +
+
+
+ ) +} + +export default Home diff --git a/examples/fast-refresh-demo/styles/home.module.css b/examples/fast-refresh-demo/styles/home.module.css new file mode 100644 index 000000000000000..693115201a064af --- /dev/null +++ b/examples/fast-refresh-demo/styles/home.module.css @@ -0,0 +1,11 @@ +.main { + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} + +.hr { + border: none; + border-bottom: 1px solid #efefef; + margin: 3em auto; +} From 5c8355e2f4a443b7c53daaff7be20a64cb439560 Mon Sep 17 00:00:00 2001 From: WeichienHung Date: Wed, 2 Sep 2020 07:48:56 +0800 Subject: [PATCH 09/15] force persistor persist again after persistStore bootstrap done (#16085) This PR is to fix "[Examples] Problem with query parameters in with-redux-persist (#15484)" The root cause is persist/rehydrate action will issue twice when query parameter is set. But persistStore initial bootstrap is not ready yet. So i add a bootstrap callback and force persistor to persist again to make overall state correct. I also modify the loading prop to a `
loading
` because it's confuse to set Component in loading prop. Attached the GIF ![demo](https://user-images.githubusercontent.com/1462027/89922530-bec04000-dc31-11ea-9831-12cd9d436d96.gif) Closes #15484 --- examples/with-redux-persist/pages/_app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/with-redux-persist/pages/_app.js b/examples/with-redux-persist/pages/_app.js index a48f78ae7bd357b..99bea7d750cf810 100644 --- a/examples/with-redux-persist/pages/_app.js +++ b/examples/with-redux-persist/pages/_app.js @@ -5,11 +5,13 @@ import { PersistGate } from 'redux-persist/integration/react' export default function App({ Component, pageProps }) { const store = useStore(pageProps.initialReduxState) - const persistor = persistStore(store) + const persistor = persistStore(store, {}, function () { + persistor.persist() + }) return ( - } persistor={persistor}> + loading
} persistor={persistor}> From f7c40c7be5bd426a93bddc8ba1ae1580df0a6a6b Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Tue, 1 Sep 2020 19:42:20 -0700 Subject: [PATCH 10/15] Make the image post-processor ignore SVG images (#16732) This is a small change to the image post-processor logic. When it's looking for images to preload, it will now ignore SVGs, as these are rarely the relevant images for LCP. --- packages/next/next-server/lib/post-process.ts | 9 ++++++++- test/integration/image-optimization/pages/index.js | 1 + test/integration/image-optimization/pages/stars.js | 1 + test/integration/image-optimization/test/index.test.js | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/next/next-server/lib/post-process.ts b/packages/next/next-server/lib/post-process.ts index d922a56bd174c18..fcbefe997a2d775 100644 --- a/packages/next/next-server/lib/post-process.ts +++ b/packages/next/next-server/lib/post-process.ts @@ -191,8 +191,10 @@ class ImageOptimizerMiddleware implements PostProcessMiddleware { } function isImgEligible(imgElement: HTMLElement): boolean { + let imgSrc = imgElement.getAttribute('src') return ( - imgElement.hasAttribute('src') && + !!imgSrc && + sourceIsSupportedType(imgSrc) && imageIsNotTooSmall(imgElement) && imageIsNotHidden(imgElement) ) @@ -243,6 +245,11 @@ function imageIsNotHidden(imgElement: HTMLElement): boolean { return true } +// Currently only filters out svg images--could be made more specific in the future. +function sourceIsSupportedType(imgSrc: string): boolean { + return !imgSrc.includes('.svg') +} + // Initialization registerPostProcessor( 'Inline-Fonts', diff --git a/test/integration/image-optimization/pages/index.js b/test/integration/image-optimization/pages/index.js index 09dec13363566a1..abbbb7dbbad2b48 100644 --- a/test/integration/image-optimization/pages/index.js +++ b/test/integration/image-optimization/pages/index.js @@ -6,6 +6,7 @@ const Page = () => { +