From 7f23954f7ef2051e1ca29c3c6751d795926038fa Mon Sep 17 00:00:00 2001 From: mongolyy Date: Tue, 5 Jan 2021 05:25:57 +0900 Subject: [PATCH 01/22] Update database config explanation in with-firebase-app example (#20709) I made the following changes. - Addition of procedure for setting private_key - Addition of procedure for creating a database and setting database_url --- examples/with-firebase-authentication/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/with-firebase-authentication/README.md b/examples/with-firebase-authentication/README.md index b5a1679a1944a..a121492805aca 100644 --- a/examples/with-firebase-authentication/README.md +++ b/examples/with-firebase-authentication/README.md @@ -18,8 +18,9 @@ Set up Firebase: - Create a project at the [Firebase console](https://console.firebase.google.com/). - Copy the contents of `.env.local.example` into a new file called `.env.local` -- Get your account credentials from the Firebase console at _Project settings > Service accounts_, where you can click on _Generate new private key_ and download the credentials as a json file. It will contain keys such as `project_id`, `client_email` and `client_id`. Set them as environment variables in the `.env.local` file at the root of this project. -- Get your authentication credentials from the Firebase console under _Project settings > General> Your apps_ Add a new web app if you don't already have one. Under _Firebase SDK snippet_ choose _Config_ to get the configuration as JSON. It will include keys like `apiKey`, `authDomain` and `databaseUrl`. Set the appropriate environment variables in the `.env.local` file at the root of this project. +- Get your account credentials from the Firebase console at _Project settings > Service accounts_, where you can click on _Generate new private key_ and download the credentials as a json file. It will contain keys such as `project_id`, `client_email` and `private_key`. Set them as environment variables in the `.env.local` file at the root of this project. +- Get your authentication credentials from the Firebase console under _Project settings > General> Your apps_ Add a new web app if you don't already have one. Under _Firebase SDK snippet_ choose _Config_ to get the configuration as JSON. It will include keys like `apiKey` and `authDomain`. Set the appropriate environment variables in the `.env.local` file at the root of this project. +- Go to **Develop**, click on **Realtime Database** and create a database if you don't already have one. Under _data_ get `databaseUrl`(e.g. `https://[dbname].firebaseio.com/`). Set the appropriate environment variables in the `.env.local` file at the root of this project. - Go to **Develop**, click on **Authentication** and in the **Sign-in method** tab enable authentication for the app. Install it and run: From 2a4887c30a5adb4faf764c6138a2d333df24c9fe Mon Sep 17 00:00:00 2001 From: Justin Philpott <8027086+justinphilpott@users.noreply.github.com> Date: Mon, 4 Jan 2021 20:46:34 +0000 Subject: [PATCH 02/22] New example: /with-storybook-styled-jsx-scss (#18570) Hi, I'm submitting this PR for consideration as a new example app showing Styled JSX with SCSS working inside and outside storybook with example components. Only known issue: I noticed that when running this example with: `$ yarn next ./examples/with-storybook-styled-jsx-scss` I receive the following error: ``` error - ./pages/_app.js Error: [BABEL] .../next.js/examples/with-storybook-styled-jsx-scss/pages/_app.js: Cannot find module 'styled-jsx-plugin-sass' (While processing: ".../next.js/node_modules/next/babel.js") ``` However I notice that this same missing module error is triggered when running this existing example app "with-styled-jsx-scss". Any changes/tweaks needed? Thanks! --- .../with-storybook-styled-jsx-scss/.babelrc | 12 + .../with-storybook-styled-jsx-scss/.gitignore | 37 +++ .../.storybook/main.js | 7 + .../.storybook/preview.js | 3 + .../with-storybook-styled-jsx-scss/README.md | 45 ++++ .../components/Button.tsx | 86 +++++++ .../components/Header.tsx | 89 +++++++ .../components/Page.tsx | 211 +++++++++++++++++ .../next-env.d.ts | 2 + .../package.json | 33 +++ .../pages/_app.tsx | 8 + .../pages/index.tsx | 35 +++ .../public/favicon.ico | Bin 0 -> 15086 bytes .../public/vercel.svg | 4 + .../stories/Button.stories.tsx | 38 +++ .../stories/Header.stories.tsx | 20 ++ .../stories/Introduction.stories.mdx | 219 ++++++++++++++++++ .../stories/Page.stories.tsx | 23 ++ .../stories/assets/code-brackets.svg | 1 + .../stories/assets/colors.svg | 1 + .../stories/assets/comments.svg | 1 + .../stories/assets/direction.svg | 1 + .../stories/assets/flow.svg | 1 + .../stories/assets/plugin.svg | 1 + .../stories/assets/repo.svg | 1 + .../stories/assets/stackalt.svg | 1 + .../styles/Home.module.css | 122 ++++++++++ .../styles/globals.css | 16 ++ .../tsconfig.json | 19 ++ examples/with-storybook/.gitignore | 3 + examples/with-storybook/README.md | 10 +- 31 files changed, 1048 insertions(+), 2 deletions(-) create mode 100644 examples/with-storybook-styled-jsx-scss/.babelrc create mode 100644 examples/with-storybook-styled-jsx-scss/.gitignore create mode 100644 examples/with-storybook-styled-jsx-scss/.storybook/main.js create mode 100644 examples/with-storybook-styled-jsx-scss/.storybook/preview.js create mode 100644 examples/with-storybook-styled-jsx-scss/README.md create mode 100644 examples/with-storybook-styled-jsx-scss/components/Button.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/components/Header.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/components/Page.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/next-env.d.ts create mode 100644 examples/with-storybook-styled-jsx-scss/package.json create mode 100644 examples/with-storybook-styled-jsx-scss/pages/_app.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/pages/index.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/public/favicon.ico create mode 100644 examples/with-storybook-styled-jsx-scss/public/vercel.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/Button.stories.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/stories/Header.stories.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/stories/Introduction.stories.mdx create mode 100644 examples/with-storybook-styled-jsx-scss/stories/Page.stories.tsx create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/code-brackets.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/colors.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/comments.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/direction.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/flow.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/plugin.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/repo.svg create mode 100644 examples/with-storybook-styled-jsx-scss/stories/assets/stackalt.svg create mode 100644 examples/with-storybook-styled-jsx-scss/styles/Home.module.css create mode 100644 examples/with-storybook-styled-jsx-scss/styles/globals.css create mode 100644 examples/with-storybook-styled-jsx-scss/tsconfig.json diff --git a/examples/with-storybook-styled-jsx-scss/.babelrc b/examples/with-storybook-styled-jsx-scss/.babelrc new file mode 100644 index 0000000000000..3eaec7a8b8557 --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "next/babel", + { + "styled-jsx": { + "plugins": ["styled-jsx-plugin-sass"] + } + } + ] + ] +} diff --git a/examples/with-storybook-styled-jsx-scss/.gitignore b/examples/with-storybook-styled-jsx-scss/.gitignore new file mode 100644 index 0000000000000..a423604f97788 --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/.gitignore @@ -0,0 +1,37 @@ +# 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 + +# Storybook +/storybook-static \ No newline at end of file diff --git a/examples/with-storybook-styled-jsx-scss/.storybook/main.js b/examples/with-storybook-styled-jsx-scss/.storybook/main.js new file mode 100644 index 0000000000000..f041eb8c96a17 --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/.storybook/main.js @@ -0,0 +1,7 @@ +module.exports = { + stories: [ + '../stories/**/*.stories.mdx', + '../stories/**/*.stories.@(js|jsx|ts|tsx)', + ], + addons: ['@storybook/addon-links', '@storybook/addon-essentials'], +} diff --git a/examples/with-storybook-styled-jsx-scss/.storybook/preview.js b/examples/with-storybook-styled-jsx-scss/.storybook/preview.js new file mode 100644 index 0000000000000..6dd87694930ad --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/.storybook/preview.js @@ -0,0 +1,3 @@ +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, +} diff --git a/examples/with-storybook-styled-jsx-scss/README.md b/examples/with-storybook-styled-jsx-scss/README.md new file mode 100644 index 0000000000000..c35210600ec7e --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/README.md @@ -0,0 +1,45 @@ +# Example app with Storybook setup for SCSS in Styled-jsx + +This example shows Styled-jsx (with SCSS) working for components written in TypeScript rendered both inside and outside of Storybook. + +## 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/with-storybook-styled-jsx-scss) + +## 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 with-storybook-styled-jsx-scss with-storybook-styled-jsx-scss-app +# or +yarn create next-app --example with-storybook-styled-jsx-scss with-storybook-styled-jsx-scss-app +``` + +### Run Storybook + +```bash +npm run storybook +# or +yarn storybook +``` + +### Build Static Storybook + +```bash +npm run build-storybook +# or +yarn build-storybook +``` + +You can use [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) to deploy Storybook. Specify `storybook-static` as the output directory. + +## Notes + +This example combines the following examples, with some required extra config added: + +- [with-storybook](https://github.com/vercel/next.js/tree/canary/examples/with-storybook) +- [with-styled-jsx-scss](https://github.com/vercel/next.js/tree/canary/examples/with-styled-jsx-scss) +- [with-typescript](https://github.com/vercel/next.js/tree/canary/examples/with-typescript) diff --git a/examples/with-storybook-styled-jsx-scss/components/Button.tsx b/examples/with-storybook-styled-jsx-scss/components/Button.tsx new file mode 100644 index 0000000000000..87c29a5084834 --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/components/Button.tsx @@ -0,0 +1,86 @@ +import React from 'react' + +export interface ButtonProps { + /** + * Is this the principal call to action on the page? + */ + primary?: boolean + /** + * What background color to use + */ + backgroundColor?: string + /** + * How large should the button be? + */ + size?: 'small' | 'medium' | 'large' + /** + * Button contents + */ + label: string + /** + * Optional click handler + */ + onClick?: () => void +} + +/** + * Primary UI component for user interaction + */ +export const Button: React.FC = ({ + primary = false, + size = 'medium', + backgroundColor, + label, + ...props +}) => { + const mode = primary + ? 'storybook-button--primary' + : 'storybook-button--secondary' + return ( + <> + + + + ) +} diff --git a/examples/with-storybook-styled-jsx-scss/components/Header.tsx b/examples/with-storybook-styled-jsx-scss/components/Header.tsx new file mode 100644 index 0000000000000..467d84061da7c --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/components/Header.tsx @@ -0,0 +1,89 @@ +import React from 'react' + +import { Button } from './Button' + +export interface HeaderProps { + user?: {} + onLogin: () => void + onLogout: () => void + onCreateAccount: () => void +} + +export const Header: React.FC = ({ + user, + onLogin, + onLogout, + onCreateAccount, +}) => ( + <> +
+
+
+ + + + + + + +

Acme

+
+
+ {user ? ( +
+
+
+ + +) diff --git a/examples/with-storybook-styled-jsx-scss/components/Page.tsx b/examples/with-storybook-styled-jsx-scss/components/Page.tsx new file mode 100644 index 0000000000000..d99229dc66ddb --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/components/Page.tsx @@ -0,0 +1,211 @@ +import React from 'react' +import Link from 'next/link' +import { Header } from './Header' + +export interface PageProps { + user?: {} + onLogin: () => void + onLogout: () => void + onCreateAccount: () => void +} + +export const Page: React.FC = ({ + user, + onLogin, + onLogout, + onCreateAccount, +}) => ( + <> +
+
+
+

Example app with Storybook setup for SCSS in Styled-jsx

+

+ This example shows Styled-jsx (with SCSS) working for components + rendered both inside and outside of Storybook. +

+

This example combines the following other examples:

+ +

+ Additionally, the Storybook demo components are moved into the + components directory and their css is refactored into component level + Styled JSX + SCSS. +

+

+ Story files live in their own directory and refer to the components + within /components. +

+

+ The /styles directory contains styles, both global and for the + index.js file. +

+

+ You might also want to check out the{' '} + + Styled JSX documentation + +

+

+ + Improvements welcome! + +

+
+
+

Pages in Storybook

+

+ (From the storybook demo setup) +

+

+ We recommend building UIs with a{' '} + + component-driven + {' '} + process starting with atomic components and ending with pages. +

+

+ Render pages with mock data. This makes it easy to build and review + page states without needing to navigate to them in your app. Here are + some handy patterns for managing page data in Storybook: +

+
    +
  • + Use a higher-level connected component. Storybook helps you compose + such data from the "args" of child component stories +
  • +
  • + Assemble data in the page component from your services. You can mock + these services out using Storybook. +
  • +
+

+ Get a guided tutorial on component-driven development at{' '} + + Learn Storybook + + . Read more in the{' '} + + docs + + . +

+
+ Tip Adjust the width of the canvas with + the{' '} + + + + + + Viewports addon in the toolbar (when viewed in Storybook) +
+
+
+ + +) diff --git a/examples/with-storybook-styled-jsx-scss/next-env.d.ts b/examples/with-storybook-styled-jsx-scss/next-env.d.ts new file mode 100644 index 0000000000000..7b7aa2c7727d8 --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/with-storybook-styled-jsx-scss/package.json b/examples/with-storybook-styled-jsx-scss/package.json new file mode 100644 index 0000000000000..0a85565d4b3ce --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/package.json @@ -0,0 +1,33 @@ +{ + "name": "with-storybook-styled-jsx-scss", + "version": "0.1.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "type-check": "tsc", + "storybook": "start-storybook -p 6006 --no-dll", + "build-storybook": "build-storybook --no-dll" + }, + "dependencies": { + "next": "^10.0.0", + "node-sass": "^4.14.1", + "react": "17.0.1", + "react-dom": "17.0.1", + "styled-jsx-plugin-sass": "^1.0.0" + }, + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.12.3", + "@storybook/addon-actions": "^6.1.11", + "@storybook/addon-essentials": "^6.1.11", + "@storybook/addon-links": "^6.1.11", + "@storybook/react": "^6.1.11", + "@types/node": "^14.14.2", + "@types/react": "^16.9.53", + "@types/react-dom": "^16.9.8", + "babel-loader": "^8.1.0", + "react-is": "^17.0.1", + "typescript": "^4.0.3" + } +} diff --git a/examples/with-storybook-styled-jsx-scss/pages/_app.tsx b/examples/with-storybook-styled-jsx-scss/pages/_app.tsx new file mode 100644 index 0000000000000..27cd5bfc2166f --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/pages/_app.tsx @@ -0,0 +1,8 @@ +import { AppProps } from 'next/app' +import '../styles/globals.css' + +function MyApp({ Component, pageProps }: AppProps) { + return +} + +export default MyApp diff --git a/examples/with-storybook-styled-jsx-scss/pages/index.tsx b/examples/with-storybook-styled-jsx-scss/pages/index.tsx new file mode 100644 index 0000000000000..cb3bcd9323e9b --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/pages/index.tsx @@ -0,0 +1,35 @@ +import Head from 'next/head' +import styles from '../styles/Home.module.css' +import { Page } from '../components/Page' + +const demoProps = { + user: {}, + onLogin: () => {}, + onLogout: () => {}, + onCreateAccount: () => {}, +} + +export default function Home() { + return ( +
+ + Create Next App + + + + {/* Including demo props here for example */} + + + +
+ ) +} diff --git a/examples/with-storybook-styled-jsx-scss/public/favicon.ico b/examples/with-storybook-styled-jsx-scss/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4965832f2c9b0605eaa189b7c7fb11124d24e48a GIT binary patch literal 15086 zcmeHOOH5Q(7(R0cc?bh2AT>N@1PWL!LLfZKyG5c!MTHoP7_p!sBz0k$?pjS;^lmgJ zU6^i~bWuZYHL)9$wuvEKm~qo~(5=Lvx5&Hv;?X#m}i|`yaGY4gX+&b>tew;gcnRQA1kp zBbm04SRuuE{Hn+&1wk%&g;?wja_Is#1gKoFlI7f`Gt}X*-nsMO30b_J@)EFNhzd1QM zdH&qFb9PVqQOx@clvc#KAu}^GrN`q5oP(8>m4UOcp`k&xwzkTio*p?kI4BPtIwX%B zJN69cGsm=x90<;Wmh-bs>43F}ro$}Of@8)4KHndLiR$nW?*{Rl72JPUqRr3ta6e#A z%DTEbi9N}+xPtd1juj8;(CJt3r9NOgb>KTuK|z7!JB_KsFW3(pBN4oh&M&}Nb$Ee2 z$-arA6a)CdsPj`M#1DS>fqj#KF%0q?w50GN4YbmMZIoF{e1yTR=4ablqXHBB2!`wM z1M1ke9+<);|AI;f=2^F1;G6Wfpql?1d5D4rMr?#f(=hkoH)U`6Gb)#xDLjoKjp)1;Js@2Iy5yk zMXUqj+gyk1i0yLjWS|3sM2-1ECc;MAz<4t0P53%7se$$+5Ex`L5TQO_MMXXi04UDIU+3*7Ez&X|mj9cFYBXqM{M;mw_ zpw>azP*qjMyNSD4hh)XZt$gqf8f?eRSFX8VQ4Y+H3jAtvyTrXr`qHAD6`m;aYmH2zOhJC~_*AuT} zvUxC38|JYN94i(05R)dVKgUQF$}#cxV7xZ4FULqFCNX*Forhgp*yr6;DsIk=ub0Hv zpk2L{9Q&|uI^b<6@i(Y+iSxeO_n**4nRLc`P!3ld5jL=nZRw6;DEJ*1z6Pvg+eW|$lnnjO zjd|8>6l{i~UxI244CGn2kK@cJ|#ecwgSyt&HKA2)z zrOO{op^o*- + + \ No newline at end of file diff --git a/examples/with-storybook-styled-jsx-scss/stories/Button.stories.tsx b/examples/with-storybook-styled-jsx-scss/stories/Button.stories.tsx new file mode 100644 index 0000000000000..3a6504b17d2cb --- /dev/null +++ b/examples/with-storybook-styled-jsx-scss/stories/Button.stories.tsx @@ -0,0 +1,38 @@ +import React from 'react' +// also exported from '@storybook/react' if you can deal with breaking changes in 6.1 +import { Story, Meta } from '@storybook/react/types-6-0' + +import { Button, ButtonProps } from '../components/Button' + +export default { + title: 'Example/Button', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as Meta + +const Template: Story = (args) => + +
+ Status Code: + {response?.status || 'None'} +
+
+ Request Limit: + {response?.limit || 'None'} +
+
+ Remaining Requests: + {response?.remaining || 'None'} +
+
+ Body: + {JSON.stringify(response?.body) || 'None'} +
+
+ + + ) +} diff --git a/examples/api-routes-rate-limit/styles.module.css b/examples/api-routes-rate-limit/styles.module.css new file mode 100644 index 0000000000000..f2cd0c79e7de7 --- /dev/null +++ b/examples/api-routes-rate-limit/styles.module.css @@ -0,0 +1,67 @@ +.container { + padding: 4rem 1rem; + max-width: 50rem; + margin: 0 auto; + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.container h1 { + font-weight: 800; +} + +.container p { + margin: 1.5rem 0; + line-height: 1.5; +} + +.container button { + border-radius: 4px; + height: 40px; + padding: 0.5rem 1rem; + font-size: 16px; + border: none; + transition: 0.25s all ease; + background-color: #eaeaea; + font-size: 14px; + font-weight: 600; + color: #111; +} + +.container button:hover { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12); +} + +.container a { + text-decoration: none; + color: #0070f3; +} + +.inlineCode { + color: #be00ff; + font-size: 16px; + white-space: pre-wrap; +} + +.inlineCode::before, +.inlineCode::after { + content: '`'; +} + +.code { + margin-top: 16px; + display: block; + background: #222222; + border-radius: 8px; + padding: 16px; + color: white; + font-size: 16px; + line-height: 1.4; +} + +.links { + margin-top: 16px; + color: #9c9c9c; +} diff --git a/examples/api-routes-rate-limit/utils/rate-limit.js b/examples/api-routes-rate-limit/utils/rate-limit.js new file mode 100644 index 0000000000000..6727949ada679 --- /dev/null +++ b/examples/api-routes-rate-limit/utils/rate-limit.js @@ -0,0 +1,31 @@ +const LRU = require('lru-cache') + +const rateLimit = (options) => { + const tokenCache = new LRU({ + max: parseInt(options.uniqueTokenPerInterval || 500, 10), + maxAge: parseInt(options.interval || 60000, 10), + }) + + return { + check: (res, limit, token) => + new Promise((resolve, reject) => { + const tokenCount = tokenCache.get(token) || [0] + if (tokenCount[0] === 0) { + tokenCache.set(token, tokenCount) + } + tokenCount[0] += 1 + + const currentUsage = tokenCount[0] + const isRateLimited = currentUsage >= parseInt(limit, 10) + res.setHeader('X-RateLimit-Limit', limit) + res.setHeader( + 'X-RateLimit-Remaining', + isRateLimited ? 0 : limit - currentUsage + ) + + return isRateLimited ? reject() : resolve() + }), + } +} + +export default rateLimit From b944b06f30322076ceb9020c10cb9bf3448d2659 Mon Sep 17 00:00:00 2001 From: Jan Varho Date: Tue, 5 Jan 2021 00:43:53 +0200 Subject: [PATCH 07/22] Fix notFound false pages returning 404 (#19861) Currently pages with `notFound: false` from `getServerSideProps` behave the same as `notFound: true`, i.e. just having the key is enough to result in a 404. This fixes the check in render.tsx and adds tests for it. --- packages/next/next-server/server/render.tsx | 2 +- .../getserversideprops/pages/not-found/[slug].js | 1 + .../getserversideprops/pages/not-found/index.js | 1 + .../getserversideprops/test/index.test.js | 12 ++++++++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 8601f3109f69e..0b3d1defd97bf 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -837,7 +837,7 @@ export async function renderToHTML( throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys)) } - if ('notFound' in data) { + if ('notFound' in data && data.notFound) { if (pathname === '/404') { throw new Error( `The /404 page can not return notFound in "getStaticProps", please remove it to continue!` diff --git a/test/integration/getserversideprops/pages/not-found/[slug].js b/test/integration/getserversideprops/pages/not-found/[slug].js index a2b94c1582bca..ef680582e17a7 100644 --- a/test/integration/getserversideprops/pages/not-found/[slug].js +++ b/test/integration/getserversideprops/pages/not-found/[slug].js @@ -27,6 +27,7 @@ export const getServerSideProps = ({ query }) => { } return { + notFound: false, props: { hello: 'world', }, diff --git a/test/integration/getserversideprops/pages/not-found/index.js b/test/integration/getserversideprops/pages/not-found/index.js index a2b94c1582bca..ef680582e17a7 100644 --- a/test/integration/getserversideprops/pages/not-found/index.js +++ b/test/integration/getserversideprops/pages/not-found/index.js @@ -27,6 +27,7 @@ export const getServerSideProps = ({ query }) => { } return { + notFound: false, props: { hello: 'world', }, diff --git a/test/integration/getserversideprops/test/index.test.js b/test/integration/getserversideprops/test/index.test.js index 77c15338b42d5..96524ee813aca 100644 --- a/test/integration/getserversideprops/test/index.test.js +++ b/test/integration/getserversideprops/test/index.test.js @@ -253,6 +253,12 @@ const navigateTest = (dev = false) => { const runTests = (dev = false) => { navigateTest(dev) + it('should render correctly when notFound is false (non-dynamic)', async () => { + const res = await fetchViaHTTP(appPort, '/not-found') + + expect(res.status).toBe(200) + }) + it('should render 404 correctly when notFound is returned (non-dynamic)', async () => { const res = await fetchViaHTTP(appPort, '/not-found', { hiding: true }) @@ -274,6 +280,12 @@ const runTests = (dev = false) => { expect(await browser.eval('window.beforeNav')).toBe(1) }) + it('should render correctly when notFound is false (dynamic)', async () => { + const res = await fetchViaHTTP(appPort, '/not-found/first') + + expect(res.status).toBe(200) + }) + it('should render 404 correctly when notFound is returned (dynamic)', async () => { const res = await fetchViaHTTP(appPort, '/not-found/first', { hiding: true, From 73a2bd6a4be16e06ceb8e098489c36dec5296bb4 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 4 Jan 2021 19:41:42 -0600 Subject: [PATCH 08/22] Add pr section to azure config (#20758) Adds the `pr` section from https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers to filter paths that don't require test runs on PRs. --- azure-pipelines.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 11de5bce016a1..008c8f15e7650 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,10 +6,10 @@ trigger: include: - '*' exclude: - - bench/* - - docs/* - - errors/* - - examples/* + - bench + - docs + - errors + - examples # Do not run Azure on `canary`, `master`, or release tags. This unnecessarily # increases the backlog, and the change was already tested on the PR. branches: @@ -20,6 +20,17 @@ trigger: - master - refs/tags/* +pr: + # Do not run Azure CI for docs-only/example-only changes: + paths: + include: + - '*' + exclude: + - bench + - docs + - errors + - examples + variables: YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn NEXT_TELEMETRY_DISABLED: '1' From a1a4af2d32fc63da3d43a550e3cf13d1aca26d84 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 5 Jan 2021 12:47:52 +0100 Subject: [PATCH 09/22] Bring back indicator documentation with a note that the feature has been removed --- .../static-optimization-indicator.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/api-reference/next.config.js/static-optimization-indicator.md diff --git a/docs/api-reference/next.config.js/static-optimization-indicator.md b/docs/api-reference/next.config.js/static-optimization-indicator.md new file mode 100644 index 0000000000000..f8c512d388a44 --- /dev/null +++ b/docs/api-reference/next.config.js/static-optimization-indicator.md @@ -0,0 +1,21 @@ +--- +description: Optimized pages include an indicator to let you know if it's being statically optimized. You can opt-out of it here. +--- + +# Static Optimization Indicator + +> **Note:** This indicator was removed in Next.js version 10.0.1. We recommend upgrading to the latest version of Next.js. + +When a page qualifies for [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) we show an indicator to let you know. + +This is helpful since automatic static optimization can be very beneficial and knowing immediately in development if the page qualifies can be useful. + +In some cases this indicator might not be useful, like when working on electron applications. To remove it open `next.config.js` and disable the `autoPrerender` config in `devIndicators`: + +```js +module.exports = { + devIndicators: { + autoPrerender: false, + }, +} +``` From 873bf5dd7603f9b25ec988b17dc41cdf8fdbead4 Mon Sep 17 00:00:00 2001 From: Tuan Nguyen <69383359+gr-qft@users.noreply.github.com> Date: Tue, 5 Jan 2021 09:26:06 -0500 Subject: [PATCH 10/22] Remove an undefined parameter (#20762) `getLoginSession` takes only one argument --- examples/with-magic/pages/api/logout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-magic/pages/api/logout.js b/examples/with-magic/pages/api/logout.js index 205f70609bfce..73444d72b7554 100644 --- a/examples/with-magic/pages/api/logout.js +++ b/examples/with-magic/pages/api/logout.js @@ -4,7 +4,7 @@ import { getLoginSession } from '../../lib/auth' export default async function logout(req, res) { try { - const session = await getLoginSession(req, false) + const session = await getLoginSession(req) if (session) { await magic.users.logoutByIssuer(session.issuer) From f8e44b94258d43f88ca1730e08a054de3b4b4d4f Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 5 Jan 2021 08:47:08 -0600 Subject: [PATCH 11/22] Add documentation for rewrite params query passing behavior (#20757) Closes: https://github.com/vercel/next.js/issues/20197 --- docs/api-reference/next.config.js/rewrites.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/api-reference/next.config.js/rewrites.md b/docs/api-reference/next.config.js/rewrites.md index c32a370eddaba..a6f4efcfcf364 100644 --- a/docs/api-reference/next.config.js/rewrites.md +++ b/docs/api-reference/next.config.js/rewrites.md @@ -39,6 +39,56 @@ module.exports = { - `source` is the incoming request path pattern. - `destination` is the path you want to route to. +## Rewrite parameters + +When using parameters in a rewrite the parameters will be passed in the query by default when none of the parameters are used in the `destination`. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/old-about/:path*', + destination: '/about', // The :path parameter isn't used here so will be automatically passed in the query + }, + ] + }, +} +``` + +If a parameter is used in the destination none of the parameters will be automatically passed in the query. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/docs/:path*', + destination: '/:path*', // The :path parameter is used here so will not be automatically passed in the query + }, + ] + }, +} +``` + +You can still pass the parameters manually in the query if one is already used in the destination by specifying the query in the `destination`. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/:first/:second', + destination: '/:first?second=:second', + // Since the :first parameter is used in the destination the :second parameter + // will not automatically be added in the query although we can manually add it + // as shown above + }, + ] + }, +} +``` + ## Path Matching Path matches are allowed, for example `/blog/:slug` will match `/blog/hello-world` (no nested paths): From 575fcf3fcf09c277a8d33df7dd5395cda6f18fde Mon Sep 17 00:00:00 2001 From: tarunama Date: Wed, 6 Jan 2021 00:11:37 +0900 Subject: [PATCH 12/22] refactor(client): add return types (#20728) ## summary - Explicitly define return types - Add type of [Observer](https://github.com/vercel/next.js/pull/20728/files#diff-5de64b97b2f26e4e41d197a8295e8750825c75b8ca557a4b947a4c3569345974R7) --- packages/next/client/experimental-script.tsx | 4 ++-- packages/next/client/head-manager.ts | 7 ++++-- packages/next/client/link.tsx | 2 +- packages/next/client/page-loader.ts | 5 +++-- packages/next/client/performance-relayer.ts | 4 ++-- packages/next/client/request-idle-callback.ts | 4 +++- packages/next/client/route-loader.ts | 20 +++++++++-------- packages/next/client/router.ts | 6 ++--- packages/next/client/use-intersection.tsx | 22 +++++++++---------- packages/next/client/with-router.tsx | 2 +- 10 files changed, 41 insertions(+), 35 deletions(-) diff --git a/packages/next/client/experimental-script.tsx b/packages/next/client/experimental-script.tsx index 77e914f331bc7..d5b645a58a571 100644 --- a/packages/next/client/experimental-script.tsx +++ b/packages/next/client/experimental-script.tsx @@ -16,7 +16,7 @@ interface Props extends ScriptHTMLAttributes { preload?: boolean } -const loadScript = (props: Props) => { +const loadScript = (props: Props): void => { const { src = '', onLoad = () => {}, @@ -83,7 +83,7 @@ const loadScript = (props: Props) => { document.body.appendChild(el) } -export default function Script(props: Props) { +export default function Script(props: Props): JSX.Element | null { const { src = '', onLoad = () => {}, diff --git a/packages/next/client/head-manager.ts b/packages/next/client/head-manager.ts index 4ea0ef9597149..9e2da8f6007d1 100644 --- a/packages/next/client/head-manager.ts +++ b/packages/next/client/head-manager.ts @@ -40,7 +40,7 @@ function reactElementToDOM({ type, props }: JSX.Element): HTMLElement { return el } -function updateElements(type: string, components: JSX.Element[]) { +function updateElements(type: string, components: JSX.Element[]): void { const headEl = document.getElementsByTagName('head')[0] const headCountEl: HTMLMetaElement = headEl.querySelector( 'meta[name=next-head-count]' @@ -84,7 +84,10 @@ function updateElements(type: string, components: JSX.Element[]) { headCountEl.content = (headCount - oldTags.length + newTags.length).toString() } -export default function initHeadManager() { +export default function initHeadManager(): { + mountedInstances: Set + updateHead: (head: JSX.Element[]) => void +} { let updatePromise: Promise | null = null return { diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 0ea989d872b7b..7a89a7e4843c8 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -62,7 +62,7 @@ function prefetch( prefetched[href + '%' + as + (curLocale ? '%' + curLocale : '')] = true } -function isModifiedEvent(event: React.MouseEvent) { +function isModifiedEvent(event: React.MouseEvent): boolean { const { target } = event.currentTarget as HTMLAnchorElement return ( (target && target !== '_self') || diff --git a/packages/next/client/page-loader.ts b/packages/next/client/page-loader.ts index 01d1350a0644e..b95c0e7e331c2 100644 --- a/packages/next/client/page-loader.ts +++ b/packages/next/client/page-loader.ts @@ -13,7 +13,7 @@ import createRouteLoader, { RouteLoader, } from './route-loader' -function normalizeRoute(route: string) { +function normalizeRoute(route: string): string { if (route[0] !== '/') { throw new Error(`Route name should start with a "/", got "${route}"`) } @@ -83,13 +83,14 @@ export default class PageLoader { /** * @param {string} href the route href (file-system path) * @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes + * @returns {string} */ getDataHref( href: string, asPath: string, ssg: boolean, locale?: string | false - ) { + ): string { const { pathname: hrefPathname, query, search } = parseRelativeUrl(href) const { pathname: asPathname } = parseRelativeUrl(asPath) const route = normalizeRoute(hrefPathname) diff --git a/packages/next/client/performance-relayer.ts b/packages/next/client/performance-relayer.ts index 83ff892f58f3b..ecd25791f0e71 100644 --- a/packages/next/client/performance-relayer.ts +++ b/packages/next/client/performance-relayer.ts @@ -12,7 +12,7 @@ const initialHref = location.href let isRegistered = false let userReportHandler: ReportHandler | undefined -function onReport(metric: Metric) { +function onReport(metric: Metric): void { if (userReportHandler) { userReportHandler(metric) } @@ -57,7 +57,7 @@ function onReport(metric: Metric) { } } -export default (onPerfEntry?: ReportHandler) => { +export default (onPerfEntry?: ReportHandler): void => { // Update function if it changes: userReportHandler = onPerfEntry diff --git a/packages/next/client/request-idle-callback.ts b/packages/next/client/request-idle-callback.ts index cea89be0ddabc..46127657673a3 100644 --- a/packages/next/client/request-idle-callback.ts +++ b/packages/next/client/request-idle-callback.ts @@ -18,7 +18,9 @@ declare global { const requestIdleCallback = (typeof self !== 'undefined' && self.requestIdleCallback) || - function (cb: (deadline: RequestIdleCallbackDeadline) => void) { + function ( + cb: (deadline: RequestIdleCallbackDeadline) => void + ): NodeJS.Timeout { let start = Date.now() return setTimeout(function () { cb({ diff --git a/packages/next/client/route-loader.ts b/packages/next/client/route-loader.ts index ce3073a5a348c..ba6023cf7047f 100644 --- a/packages/next/client/route-loader.ts +++ b/packages/next/client/route-loader.ts @@ -55,7 +55,7 @@ function withFuture( return Promise.resolve(entry) } let resolver: (entrypoint: T) => void - const prom = new Promise((resolve) => { + const prom: Promise = new Promise((resolve) => { resolver = resolve }) map.set(key, (entry = { resolve: resolver!, future: prom })) @@ -120,7 +120,7 @@ export function markAssetError(err: Error): Error { return Object.defineProperty(err, ASSET_LOAD_ERROR, {}) } -export function isAssetError(err?: Error) { +export function isAssetError(err?: Error): boolean | undefined { return err && ASSET_LOAD_ERROR in err } @@ -166,7 +166,9 @@ export function getClientBuildManifest(): Promise { return Promise.resolve(self.__BUILD_MANIFEST) } - const onBuildManifest = new Promise((resolve) => { + const onBuildManifest: Promise = new Promise< + ClientBuildManifest + >((resolve) => { // Mandatory because this is not concurrent safe: const cb = self.__BUILD_MANIFEST_CB self.__BUILD_MANIFEST_CB = () => { @@ -229,7 +231,7 @@ function createRouteLoader(assetPrefix: string): RouteLoader { > = new Map() function maybeExecuteScript(src: string): Promise { - let prom = loadedScripts.get(src) + let prom: Promise | undefined = loadedScripts.get(src) if (prom) { return prom } @@ -244,7 +246,7 @@ function createRouteLoader(assetPrefix: string): RouteLoader { } function fetchStyleSheet(href: string): Promise { - let prom = styleSheets.get(href) + let prom: Promise | undefined = styleSheets.get(href) if (prom) { return prom } @@ -269,7 +271,7 @@ function createRouteLoader(assetPrefix: string): RouteLoader { whenEntrypoint(route: string) { return withFuture(route, entrypoints) }, - onEntrypoint(route, execute) { + onEntrypoint(route: string, execute: () => unknown) { Promise.resolve(execute) .then((fn) => fn()) .then( @@ -285,7 +287,7 @@ function createRouteLoader(assetPrefix: string): RouteLoader { if (old && 'resolve' in old) old.resolve(input) }) }, - loadRoute(route) { + loadRoute(route: string) { return withFuture(route, routes, async () => { try { const { scripts, css } = await getFilesForRoute(assetPrefix, route) @@ -296,7 +298,7 @@ function createRouteLoader(assetPrefix: string): RouteLoader { Promise.all(css.map(fetchStyleSheet)), ] as const) - const entrypoint = await Promise.race([ + const entrypoint: RouteEntrypoint = await Promise.race([ this.whenEntrypoint(route), idleTimeout( MS_MAX_IDLE_DELAY, @@ -315,7 +317,7 @@ function createRouteLoader(assetPrefix: string): RouteLoader { } }) }, - prefetch(route) { + prefetch(route: string): Promise { // https://github.com/GoogleChromeLabs/quicklink/blob/453a661fa1fa940e2d2e044452398e38c67a98fb/src/index.mjs#L115-L118 // License: Apache 2.0 let cn diff --git a/packages/next/client/router.ts b/packages/next/client/router.ts index 093a310f3b60d..ff3c66dc92013 100644 --- a/packages/next/client/router.ts +++ b/packages/next/client/router.ts @@ -66,7 +66,7 @@ Object.defineProperty(singletonRouter, 'events', { }, }) -urlPropertyFields.forEach((field) => { +urlPropertyFields.forEach((field: string) => { // Here we need to use Object.defineProperty because, we need to return // the property assigned to the actual router // The value might get changed as we change routes and this is the @@ -79,7 +79,7 @@ urlPropertyFields.forEach((field) => { }) }) -coreMethodFields.forEach((field) => { +coreMethodFields.forEach((field: string) => { // We don't really know the types here, so we add them later instead ;(singletonRouter as any)[field] = (...args: any[]) => { const router = getRouter() as any @@ -87,7 +87,7 @@ coreMethodFields.forEach((field) => { } }) -routerEvents.forEach((event) => { +routerEvents.forEach((event: string) => { singletonRouter.ready(() => { Router.events.on(event, (...args) => { const eventField = `on${event.charAt(0).toUpperCase()}${event.substring( diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 93aa6c385f43e..ad198e4d7f5ff 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -4,6 +4,11 @@ import requestIdleCallback from './request-idle-callback' type UseIntersectionObserverInit = Pick type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit type ObserveCallback = (isVisible: boolean) => void +type Observer = { + id: string + observer: IntersectionObserver + elements: Map +} const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined' @@ -11,7 +16,7 @@ export function useIntersection({ rootMargin, disabled, }: UseIntersection): [(element: T | null) => void, boolean] { - const isDisabled = disabled || !hasIntersectionObserver + const isDisabled: boolean = disabled || !hasIntersectionObserver const unobserve = useRef() const [visible, setVisible] = useState(false) @@ -49,12 +54,12 @@ function observe( element: Element, callback: ObserveCallback, options: UseIntersectionObserverInit -) { +): () => void { const { id, observer, elements } = createObserver(options) elements.set(element, callback) observer.observe(element) - return function unobserve() { + return function unobserve(): void { elements.delete(element) observer.unobserve(element) @@ -66,15 +71,8 @@ function observe( } } -const observers = new Map< - string, - { - id: string - observer: IntersectionObserver - elements: Map - } ->() -function createObserver(options: UseIntersectionObserverInit) { +const observers = new Map() +function createObserver(options: UseIntersectionObserverInit): Observer { const id = options.rootMargin || '' let instance = observers.get(id) if (instance) { diff --git a/packages/next/client/with-router.tsx b/packages/next/client/with-router.tsx index 59f50c8dc26f8..217a65bd15c1d 100644 --- a/packages/next/client/with-router.tsx +++ b/packages/next/client/with-router.tsx @@ -17,7 +17,7 @@ export default function withRouter< >( ComposedComponent: NextComponentType ): React.ComponentType> { - function WithRouterWrapper(props: any) { + function WithRouterWrapper(props: any): JSX.Element { return } From 266a4d33b1a783d819dd03fe615e47b0df87d331 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 5 Jan 2021 17:42:23 +0100 Subject: [PATCH 13/22] fix deploy button (#20774) --- examples/with-sitemap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-sitemap/README.md b/examples/with-sitemap/README.md index 9fbe6e410b82c..d37b012317d39 100644 --- a/examples/with-sitemap/README.md +++ b/examples/with-sitemap/README.md @@ -6,7 +6,7 @@ This example shows how to generate a `sitemap.xml` file based on the pages in yo 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/hello-world) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-sitemap) ## How to use From 6fd877ff345e1402e840bc26465237ffda8868aa Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 5 Jan 2021 17:43:27 +0100 Subject: [PATCH 14/22] fix deploy button + create-next-app command (#20777) --- examples/with-strict-csp/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/with-strict-csp/README.md b/examples/with-strict-csp/README.md index 7523e5c870c11..8960ef5740113 100644 --- a/examples/with-strict-csp/README.md +++ b/examples/with-strict-csp/README.md @@ -9,16 +9,16 @@ Note: There are still valid cases for using a nonce in case you need to inline s 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/with-strict-csp-hash) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-strict-csp) ## 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 with-strict-csp-hash with-strict-csp-hash-app +npx create-next-app --example with-strict-csp with-strict-csp-app # or -yarn create next-app --example with-strict-csp-hash with-strict-csp-hash-app +yarn create next-app --example with-strict-csp with-strict-csp-app ``` 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)). From b6c6770cd9611c0b77d52d307ac0c8107bab478a Mon Sep 17 00:00:00 2001 From: kaykdm <34934746+kaykdm@users.noreply.github.com> Date: Wed, 6 Jan 2021 01:44:40 +0900 Subject: [PATCH 15/22] Fix with-react-intl example (#20763) Co-authored-by: Kanta Kodama --- examples/with-react-intl/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/with-react-intl/package.json b/examples/with-react-intl/package.json index eeaee257d9a55..c9e0ff93b7a5e 100644 --- a/examples/with-react-intl/package.json +++ b/examples/with-react-intl/package.json @@ -6,7 +6,7 @@ "dev-no-custom-server": "next dev", "build": "npm run extract:i18n && npm run compile:i18n && next build && tsc -p tsconfig.server.json", "extract:i18n": "formatjs extract '{pages,components}/*.{js,ts,tsx}' --format simple --id-interpolation-pattern '[sha512:contenthash:base64:6]' --out-file lang/en.json", - "compile:i18n": "formatjs compile-folder --ast --format simple lang/ compiled-lang/", + "compile:i18n": "formatjs compile-folder --ast --format simple lang compiled-lang", "start": "cross-env NODE_ENV=production NODE_ICU_DATA=node_modules/full-icu node dist/server", "start-no-custom-server": "next start" }, @@ -32,7 +32,7 @@ "@types/accepts": "^1.3.5", "cross-spawn": "7.0.3", "prettier": "2.0.5", - "ts-node": "8.0.0", + "ts-node": "8.0.1", "typescript": "4.0" }, "prettier": { From c8bc17f330499988173f4fc8ec4606168c85d107 Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Tue, 5 Jan 2021 12:51:34 -0800 Subject: [PATCH 16/22] Support for custom image loaders via image component prop (#20216) This is a #19325 reconfigured to support a loader passed in via a `loader` prop on the Image component, rather than using a config-based approach. The idea is that applications wanting to use a custom loader will create a wrapper element for the image component that incorporates that loader. See a simple example of this pattern in the integration tests. This solution is similar to the one prototyped by @ricokahler in #20213 and described at https://github.com/vercel/next.js/issues/18606#issuecomment-720149156 --- Closes #19325 Fixes #18606 --- packages/next/client/image.tsx | 101 +++++++++++------- .../custom-resolver/next.config.js | 8 ++ .../custom-resolver/pages/client-side.js | 36 +++++++ .../custom-resolver/pages/index.js | 40 +++++++ .../custom-resolver/test/index.test.js | 60 +++++++++++ 5 files changed, 205 insertions(+), 40 deletions(-) create mode 100644 test/integration/image-component/custom-resolver/next.config.js create mode 100644 test/integration/image-component/custom-resolver/pages/client-side.js create mode 100644 test/integration/image-component/custom-resolver/pages/index.js create mode 100644 test/integration/image-component/custom-resolver/test/index.test.js diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 45adafe472eaf..2cd80c1d0c6ba 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -16,7 +16,20 @@ if (typeof window === 'undefined') { const VALID_LOADING_VALUES = ['lazy', 'eager', undefined] as const type LoadingValue = typeof VALID_LOADING_VALUES[number] -const loaders = new Map string>([ +export type ImageLoader = (resolverProps: ImageLoaderProps) => string + +export type ImageLoaderProps = { + src: string + width: number + quality?: number +} + +type DefaultImageLoaderProps = ImageLoaderProps & { root: string } + +const loaders = new Map< + LoaderValue, + (props: DefaultImageLoaderProps) => string +>([ ['imgix', imgixLoader], ['cloudinary', cloudinaryLoader], ['akamai', akamaiLoader], @@ -39,6 +52,7 @@ export type ImageProps = Omit< 'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading' | 'style' > & { src: string + loader?: ImageLoader quality?: number | string priority?: boolean loading?: LoadingValue @@ -103,28 +117,11 @@ function getWidths( return { widths, kind: 'x' } } -type CallLoaderProps = { - src: string - width: number - quality?: number -} - -function callLoader(loaderProps: CallLoaderProps): string { - const load = loaders.get(configLoader) - if (load) { - return load({ root: configPath, ...loaderProps }) - } - throw new Error( - `Unknown "loader" found in "next.config.js". Expected: ${VALID_LOADERS.join( - ', ' - )}. Received: ${configLoader}` - ) -} - type GenImgAttrsData = { src: string unoptimized: boolean layout: LayoutValue + loader: ImageLoader width?: number quality?: number sizes?: string @@ -143,6 +140,7 @@ function generateImgAttrs({ width, quality, sizes, + loader, }: GenImgAttrsData): GenImgAttrsResult { if (unoptimized) { return { src, srcSet: undefined, sizes: undefined } @@ -151,22 +149,18 @@ function generateImgAttrs({ const { widths, kind } = getWidths(width, layout) const last = widths.length - 1 - const srcSet = widths - .map( - (w, i) => - `${callLoader({ src, quality, width: w })} ${ - kind === 'w' ? w : i + 1 - }${kind}` - ) - .join(', ') - - if (!sizes && kind === 'w') { - sizes = '100vw' + return { + src: loader({ src, quality, width: widths[last] }), + sizes: !sizes && kind === 'w' ? '100vw' : sizes, + srcSet: widths + .map( + (w, i) => + `${loader({ src, quality, width: w })} ${ + kind === 'w' ? w : i + 1 + }${kind}` + ) + .join(', '), } - - src = callLoader({ src, quality, width: widths[last] }) - - return { src, sizes, srcSet } } function getInt(x: unknown): number | undefined { @@ -179,6 +173,18 @@ function getInt(x: unknown): number | undefined { return undefined } +function defaultImageLoader(loaderProps: ImageLoaderProps) { + const load = loaders.get(configLoader) + if (load) { + return load({ root: configPath, ...loaderProps }) + } + throw new Error( + `Unknown "loader" found in "next.config.js". Expected: ${VALID_LOADERS.join( + ', ' + )}. Received: ${configLoader}` + ) +} + export default function Image({ src, sizes, @@ -191,6 +197,7 @@ export default function Image({ height, objectFit, objectPosition, + loader = defaultImageLoader, ...all }: ImageProps) { let rest: Partial = all @@ -377,6 +384,7 @@ export default function Image({ width: widthInt, quality: qualityInt, sizes, + loader, }) } @@ -444,13 +452,16 @@ export default function Image({ //BUILT IN LOADERS -type LoaderProps = CallLoaderProps & { root: string } - function normalizeSrc(src: string): string { return src[0] === '/' ? src.slice(1) : src } -function imgixLoader({ root, src, width, quality }: LoaderProps): string { +function imgixLoader({ + root, + src, + width, + quality, +}: DefaultImageLoaderProps): string { // Demo: https://static.imgix.net/daisy.png?format=auto&fit=max&w=300 const params = ['auto=format', 'fit=max', 'w=' + width] let paramsString = '' @@ -464,18 +475,28 @@ function imgixLoader({ root, src, width, quality }: LoaderProps): string { return `${root}${normalizeSrc(src)}${paramsString}` } -function akamaiLoader({ root, src, width }: LoaderProps): string { +function akamaiLoader({ root, src, width }: DefaultImageLoaderProps): string { return `${root}${normalizeSrc(src)}?imwidth=${width}` } -function cloudinaryLoader({ root, src, width, quality }: LoaderProps): string { +function cloudinaryLoader({ + root, + src, + width, + quality, +}: DefaultImageLoaderProps): string { // Demo: https://res.cloudinary.com/demo/image/upload/w_300,c_limit,q_auto/turtles.jpg const params = ['f_auto', 'c_limit', 'w_' + width, 'q_' + (quality || 'auto')] let paramsString = params.join(',') + '/' return `${root}${paramsString}${normalizeSrc(src)}` } -function defaultLoader({ root, src, width, quality }: LoaderProps): string { +function defaultLoader({ + root, + src, + width, + quality, +}: DefaultImageLoaderProps): string { if (process.env.NODE_ENV !== 'production') { const missingValues = [] diff --git a/test/integration/image-component/custom-resolver/next.config.js b/test/integration/image-component/custom-resolver/next.config.js new file mode 100644 index 0000000000000..6a4a688ab4c85 --- /dev/null +++ b/test/integration/image-component/custom-resolver/next.config.js @@ -0,0 +1,8 @@ +module.exports = { + images: { + deviceSizes: [480, 1024, 1600, 2000], + imageSizes: [16, 32, 48, 64], + path: 'https://globalresolver.com/myaccount/', + loader: 'imgix', + }, +} diff --git a/test/integration/image-component/custom-resolver/pages/client-side.js b/test/integration/image-component/custom-resolver/pages/client-side.js new file mode 100644 index 0000000000000..d702f8c0d71c3 --- /dev/null +++ b/test/integration/image-component/custom-resolver/pages/client-side.js @@ -0,0 +1,36 @@ +import React from 'react' +import Image from 'next/image' + +const myLoader = ({ src, width, quality }) => { + return `https://customresolver.com/${src}?w~~${width},q~~${quality}` +} + +const MyImage = (props) => { + return +} + +const Page = () => { + return ( +
+

Image Client Side Test

+ + +
+ ) +} + +export default Page diff --git a/test/integration/image-component/custom-resolver/pages/index.js b/test/integration/image-component/custom-resolver/pages/index.js new file mode 100644 index 0000000000000..fd8db6f3e72d0 --- /dev/null +++ b/test/integration/image-component/custom-resolver/pages/index.js @@ -0,0 +1,40 @@ +import React from 'react' +import Image from 'next/image' +import Link from 'next/link' + +const myLoader = ({ src, width, quality }) => { + return `https://customresolver.com/${src}?w~~${width},q~~${quality}` +} + +const MyImage = (props) => { + return +} + +const Page = () => { + return ( +
+

Image SSR Test

+ + + + Client Side + +
+ ) +} + +export default Page diff --git a/test/integration/image-component/custom-resolver/test/index.test.js b/test/integration/image-component/custom-resolver/test/index.test.js new file mode 100644 index 0000000000000..c7a9155dbdabb --- /dev/null +++ b/test/integration/image-component/custom-resolver/test/index.test.js @@ -0,0 +1,60 @@ +/* eslint-env jest */ + +import { join } from 'path' +import { killApp, findPort, nextStart, nextBuild } from 'next-test-utils' +import webdriver from 'next-webdriver' + +jest.setTimeout(1000 * 30) + +const appDir = join(__dirname, '../') +let appPort +let app +let browser + +function runTests() { + it('Should use a custom resolver for image URL', async () => { + expect(await browser.elementById('basic-image').getAttribute('src')).toBe( + 'https://customresolver.com/foo.jpg?w~~1024,q~~60' + ) + }) + it('should add a srcset based on the custom resolver', async () => { + expect( + await browser.elementById('basic-image').getAttribute('srcset') + ).toBe( + 'https://customresolver.com/foo.jpg?w~~480,q~~60 1x, https://customresolver.com/foo.jpg?w~~1024,q~~60 2x' + ) + }) + it('should support the unoptimized attribute', async () => { + expect( + await browser.elementById('unoptimized-image').getAttribute('src') + ).toBe('https://arbitraryurl.com/foo.jpg') + }) +} + +describe('Custom Resolver Tests', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killApp(app)) + describe('SSR Custom Loader Tests', () => { + beforeAll(async () => { + browser = await webdriver(appPort, '/') + }) + afterAll(async () => { + browser = null + }) + runTests() + }) + describe('Client-side Custom Loader Tests', () => { + beforeAll(async () => { + browser = await webdriver(appPort, '/') + await browser.waitForElementByCss('#clientlink').click() + }) + afterAll(async () => { + browser = null + }) + runTests() + }) +}) From 356bcdec0302c13af312ddcc5c39e78ef8e40c9d Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Tue, 5 Jan 2021 13:59:37 -0800 Subject: [PATCH 17/22] Move `window` check to after initializing Firebase (#20764) You can initialize the Firebase app instance without checking for window, but it is required for using the analytics module. --- examples/with-firebase/firebase/clientApp.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/with-firebase/firebase/clientApp.js b/examples/with-firebase/firebase/clientApp.js index ed52fa5f79014..68c829cfd9741 100644 --- a/examples/with-firebase/firebase/clientApp.js +++ b/examples/with-firebase/firebase/clientApp.js @@ -15,13 +15,15 @@ const clientCredentials = { appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, } -// Check that `window` is in scope for the analytics module! -if (typeof window !== 'undefined' && !firebase.apps.length) { +if (!firebase.apps.length) { firebase.initializeApp(clientCredentials) - // To enable analytics. https://firebase.google.com/docs/analytics/get-started - if ('measurementId' in clientCredentials) { - firebase.analytics() - firebase.performance() + // Check that `window` is in scope for the analytics module! + if (typeof window !== 'undefined') { + // Enable analytics. https://firebase.google.com/docs/analytics/get-started + if ('measurementId' in clientCredentials) { + firebase.analytics() + firebase.performance() + } } } From 51f2a530d623f4b50e71ce849e4276d2fea28211 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 5 Jan 2021 17:36:33 -0500 Subject: [PATCH 18/22] Updated deploy button URL (#20756) * Updated deploy button URL * Update bottom button --- examples/with-graphql-faunadb/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/with-graphql-faunadb/README.md b/examples/with-graphql-faunadb/README.md index ffe8b9774f63b..4c3c3856b928d 100644 --- a/examples/with-graphql-faunadb/README.md +++ b/examples/with-graphql-faunadb/README.md @@ -6,7 +6,7 @@ This simple Guestbook SPA example shows you how to use [FaunaDB's GraphQL endpoi Deploy the example using [Vercel](https://vercel.com): -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fhello-world&env=NEXT_PUBLIC_FAUNADB_SECRET,NEXT_PUBLIC_FAUNADB_GRAPHQL_ENDPOINT&envDescription=Client%20secret%20and%20GraphQL%20endpoint%20needed%20for%20communicating%20with%20the%20live%20Fauna%20database&project-name=my-nextjs-guestbook&repository-name=my-nextjs-guestbook&demo-title=Next.js%20Fauna%20Guestbook%20App&demo-description=A%20simple%20guestbook%20application%20built%20with%20Next.js%20and%20Fauna&demo-url=https%3A%2F%2Fnextjs-guestbook.vercel.app%2F) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-graphql-faunadb&env=NEXT_PUBLIC_FAUNADB_SECRET,NEXT_PUBLIC_FAUNADB_GRAPHQL_ENDPOINT&envDescription=Client%20secret%20and%20GraphQL%20endpoint%20needed%20for%20communicating%20with%20the%20live%20Fauna%20database&project-name=my-nextjs-guestbook&repository-name=my-nextjs-guestbook&demo-title=Next.js%20Fauna%20Guestbook%20App&demo-description=A%20simple%20guestbook%20application%20built%20with%20Next.js%20and%20Fauna&demo-url=https%3A%2F%2Fnextjs-guestbook.vercel.app%2F) ## Why FaunaDB @@ -58,4 +58,4 @@ Your app should be up and running on [http://localhost:3000](http://localhost:30 ### Deploy -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fhello-world&env=NEXT_PUBLIC_FAUNADB_SECRET,NEXT_PUBLIC_FAUNADB_GRAPHQL_ENDPOINT&envDescription=Client%20secret%20and%20GraphQL%20endpoint%20needed%20for%20communicating%20with%20the%20live%20Fauna%20database&project-name=my-nextjs-guestbook&repository-name=my-nextjs-guestbook&demo-title=Next.js%20Fauna%20Guestbook%20App&demo-description=A%20simple%20guestbook%20application%20built%20with%20Next.js%20and%20Fauna&demo-url=https%3A%2F%2Fnextjs-guestbook.vercel.app%2F) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-graphql-faunadb&env=NEXT_PUBLIC_FAUNADB_SECRET,NEXT_PUBLIC_FAUNADB_GRAPHQL_ENDPOINT&envDescription=Client%20secret%20and%20GraphQL%20endpoint%20needed%20for%20communicating%20with%20the%20live%20Fauna%20database&project-name=my-nextjs-guestbook&repository-name=my-nextjs-guestbook&demo-title=Next.js%20Fauna%20Guestbook%20App&demo-description=A%20simple%20guestbook%20application%20built%20with%20Next.js%20and%20Fauna&demo-url=https%3A%2F%2Fnextjs-guestbook.vercel.app%2F) From 47b7660aec950d2985f1d137d574fb84b935d555 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 5 Jan 2021 19:20:04 -0600 Subject: [PATCH 19/22] Ensure path starts with / when deleting index basePath with query (#20766) This is a follow-up to https://github.com/vercel/next.js/pull/20596 and https://github.com/vercel/next.js/pull/20658 ensuring the `as` value is prefixed with the `basePath` correctly with a query. This updates the test to also ensure no errors are shown when a query is present on the index `basePath` route. Fixes: https://github.com/vercel/next.js/pull/20757#issuecomment-754338510 --- .../next/next-server/lib/router/router.ts | 39 +++++++++++++++---- test/integration/basepath/test/index.test.js | 8 ++++ .../build-output/test/index.test.js | 6 +-- .../integration/size-limit/test/index.test.js | 2 +- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index 00958c73dc5aa..00c9c4ae2a313 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -81,7 +81,7 @@ function addPathPrefix(path: string, prefix?: string) { return prefix && path.startsWith('/') ? path === '/' ? normalizePathTrailingSlash(prefix) - : `${prefix}${path}` + : `${prefix}${pathNoQueryHash(path) === '/' ? path.substring(1) : path}` : path } @@ -133,13 +133,18 @@ export function delLocale(path: string, locale?: string) { return path } -export function hasBasePath(path: string): boolean { +function pathNoQueryHash(path: string) { const queryIndex = path.indexOf('?') const hashIndex = path.indexOf('#') if (queryIndex > -1 || hashIndex > -1) { path = path.substring(0, queryIndex > -1 ? queryIndex : hashIndex) } + return path +} + +export function hasBasePath(path: string): boolean { + path = pathNoQueryHash(path) return path === basePath || path.startsWith(basePath + '/') } @@ -149,7 +154,9 @@ export function addBasePath(path: string): string { } export function delBasePath(path: string): string { - return path.slice(basePath.length) || '/' + path = path.slice(basePath.length) + if (!path.startsWith('/')) path = `/${path}` + return path } /** @@ -302,15 +309,31 @@ export function resolveHref( } } +function stripOrigin(url: string) { + const origin = getLocationOrigin() + + return url.startsWith(origin) ? url.substring(origin.length) : url +} + function prepareUrlAs(router: NextRouter, url: Url, as?: Url) { // If url and as provided as an object representation, // we'll format them into the string version here. - const [resolvedHref, resolvedAs] = resolveHref(router.pathname, url, true) + let [resolvedHref, resolvedAs] = resolveHref(router.pathname, url, true) + const origin = getLocationOrigin() + const hrefHadOrigin = resolvedHref.startsWith(origin) + const asHadOrigin = resolvedAs && resolvedAs.startsWith(origin) + + resolvedHref = stripOrigin(resolvedHref) + resolvedAs = resolvedAs ? stripOrigin(resolvedAs) : resolvedAs + + const preparedUrl = hrefHadOrigin ? resolvedHref : addBasePath(resolvedHref) + const preparedAs = as + ? stripOrigin(resolveHref(router.pathname, as)) + : resolvedAs || resolvedHref + return { - url: addBasePath(resolvedHref), - as: addBasePath( - as ? resolveHref(router.pathname, as) : resolvedAs || resolvedHref - ), + url: preparedUrl, + as: asHadOrigin ? preparedAs : addBasePath(preparedAs), } } diff --git a/test/integration/basepath/test/index.test.js b/test/integration/basepath/test/index.test.js index 8d0b5ae23c5d4..3c73d03e52399 100644 --- a/test/integration/basepath/test/index.test.js +++ b/test/integration/basepath/test/index.test.js @@ -1024,6 +1024,10 @@ const runTests = (dev = false) => { `${basePath}/hello` ) expect(await browser.eval('window.location.search')).toBe('?query=true') + + if (dev) { + expect(await hasRedbox(browser, false)).toBe(false) + } } finally { await browser.close() } @@ -1044,6 +1048,10 @@ const runTests = (dev = false) => { expect(pathname).toBe('/') expect(await browser.eval('window.location.pathname')).toBe(basePath) expect(await browser.eval('window.location.search')).toBe('?query=true') + + if (dev) { + expect(await hasRedbox(browser, false)).toBe(false) + } } finally { await browser.close() } diff --git a/test/integration/build-output/test/index.test.js b/test/integration/build-output/test/index.test.js index 36efddc1d58f1..aee083a85e08c 100644 --- a/test/integration/build-output/test/index.test.js +++ b/test/integration/build-output/test/index.test.js @@ -95,16 +95,16 @@ describe('Build Output', () => { expect(indexSize.endsWith('B')).toBe(true) // should be no bigger than 62.2 kb - expect(parseFloat(indexFirstLoad)).toBeCloseTo(62.3, 1) + expect(parseFloat(indexFirstLoad)).toBeCloseTo(62.4, 1) expect(indexFirstLoad.endsWith('kB')).toBe(true) expect(parseFloat(err404Size) - 3.7).toBeLessThanOrEqual(0) expect(err404Size.endsWith('kB')).toBe(true) - expect(parseFloat(err404FirstLoad)).toBeCloseTo(65.5, 1) + expect(parseFloat(err404FirstLoad)).toBeCloseTo(65.6, 1) expect(err404FirstLoad.endsWith('kB')).toBe(true) - expect(parseFloat(sharedByAll)).toBeCloseTo(62, 1) + expect(parseFloat(sharedByAll)).toBeCloseTo(62.1, 1) expect(sharedByAll.endsWith('kB')).toBe(true) if (_appSize.endsWith('kB')) { diff --git a/test/integration/size-limit/test/index.test.js b/test/integration/size-limit/test/index.test.js index a2bc467eec594..fcf4002ff8bc6 100644 --- a/test/integration/size-limit/test/index.test.js +++ b/test/integration/size-limit/test/index.test.js @@ -81,6 +81,6 @@ describe('Production response size', () => { const delta = responseSizesBytes / 1024 // Expected difference: < 0.5 - expect(delta).toBeCloseTo(282, 0) + expect(delta).toBeCloseTo(282.6, 0) }) }) From 9f6bcd47b63b1794f647444b7d21cf09c675b09e Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Tue, 5 Jan 2021 20:20:36 -0500 Subject: [PATCH 20/22] v10.0.5-canary.10 --- 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-env/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-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next/package.json | 12 ++++++------ packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index 54310c2c4acc3..028b8eba549e1 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "10.0.5-canary.9" + "version": "10.0.5-canary.10" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 66f35afe979b6..a18efa5e4e518 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "keywords": [ "react", "next", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 643866fb7ac7d..6b51ea4e2a64c 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": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 d0ee79abfd89c..f9c10292018af 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 163db6690e599..3fdbf25c9f743 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index d307d6c205d52..0a93ecd50ad91 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index d7071244f9e80..94fc9e101c064 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 264529d12ea89..1a817d86b86ce 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": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 f143f1c3a906a..a6f952753ba56 100644 --- a/packages/next-plugin-sentry/package.json +++ b/packages/next-plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-sentry", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 481066b876e9f..fba5e244f4c79 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 8e272f600433a..ef3bdb8fa68f3 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 a4d9cf64a4e97..00240caeec113 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 560692b32cae8..88e6c678398db 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -63,10 +63,10 @@ "@ampproject/toolbox-optimizer": "2.7.1-alpha.0", "@babel/runtime": "7.12.5", "@hapi/accept": "5.0.1", - "@next/env": "10.0.5-canary.9", - "@next/polyfill-module": "10.0.5-canary.9", - "@next/react-dev-overlay": "10.0.5-canary.9", - "@next/react-refresh-utils": "10.0.5-canary.9", + "@next/env": "10.0.5-canary.10", + "@next/polyfill-module": "10.0.5-canary.10", + "@next/react-dev-overlay": "10.0.5-canary.10", + "@next/react-refresh-utils": "10.0.5-canary.10", "@opentelemetry/api": "0.14.0", "ast-types": "0.13.2", "babel-plugin-transform-define": "2.0.0", @@ -144,7 +144,7 @@ "@babel/preset-react": "7.12.10", "@babel/preset-typescript": "7.12.7", "@babel/types": "7.12.12", - "@next/polyfill-nomodule": "10.0.5-canary.9", + "@next/polyfill-nomodule": "10.0.5-canary.10", "@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 a306b33e75021..b5deacd01dfa0 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": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "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 38fe96ce57867..bb33edd215533 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": "10.0.5-canary.9", + "version": "10.0.5-canary.10", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From 9afc0f58c4f3120a8e04b03d48973433f335324a Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Tue, 5 Jan 2021 20:16:09 -0600 Subject: [PATCH 21/22] Add docs for custom image loaders. (#20788) Docs for https://github.com/vercel/next.js/pull/20216/. --- docs/api-reference/next/image.md | 31 +++++++++++++++++++++++ docs/basic-features/image-optimization.md | 4 ++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index dcca53f4db4ed..e596719b242e8 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,6 +16,7 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------ | +| `v10.0.5` | `loader` prop added. | | `v10.0.1` | `layout` prop added. | | `v10.0.0` | `next/image` introduced. | @@ -111,6 +112,36 @@ Try it out: - [Demo the `fill` layout](https://image-component.nextjs.gallery/layout-fill) - [Demo background image](https://image-component.nextjs.gallery/background) +### loader + +A custom function used to resolve URLs. Defaults to [`images` object in `next.config.js`](/docs/basic-features/image-optimization.md#loader). + +`loader` is a function returning a string, given the following parameters: + +- [`src`](#src) +- [`width`](#width) +- [`quality`](#quality) + +```js +import Image from 'next/image' + +const myLoader = ({ src, width, quality }) => { + return `https://example.com/${src}?w=${width}&q=${quality || 75}` +} + +const MyImage = (props) => { + return ( + Picture of the author + ) +} +``` + ### sizes A string mapping media queries to device sizes. Defaults to `100vw`. diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index e3734f53db5cd..8ea1ae23b912f 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -82,7 +82,7 @@ module.exports = { } ``` -The following Image Optimization cloud providers are supported: +The following Image Optimization cloud providers are included: - [Vercel](https://vercel.com): Works automatically when you deploy on Vercel, no configuration necessary. [Learn more](https://vercel.com/docs/next.js/image-optimization) - [Imgix](https://www.imgix.com): `loader: 'imgix'` @@ -90,6 +90,8 @@ The following Image Optimization cloud providers are supported: - [Akamai](https://www.akamai.com): `loader: 'akamai'` - Default: Works automatically with `next dev`, `next start`, or a custom server +If you need a different provider, you can use the [`loader`](/docs/api-reference/next/image.md#loader) prop with `next/image`. + ## Caching The following describes the caching algorithm for the default [loader](#loader). For all other loaders, please refer to your cloud provider's documentation. From 1a3adaa5d93730ad9d5fbffbf368d1526007fc3e Mon Sep 17 00:00:00 2001 From: Benjamin Bender Date: Wed, 6 Jan 2021 10:13:45 +0100 Subject: [PATCH 22/22] Fix mini typo in with-cookie-auth-fauna-example (#20808) Fix mini typo --- examples/with-cookie-auth-fauna/pages/api/signup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-cookie-auth-fauna/pages/api/signup.js b/examples/with-cookie-auth-fauna/pages/api/signup.js index 9e9f253649e3f..0610e7990eaca 100644 --- a/examples/with-cookie-auth-fauna/pages/api/signup.js +++ b/examples/with-cookie-auth-fauna/pages/api/signup.js @@ -1,7 +1,7 @@ import { query as q } from 'faunadb' import { serverClient, serializeFaunaCookie } from '../../utils/fauna-auth' -export default async function signuo(req, res) { +export default async function signup(req, res) { const { email, password } = await req.body try {