From 9081cf870b14844da189007287a4de7ee2932fd8 Mon Sep 17 00:00:00 2001
From: Kumar Deepanshu <62591080+kumard3@users.noreply.github.com>
Date: Mon, 7 Feb 2022 03:01:14 +0530
Subject: [PATCH 1/7] progressive web app example converted to typescript
(#33100)
## Bug
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`
## Feature
- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
## Documentation / Examples
- [ ] Make sure the linting passes by running `yarn lint`
Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
---
examples/progressive-web-app/next-env.d.ts | 5 +++++
examples/progressive-web-app/next.config.js | 1 +
examples/progressive-web-app/package.json | 5 +++++
.../pages/{_app.js => _app.tsx} | 3 ++-
.../pages/api/{hello.js => hello.ts} | 3 ++-
.../pages/{index.js => index.tsx} | 0
examples/progressive-web-app/tsconfig.json | 20 +++++++++++++++++++
7 files changed, 35 insertions(+), 2 deletions(-)
create mode 100644 examples/progressive-web-app/next-env.d.ts
rename examples/progressive-web-app/pages/{_app.js => _app.tsx} (90%)
rename examples/progressive-web-app/pages/api/{hello.js => hello.ts} (55%)
rename examples/progressive-web-app/pages/{index.js => index.tsx} (100%)
create mode 100644 examples/progressive-web-app/tsconfig.json
diff --git a/examples/progressive-web-app/next-env.d.ts b/examples/progressive-web-app/next-env.d.ts
new file mode 100644
index 0000000000000..4f11a03dc6cc3
--- /dev/null
+++ b/examples/progressive-web-app/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/examples/progressive-web-app/next.config.js b/examples/progressive-web-app/next.config.js
index 778ea93311dec..a62a930ff3634 100644
--- a/examples/progressive-web-app/next.config.js
+++ b/examples/progressive-web-app/next.config.js
@@ -1,3 +1,4 @@
+/** @type {import('next').NextConfig} */
const withPWA = require('next-pwa')
const runtimeCaching = require('next-pwa/cache')
diff --git a/examples/progressive-web-app/package.json b/examples/progressive-web-app/package.json
index c16f55ccc4985..1c8039c1130c0 100644
--- a/examples/progressive-web-app/package.json
+++ b/examples/progressive-web-app/package.json
@@ -10,5 +10,10 @@
"next-pwa": "^5.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "@types/node": "17.0.4",
+ "@types/react": "17.0.38",
+ "typescript": "4.5.4"
}
}
diff --git a/examples/progressive-web-app/pages/_app.js b/examples/progressive-web-app/pages/_app.tsx
similarity index 90%
rename from examples/progressive-web-app/pages/_app.js
rename to examples/progressive-web-app/pages/_app.tsx
index cca47aae9e44a..3601ddfa987de 100644
--- a/examples/progressive-web-app/pages/_app.js
+++ b/examples/progressive-web-app/pages/_app.tsx
@@ -1,7 +1,8 @@
import Head from 'next/head'
import '../styles/globals.css'
+import { AppProps } from 'next/app'
-export default function MyApp({ Component, pageProps }) {
+export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
diff --git a/examples/progressive-web-app/pages/api/hello.js b/examples/progressive-web-app/pages/api/hello.ts
similarity index 55%
rename from examples/progressive-web-app/pages/api/hello.js
rename to examples/progressive-web-app/pages/api/hello.ts
index 1424b47343d64..a42f4634c2b13 100644
--- a/examples/progressive-web-app/pages/api/hello.js
+++ b/examples/progressive-web-app/pages/api/hello.ts
@@ -1,6 +1,7 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
+import { NextApiRequest, NextApiResponse } from 'next'
-const hello = (req, res) => {
+const hello = (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).json({ name: 'John Doe' })
}
diff --git a/examples/progressive-web-app/pages/index.js b/examples/progressive-web-app/pages/index.tsx
similarity index 100%
rename from examples/progressive-web-app/pages/index.js
rename to examples/progressive-web-app/pages/index.tsx
diff --git a/examples/progressive-web-app/tsconfig.json b/examples/progressive-web-app/tsconfig.json
new file mode 100644
index 0000000000000..99710e857874f
--- /dev/null
+++ b/examples/progressive-web-app/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
From 8eb6ddc34303a679bcea049654576ad1d63a7332 Mon Sep 17 00:00:00 2001
From: Jess Telford
Date: Mon, 7 Feb 2022 08:44:19 +1100
Subject: [PATCH 2/7] [docs] Add env var load order (#32350)
My team & I keep asking the question "What order are env vars _actually_ loaded in?".
This addition surfaces the order in a clear and readable way without having to read and understand the entire "Environment Variables" documentation first.
## Documentation / Examples
- [x] Make sure the linting passes by running `yarn lint`
Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
---
docs/basic-features/environment-variables.md | 26 ++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/docs/basic-features/environment-variables.md b/docs/basic-features/environment-variables.md
index 91182cbb2f3f9..3b589e4b75fa3 100644
--- a/docs/basic-features/environment-variables.md
+++ b/docs/basic-features/environment-variables.md
@@ -149,3 +149,29 @@ export default async () => {
loadEnvConfig(projectDir)
}
```
+
+## Environment Variable Load Order
+
+Depending on the environment (as set by `NODE_ENV`), variables are loaded from the following sources in order from top-to-bottom. In all environments existing env is not overridden by following sources.
+
+`NODE_ENV=production`
+
+- `.env.production.local`
+- `.env.local`
+- `.env.production`
+- `.env`
+
+`NODE_ENV=development`
+
+- `.env.development.local`
+- `.env.local`
+- `.env.development`
+- `.env`
+
+`NODE_ENV=test`
+
+- `.env.test.local`
+- `.env.test`
+- `.env`
+
+_(note: `.env.local` is not loaded when `NODE_ENV=test`)_
From 6d0bd20ffa6ab628247bf419fa5fd408bc658ea4 Mon Sep 17 00:00:00 2001
From: Neeraj Rajpurohit <31539812+neeraj3029@users.noreply.github.com>
Date: Mon, 7 Feb 2022 04:08:42 +0530
Subject: [PATCH 3/7] Fixes #31240: Adding a recursive addPackagePath function
in webpack-config (#31264)
Fixes: https://github.com/vercel/next.js/issues/31240
Closes: https://github.com/vercel/next.js/pull/32324
Adding a try-catch block to handle situations when packages are found at relative path in getPackagePath function. This is likely to occur when using `preact` instead of `react-dom`, as `scheduler` package will not be found wrt `react-dom`
## Bug
- [x] Related issues linked using `fixes #31240`
Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
---
packages/next/build/webpack-config.ts | 64 ++++++++++++++++-----------
1 file changed, 38 insertions(+), 26 deletions(-)
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index 190d531b745ad..6a16a2cf65f3b 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -685,35 +685,47 @@ export default async function getBaseWebpackConfig(
)
}
- const getPackagePath = (name: string, relativeToPath: string) => {
- const packageJsonPath = require.resolve(`${name}/package.json`, {
- paths: [relativeToPath],
- })
- // Include a trailing slash so that a `.startsWith(packagePath)` check avoids false positives
- // when one package name starts with the full name of a different package.
- // For example:
- // "node_modules/react-slider".startsWith("node_modules/react") // true
- // "node_modules/react-slider".startsWith("node_modules/react/") // false
- return path.join(packageJsonPath, '../')
- }
-
// Packages which will be split into the 'framework' chunk.
// Only top-level packages are included, e.g. nested copies like
// 'node_modules/meow/node_modules/object-assign' are not included.
- const topLevelFrameworkPaths = [
- getPackagePath('react', dir),
- getPackagePath('react-dom', dir),
- getPackagePath('scheduler', require.resolve('react-dom', { paths: [dir] })),
- getPackagePath('object-assign', require.resolve('react', { paths: [dir] })),
- getPackagePath(
- 'object-assign',
- require.resolve('react-dom', { paths: [dir] })
- ),
- getPackagePath(
- 'use-subscription',
- require.resolve('next', { paths: [dir] })
- ),
- ]
+ const topLevelFrameworkPaths: string[] = []
+ const visitedFrameworkPackages = new Set()
+
+ // Adds package-paths of dependencies recursively
+ const addPackagePath = (packageName: string, relativeToPath: string) => {
+ try {
+ if (visitedFrameworkPackages.has(packageName)) {
+ return
+ }
+ visitedFrameworkPackages.add(packageName)
+
+ const packageJsonPath = require.resolve(`${packageName}/package.json`, {
+ paths: [relativeToPath],
+ })
+
+ // Include a trailing slash so that a `.startsWith(packagePath)` check avoids false positives
+ // when one package name starts with the full name of a different package.
+ // For example:
+ // "node_modules/react-slider".startsWith("node_modules/react") // true
+ // "node_modules/react-slider".startsWith("node_modules/react/") // false
+ const directory = path.join(packageJsonPath, '../')
+
+ // Returning from the function in case the directory has already been added and traversed
+ if (topLevelFrameworkPaths.includes(directory)) return
+ topLevelFrameworkPaths.push(directory)
+
+ const dependencies = require(packageJsonPath).dependencies || {}
+ for (const name of Object.keys(dependencies)) {
+ addPackagePath(name, directory)
+ }
+ } catch (_) {
+ // don't error on failing to resolve framework packages
+ }
+ }
+
+ for (const packageName of ['react', 'react-dom']) {
+ addPackagePath(packageName, dir)
+ }
// Select appropriate SplitChunksPlugin config for this build
const splitChunksConfig: webpack.Options.SplitChunksOptions | false = dev
From 91146b23a21e33d54332a469f30afe6e6156cd65 Mon Sep 17 00:00:00 2001
From: Glenn Gijsberts
Date: Sun, 6 Feb 2022 23:50:55 +0100
Subject: [PATCH 4/7] Make adjustment to cache config of with-apollo example
(#32733)
## Description
This year we implemented the new Apollo config using this example. We recently moved to `getServerSideProps` as well, however, this was giving us some cache problems. The issue was that the cache was older than the actual data that was coming from the server side query.
Because the `merge` of the cache in `apolloClient.js` is merging the existingCache in the initialState it will overwrite the "fresh" initialState with properties that already exists. This means that if you have something in your cache from previous client render, for example `user` with the value `null`, and you go to a new page and do a new query on the server which returns a value for the `user` field, it will still return `null` because of that `merge` function.
Since this was weird in our opinion, we've changed this in our own environment by always overwriting the existing cache with the new initialState, so that the initialState that is coming from a fresh server side query is overwriting. We thought it was a good idea to reflect this also in this example, because we couldn't think of a reason why the existing cache should overwrite fresh queries.
I've added a reproduction that shows what this is exactly about. I hope my description makes sense, let me know what you think!
## Reproduction of old scenario
Created a reproduction branch here: https://github.com/glenngijsberts/with-apollo-example. Using a different API than in the example, because of https://github.com/vercel/next.js/issues/32731.
1. checkout the example
2. yarn
3. yarn dev
4. visit http://localhost:3000/client-only
5. Hit "Update name". This will run a mutation that will update the cache automatically. Because I use a mocked API, the actual value on the API won't be updated, but this is actually the perfect scenario for the problem because in reality data can already change in the meantime when you're doing a new request.
6. Go to the SSR page
7. This will display two values: one is coming from the server side request (which is the latest data, because no cache is used in `getServerSideProps`) and the other is using the cache, which is outdated at that point, yet it's still returned because the old way of merging the cache was picking the existing cache over the initialState that was coming from the fresh server side query.
## Documentation / Examples
- [x] Make sure the linting passes by running `yarn lint`
---
examples/with-apollo/lib/apolloClient.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/with-apollo/lib/apolloClient.js b/examples/with-apollo/lib/apolloClient.js
index 554bcea87ded5..b6aa661ce2079 100644
--- a/examples/with-apollo/lib/apolloClient.js
+++ b/examples/with-apollo/lib/apolloClient.js
@@ -36,8 +36,8 @@ export function initializeApollo(initialState = null) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
- // Merge the existing cache into data passed from getStaticProps/getServerSideProps
- const data = merge(initialState, existingCache, {
+ // Merge the initialState from getStaticProps/getServerSideProps in the existing cache
+ const data = merge(existingCache, initialState, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
From 41706d6fd3ae61d087997abec5012014b936753d Mon Sep 17 00:00:00 2001
From: Adam Pietrasiak
Date: Mon, 7 Feb 2022 01:04:37 +0100
Subject: [PATCH 5/7] Require component rendered as child of `Link` to pass
event to `onClick` handler (#27723)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently, if you render `next/link` like
```ts
```
and have
```tsx
interface Props {
onClick?: () => void; // <— note we're not passing event as an argument
}
function CustomComponent({ onClick }: Props) {
return
onClick?.()}>Hello
}
```
It'll result in error
```
link.js?f421:21 Uncaught TypeError: Cannot read property 'defaultPrevented' of undefined
```
## Bug
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`
## Feature
- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
## Documentation / Examples
- [ ] Make sure the linting passes
Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
---
packages/next/client/link.tsx | 7 ++++
.../pages/link-invalid-onclick.js | 35 +++++++++++++++++++
.../client-navigation/test/index.test.js | 7 ++++
3 files changed, 49 insertions(+)
create mode 100644 test/integration/client-navigation/pages/link-invalid-onclick.js
diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx
index 2aba7f79139d8..9a7f65b52997b 100644
--- a/packages/next/client/link.tsx
+++ b/packages/next/client/link.tsx
@@ -284,6 +284,13 @@ function Link(props: React.PropsWithChildren) {
} = {
ref: setRef,
onClick: (e: React.MouseEvent) => {
+ if (process.env.NODE_ENV !== 'production') {
+ if (!e) {
+ throw new Error(
+ `Component rendered inside next/link has to pass click event to "onClick" prop.`
+ )
+ }
+ }
if (child.props && typeof child.props.onClick === 'function') {
child.props.onClick(e)
}
diff --git a/test/integration/client-navigation/pages/link-invalid-onclick.js b/test/integration/client-navigation/pages/link-invalid-onclick.js
new file mode 100644
index 0000000000000..7cebe86d2895d
--- /dev/null
+++ b/test/integration/client-navigation/pages/link-invalid-onclick.js
@@ -0,0 +1,35 @@
+import Link from 'next/link'
+import { useState } from 'react'
+
+export default function Page(props) {
+ const [errorCount, setErrorCount] = useState(0)
+
+ function Button(props) {
+ return (
+ {
+ e.preventDefault()
+ try {
+ props.onClick()
+ } catch (err) {
+ setErrorCount(errorCount + 1)
+ console.error(err)
+ }
+ }}
+ >
+ {props.href}
+
+ )
+ }
+
+ return (
+ <>
+
{errorCount}
+
+
+
+ >
+ )
+}
diff --git a/test/integration/client-navigation/test/index.test.js b/test/integration/client-navigation/test/index.test.js
index b4b711702e023..4a6921dfdd446 100644
--- a/test/integration/client-navigation/test/index.test.js
+++ b/test/integration/client-navigation/test/index.test.js
@@ -87,6 +87,13 @@ describe('Client Navigation', () => {
expect(text).toMatch(/this is the about page/i)
})
+ it('should error when calling onClick without event', async () => {
+ const browser = await webdriver(context.appPort, '/link-invalid-onclick')
+ expect(await browser.elementByCss('#errors').text()).toBe('0')
+ await browser.elementByCss('#custom-button').click()
+ expect(await browser.elementByCss('#errors').text()).toBe('1')
+ })
+
it('should navigate via the client side', async () => {
const browser = await webdriver(context.appPort, '/nav')
From b49717f8dbf530f2fe8a2af8c8c3f7a3ae1209d5 Mon Sep 17 00:00:00 2001
From: Wouter Raateland
Date: Mon, 7 Feb 2022 01:38:55 +0100
Subject: [PATCH 6/7] Allow scroll prevention on hash change (#31921)
* Allow scroll prevention on hash change
Currently, `scrollToHash` is performed on every hash change, even when this change is caused by ``.
This change prevents scrolling in this case and allows users to specify the desired scrolling behavior in the router's `hashChangeComplete` event.
* Add test case and apply fixes
Co-authored-by: JJ Kasper
---
packages/next/client/link.tsx | 5 -----
packages/next/shared/lib/router/router.ts | 11 ++++++++---
.../client-navigation/pages/nav/hash-changes.js | 5 +++++
test/integration/client-navigation/test/index.test.js | 11 +++++++++++
4 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx
index 9a7f65b52997b..9bb634d251adc 100644
--- a/packages/next/client/link.tsx
+++ b/packages/next/client/link.tsx
@@ -96,11 +96,6 @@ function linkClicked(
e.preventDefault()
- // avoid scroll for urls with anchor refs
- if (scroll == null && as.indexOf('#') >= 0) {
- scroll = false
- }
-
// replace state instead of push if prop is present
router[replace ? 'replace' : 'push'](href, as, {
shallow,
diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts
index 9a41567ba0bfa..ed80980691fd1 100644
--- a/packages/next/shared/lib/router/router.ts
+++ b/packages/next/shared/lib/router/router.ts
@@ -1009,7 +1009,7 @@ export default class Router implements BaseRouter {
performance.mark('routeChange')
}
- const { shallow = false } = options
+ const { shallow = false, scroll = true } = options
const routeProps = { shallow }
if (this._inFlightRoute) {
@@ -1045,8 +1045,13 @@ export default class Router implements BaseRouter {
this.asPath = cleanedAs
Router.events.emit('hashChangeStart', as, routeProps)
// TODO: do we need the resolved href when only a hash change?
- this.changeState(method, url, as, options)
- this.scrollToHash(cleanedAs)
+ this.changeState(method, url, as, {
+ ...options,
+ scroll: false,
+ })
+ if (scroll) {
+ this.scrollToHash(cleanedAs)
+ }
this.notify(this.components[this.route], null)
Router.events.emit('hashChangeComplete', as, routeProps)
return true
diff --git a/test/integration/client-navigation/pages/nav/hash-changes.js b/test/integration/client-navigation/pages/nav/hash-changes.js
index e76f3a22d89fc..67c9a35cde53f 100644
--- a/test/integration/client-navigation/pages/nav/hash-changes.js
+++ b/test/integration/client-navigation/pages/nav/hash-changes.js
@@ -27,6 +27,11 @@ const HashChanges = ({ count }) => {
Go to name item 400
+
+
+ Go to name item 400 (no scroll)
+
+