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}

+ +