diff --git a/contributing.md b/contributing.md index 03d2a06cec1af..fa17d4ae5e2a7 100644 --- a/contributing.md +++ b/contributing.md @@ -1,6 +1,6 @@ # Contributing to Next.js -Our Commitment to Open Source can be found [here](https://vercel.com/oss). +Read about our [Commitment to Open Source](https://vercel.com/oss). 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. 2. Create a new branch: `git checkout -b MY_BRANCH_NAME` diff --git a/docs/advanced-features/amp-support/typescript.md b/docs/advanced-features/amp-support/typescript.md index 273c943d94920..2f284c26d8035 100644 --- a/docs/advanced-features/amp-support/typescript.md +++ b/docs/advanced-features/amp-support/typescript.md @@ -6,4 +6,4 @@ description: Using AMP with TypeScript? Extend your typings to allow AMP compone AMP currently doesn't have built-in types for TypeScript, but it's in their roadmap ([#13791](https://github.com/ampproject/amphtml/issues/13791)). -As a workaround you can manually create a file called `amp.d.ts` inside your project and add the custom types described [here](https://stackoverflow.com/a/50601125). +As a workaround you can manually create a file called `amp.d.ts` inside your project and add these [custom types](https://stackoverflow.com/a/50601125). diff --git a/docs/advanced-features/codemods.md b/docs/advanced-features/codemods.md index e6e990cfe9cf1..27f3bc59fc546 100644 --- a/docs/advanced-features/codemods.md +++ b/docs/advanced-features/codemods.md @@ -17,6 +17,14 @@ Codemods are transformations that run on your codebase programmatically. This al - `--dry` Do a dry-run, no code will be edited - `--print` Prints the changed output for comparison +## Next.js 11 + +### `cra-to-next` (experimental) + +Migrates a Create React App project to Next.js; creating a pages directory and necessary config to match behavior. Client-side only rendering is leveraged initially to prevent breaking compatibility due to `window` usage during SSR and can be enabled seamlessly to allow gradual adoption of Next.js specific features. + +Please share any feedback related to this transform [in this discussion](https://github.com/vercel/next.js/discussions/25858). + ## Next.js 10 ### `add-missing-react-import` diff --git a/docs/advanced-features/measuring-performance.md b/docs/advanced-features/measuring-performance.md index 329cddd452cf7..789d94bb8759c 100644 --- a/docs/advanced-features/measuring-performance.md +++ b/docs/advanced-features/measuring-performance.md @@ -175,7 +175,7 @@ export function reportWebVitals(metric) { > } > ``` > -> Read more about sending results to Google Analytics [here](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics). +> Read more about [sending results to Google Analytics](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics). ## TypeScript diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md index f13204ad7808f..3dda5faf0c1b2 100644 --- a/docs/api-reference/cli.md +++ b/docs/api-reference/cli.md @@ -74,6 +74,12 @@ The application will start at `http://localhost:3000` by default. The default po npx next dev -p 4000 ``` +Similarly, you can also set the hostname to be different from the default of `0.0.0.0`, this can be useful for making the application available for other devices on the network. The default hostname can be changed with `-H`, like so: + +```bash +npx next dev -H 192.168.1.2 +``` + ## Production `next start` starts the application in production mode. The application should be compiled with [`next build`](#build) first. diff --git a/docs/api-reference/next.config.js/introduction.md b/docs/api-reference/next.config.js/introduction.md index 474257638f4bc..097da42d032d1 100644 --- a/docs/api-reference/next.config.js/introduction.md +++ b/docs/api-reference/next.config.js/introduction.md @@ -26,7 +26,7 @@ module.exports = (phase, { defaultConfig }) => { } ``` -`phase` is the current context in which the configuration is loaded. You can see the available phases [here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/lib/constants.ts#L1-L4). Phases can be imported from `next/constants`: +`phase` is the current context in which the configuration is loaded. You can see the [available phases](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/lib/constants.ts#L1-L4). Phases can be imported from `next/constants`: ```js const { PHASE_DEVELOPMENT_SERVER } = require('next/constants') @@ -44,7 +44,7 @@ module.exports = (phase, { defaultConfig }) => { } ``` -The commented lines are the place where you can put the configs allowed by `next.config.js`, which are defined [here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts#L68). +The commented lines are the place where you can put the configs allowed by `next.config.js`, which are [defined in this file](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts#L68). However, none of the configs are required, and it's not necessary to understand what each config does. Instead, search for the features you need to enable or modify in this section and they will show you what to do. diff --git a/docs/api-reference/next/amp.md b/docs/api-reference/next/amp.md index ac3ee8924b560..6cc212791168d 100644 --- a/docs/api-reference/next/amp.md +++ b/docs/api-reference/next/amp.md @@ -11,7 +11,7 @@ description: Enable AMP in a page, and control the way Next.js adds AMP to the p -> AMP support is one of our advanced features, you can read more about it [here](/docs/advanced-features/amp-support/introduction.md). +> AMP support is one of our advanced features, you can [read more about AMP here](/docs/advanced-features/amp-support/introduction.md). To enable AMP, add the following config to your page: diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 7ce38c6b849e4..2a075daf5b44e 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -132,7 +132,7 @@ const MyImage = (props) => { return ( Picture of the author { export default Profile ``` -You can view this example in action [here](https://next-with-iron-session.vercel.app/). Check out the [`with-iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session) example to see how it works. +You can view this [example in action](https://next-with-iron-session.vercel.app/). Check out the [`with-iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session) example to see how it works. ### Authenticating Server-Rendered Pages diff --git a/errors/placeholder-blur-data-url.md b/errors/placeholder-blur-data-url.md new file mode 100644 index 0000000000000..2754a4a387503 --- /dev/null +++ b/errors/placeholder-blur-data-url.md @@ -0,0 +1,15 @@ +# `placeholder=blur` without `blurDataURL` + +#### Why This Error Occurred + +You are attempting use the `next/image` component with `placeholder=blur` property but no `blurDataURL` property. + +The `blurDataURL` might be missing because your using a string for `src` instead of a static import. + +Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, and webp are supported at this time. + +#### Possible Ways to Fix It + +- Add a [`blurDataURL`](https://nextjs.org/docs/api-reference/next/image#blurdataurl) property, the contents should be a small Data URL to represent the image +- Change the [`src`](https://nextjs.org/docs/api-reference/next/image#src) property to a static import with one of the supported file types: jpg, png, or webp +- Remove the [`placeholder`](https://nextjs.org/docs/api-reference/next/image#placeholder) property, effectively no blur effect diff --git a/errors/postcss-ignored-plugin.md b/errors/postcss-ignored-plugin.md index 658ad57f28575..e8c73f1c69965 100644 --- a/errors/postcss-ignored-plugin.md +++ b/errors/postcss-ignored-plugin.md @@ -17,4 +17,4 @@ Remove the plugin specified in the error message from your custom PostCSS config #### How do I configure CSS Modules? CSS Modules are supported in [Next.js' built-in CSS support](https://nextjs.org/docs/advanced-features/customizing-postcss-config). -You can [read more](https://nextjs.org/docs/advanced-features/customizing-postcss-config) about how to use them [here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). +You can [read more about how to use CSS Modules here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). diff --git a/errors/postcss-shape.md b/errors/postcss-shape.md index 889fe55bcbc68..f121c889d04b1 100644 --- a/errors/postcss-shape.md +++ b/errors/postcss-shape.md @@ -39,7 +39,7 @@ module.exports = { } ``` -You can [read more](https://nextjs.org/docs/advanced-features/customizing-postcss-config) about configuring PostCSS in Next.js [here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). +You can [read more about configuring PostCSS in Next.js here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). #### Common Errors diff --git a/examples/amp-first/README.md b/examples/amp-first/README.md index f1734f4feb8d8..4db9d29709f07 100644 --- a/examples/amp-first/README.md +++ b/examples/amp-first/README.md @@ -1,6 +1,6 @@ # AMP First Boilerplate ⚡ -This example sets up the boilerplate for an AMP First Site. You can see a live version [here](https://my-next-app.sebastianbenz.vercel.app). The boilerplate includes the following features: +This example sets up the boilerplate for an AMP First Site. You can see a [live version here](https://my-next-app.sebastianbenz.vercel.app). The boilerplate includes the following features: - AMP Extension helpers (`amp-state`, `amp-script`, ...) - AMP Serviceworker integration diff --git a/examples/auth0/package.json b/examples/auth0/package.json index 16f771f656d6d..2c77b7313cb42 100644 --- a/examples/auth0/package.json +++ b/examples/auth0/package.json @@ -11,6 +11,7 @@ "@auth0/nextjs-auth0": "^0.8.0", "next": "latest", "react": "^16.12.0", - "react-dom": "^16.12.0" + "react-dom": "^16.12.0", + "tslib": "^2.2.0" } } diff --git a/examples/blog-starter/README.md b/examples/blog-starter/README.md index f4966f6d10aba..0b9ab5a1d932b 100644 --- a/examples/blog-starter/README.md +++ b/examples/blog-starter/README.md @@ -42,10 +42,16 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu 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 blog-starter blog-starter-app -# or + +``` + +or + +``` yarn create next-app --example blog-starter blog-starter-app + ``` Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). diff --git a/examples/i18n-routing/README.md b/examples/i18n-routing/README.md index f9956e869d054..be097c1aedd54 100644 --- a/examples/i18n-routing/README.md +++ b/examples/i18n-routing/README.md @@ -2,7 +2,7 @@ This example shows how to create internationalized pages using Next.js and the i18n routing feature. It shows a normal page, a non-dynamic `getStaticProps` page, a dynamic `getStaticProps` page, and a `getServerSideProps` page. -For further documentation on this feature see the documentation [here](https://nextjs.org/docs/advanced-features/i18n-routing) +For further documentation on this feature see the [Internationalized Routing docs](https://nextjs.org/docs/advanced-features/i18n-routing). ## Preview diff --git a/examples/image-component/pages/index.js b/examples/image-component/pages/index.js index 11e1543ebfbd7..c4da5a71a27ea 100644 --- a/examples/image-component/pages/index.js +++ b/examples/image-component/pages/index.js @@ -2,6 +2,7 @@ import styles from '../styles.module.css' import Image from 'next/image' import Link from 'next/link' import ViewSource from '../components/view-source' +import vercel from '../public/vercel.png' const Code = (p) => @@ -62,6 +63,17 @@ const Index = () => (
+

Placeholder

+

+ Adding placeholder="blur" to an image enables a blurry + placeholder effect while that image loads. +

+

+ + See an example of the blurry placeholder. + +

+

Internal Image

The following is an example of a reference to an interal image from the{' '} @@ -71,7 +83,7 @@ const Index = () => ( This image is intentionally large so you have to scroll down to the next image.

- Vercel logo + Vercel logo

External Image

diff --git a/examples/image-component/pages/layout-fill.js b/examples/image-component/pages/layout-fill.js index dcc752235eb9e..526723d427f84 100644 --- a/examples/image-component/pages/layout-fill.js +++ b/examples/image-component/pages/layout-fill.js @@ -1,22 +1,18 @@ import Image from 'next/image' import ViewSource from '../components/view-source' +import mountains from '../public/mountains.jpg' const Fill = () => (

Image Component With Layout Fill

- Mountains + Mountains
Mountains @@ -24,7 +20,7 @@ const Fill = () => (
Mountains (
@@ -7,7 +8,7 @@ const Fixed = () => (

Image Component With Layout Fixed

Mountains (
@@ -7,7 +8,7 @@ const Intrinsic = () => (

Image Component With Layout Intrinsic

Mountains (
@@ -7,7 +8,7 @@ const Responsive = () => (

Image Component With Layout Responsive

Mountains ( +
+ +

Image Component With Layout Responsive

+ Mountains +
+) + +export default Responsive diff --git a/examples/with-electron/README.md b/examples/with-electron/README.md index 384df5a1d4907..fd95f3d8684e9 100644 --- a/examples/with-electron/README.md +++ b/examples/with-electron/README.md @@ -4,7 +4,7 @@ This example shows how you can use Next.js inside an Electron application to avo For development it's going to run an HTTP server and let Next.js handle routing. In production it uses `next export` to pre-generate HTML static files and uses them in your app instead of running an HTTP server. -**You can find a detailed documentation about how to build Electron apps with Next.js [here](https://leo.im/2017/electron-next)!** +**For detailed documentation about how to build Electron apps with Next.js, see [this blog post](https://leo.im/2017/electron-next)!** ## How to use diff --git a/examples/with-netlify-cms/content/home.md b/examples/with-netlify-cms/content/home.md index 7e0dfd0c5737a..e559431f2b1c1 100644 --- a/examples/with-netlify-cms/content/home.md +++ b/examples/with-netlify-cms/content/home.md @@ -5,4 +5,4 @@ date: 2019-03-17T19:31:20.591Z ## Welcome to the Home Page -If you want to know more about Next.js + Netlifycms go to the official tutorial [here](https://www.netlifycms.org/docs/nextjs/). +If you want to know more about Next.js + Netlify CMS go to their [official tutorial](https://www.netlifycms.org/docs/nextjs/). diff --git a/examples/with-passport/README.md b/examples/with-passport/README.md index 8c82af38da04f..4032d0f89f8f4 100644 --- a/examples/with-passport/README.md +++ b/examples/with-passport/README.md @@ -4,7 +4,7 @@ This example show how to use [Passport.js](http://www.passportjs.org) with Next. The example shows how to do a login, signup and logout; and to get the user info using a hook with [SWR](https://swr.vercel.app). -A DB is not included. You can use any db you want and add it [here](lib/user.js). +A database is not included. You can use any database you want and add it [in this file](lib/user.js). The login cookie is httpOnly, meaning it can only be accessed by the API, and it's encrypted using [@hapi/iron](https://hapi.dev/family/iron) for more security. diff --git a/examples/with-redux-persist/README.md b/examples/with-redux-persist/README.md index adeca8876e98b..80caf652424b7 100644 --- a/examples/with-redux-persist/README.md +++ b/examples/with-redux-persist/README.md @@ -4,6 +4,12 @@ This example shows how to integrate Redux with the power of Redux Persist in Nex With the advantage of having a global state for your app using `redux`. You'll also require some of your state values to be available offline. There comes `redux-persist` using which you can persist your states in browser's local storage. While there are various ways of persisting your states which you can always find in there [documentation](https://github.com/rt2zz/redux-persist/blob/master/README.md). This is an example of how you can integrate `redux-persist` with redux along with Next.js's universal rendering approach. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-redux-persist) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/with-rematch/README.md b/examples/with-rematch/README.md index dafa6cea2af18..e037ef1ea8948 100644 --- a/examples/with-rematch/README.md +++ b/examples/with-rematch/README.md @@ -2,7 +2,7 @@ This example has two pages. The first page has a counter which can be incremented synchronously or asynchronously. The second page is a page which shows a list of github users. It fetches data from the github api using this [endpoint](api.github.com/users). -Since rematch is utility which uses redux under the hood, some elements like `store.js` and `withRematch` are very similar to the `with-redux` example. Please go through the `with-redux` example [here](https://github.com/vercel/next.js/tree/master/examples/with-redux) before reading further if you are not familiar with how redux is integrated with nextjs. Rematch is just an extension for Redux so a lot of elements are the same. +Since rematch is utility which uses redux under the hood, some elements like `store.js` and `withRematch` are very similar to the `with-redux` example. Please go through the [`with-redux` example](https://github.com/vercel/next.js/tree/master/examples/with-redux) before reading further if you are not familiar with how redux is integrated with Next.js. Rematch is just an extension for Redux so a lot of elements are the same. ## Preview diff --git a/examples/with-stencil/packages/test-component/readme.md b/examples/with-stencil/packages/test-component/readme.md index db8c4cca0c49f..d6812363edd4e 100644 --- a/examples/with-stencil/packages/test-component/readme.md +++ b/examples/with-stencil/packages/test-component/readme.md @@ -43,7 +43,7 @@ To run the unit tests for the components, run: npm test ``` -Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component). +Need help? [Check out our docs](https://stenciljs.com/docs/my-first-component). ## Naming Components diff --git a/examples/with-unsplash/README.md b/examples/with-unsplash/README.md index 23535a5020cd6..452d880e72ddc 100644 --- a/examples/with-unsplash/README.md +++ b/examples/with-unsplash/README.md @@ -24,7 +24,7 @@ First, you'll need to [create an account on Unsplash](https://unsplash.com/) if ### Step 1. Create an app on Unsplash -To create a new application on Unsplash, click [here](https://unsplash.com/oauth/applications/new) or go to https://unsplash.com/oauth/applications/new. +Create a [new application on Unsplash](https://unsplash.com/oauth/applications/new). Before creating an app you'll have to accept the terms for API use: diff --git a/examples/with-urql/README.md b/examples/with-urql/README.md index 88bb790ebbd36..ff2ba7eb93d90 100644 --- a/examples/with-urql/README.md +++ b/examples/with-urql/README.md @@ -2,6 +2,12 @@ Use [urql](https://github.com/FormidableLabs/urql) with Next.js using SSG. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-urql) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/with-urql/graphql/client.js b/examples/with-urql/graphql/client.js index 40bbb98327911..c34860ff81923 100644 --- a/examples/with-urql/graphql/client.js +++ b/examples/with-urql/graphql/client.js @@ -1,5 +1,5 @@ import { createClient } from 'urql' export const client = createClient({ - url: 'https://graphql-pokemon.vercel.app/', + url: 'https://graphql-pokemon2.vercel.app/', }) diff --git a/examples/with-videojs/README.md b/examples/with-videojs/README.md index 58cf100cd7413..0429d5efa8a57 100644 --- a/examples/with-videojs/README.md +++ b/examples/with-videojs/README.md @@ -2,6 +2,12 @@ This example shows how to use Next.js along with [Video.js](https://videojs.com) including handling of default styles. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-videojs) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/with-xstate/README.md b/examples/with-xstate/README.md index 7c8ab9ffbe93e..d150c3d95f472 100644 --- a/examples/with-xstate/README.md +++ b/examples/with-xstate/README.md @@ -1,6 +1,6 @@ # XState example -This example shows how to integrate XState in Next.js. For more info about XState you can visit [here](https://xstate.js.org/). +This example shows how to integrate XState in Next.js. [Learn more about XState](https://xstate.js.org/). ## Preview diff --git a/lerna.json b/lerna.json index b0c9450ebebf2..ba19af03d6df1 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "10.2.4-canary.11" + "version": "10.2.4-canary.13" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 22cd4b634d663..133906dd5ab1b 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.2.4-canary.11", + "version": "10.2.4-canary.13", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 89ef7f1887ad9..2409d5673c714 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "10.2.4-canary.11", + "version": "10.2.4-canary.13", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "10.2.4-canary.11", + "@next/eslint-plugin-next": "10.2.4-canary.13", "@rushstack/eslint-patch": "^1.0.6", "@typescript-eslint/parser": "^4.20.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index b39a3d73c96f4..37ec33566a10b 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.2.4-canary.11", + "version": "10.2.4-canary.13", "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 a7061888920cd..5d3aba920b0b0 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.2.4-canary.11", + "version": "10.2.4-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index ca64d5bbccde6..18b665153470c 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "10.2.4-canary.11", + "version": "10.2.4-canary.13", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index a4dae1f2e911c..c2bbb5fe083a6 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "10.2.4-canary.11", + "version": "10.2.4-canary.13", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 33e301b201cc1..a60d0efc8e807 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "10.2.4-canary.11", + "version": "10.2.4-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index ccdbfbc0fae4f..a64d4fb805c2c 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.2.4-canary.11", + "version": "10.2.4-canary.13", "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 f10641451c834..75b778f8cf968 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.2.4-canary.11", + "version": "10.2.4-canary.13", "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 958cfe8343d14..146c0ca7d13c7 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.2.4-canary.11", + "version": "10.2.4-canary.13", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/build/webpack/loaders/next-image-loader.js b/packages/next/build/webpack/loaders/next-image-loader.js index f81663143a044..62906792ea725 100644 --- a/packages/next/build/webpack/loaders/next-image-loader.js +++ b/packages/next/build/webpack/loaders/next-image-loader.js @@ -2,7 +2,9 @@ import loaderUtils from 'next/dist/compiled/loader-utils' import sizeOf from 'image-size' import { processBuffer } from '../../../next-server/server/lib/squoosh/main' -const PLACEHOLDER_SIZE = 8 +const BLUR_IMG_SIZE = 8 +const BLUR_QUALITY = 70 +const VALID_BLUR_EXT = ['jpeg', 'png', 'webp'] async function nextImageLoader(content) { const context = this.rootContext @@ -19,20 +21,20 @@ async function nextImageLoader(content) { } const imageSize = sizeOf(content) - let placeholder - if (extension === 'jpeg' || extension === 'png') { - // Shrink the image's largest dimension to 6 pixels + let blurDataURL + if (VALID_BLUR_EXT.includes(extension)) { + // Shrink the image's largest dimension const resizeOperationOpts = imageSize.width >= imageSize.height - ? { type: 'resize', width: PLACEHOLDER_SIZE } - : { type: 'resize', height: PLACEHOLDER_SIZE } + ? { type: 'resize', width: BLUR_IMG_SIZE } + : { type: 'resize', height: BLUR_IMG_SIZE } const resizedImage = await processBuffer( content, [resizeOperationOpts], extension, - 70 + BLUR_QUALITY ) - placeholder = `data:image/${extension};base64,${resizedImage.toString( + blurDataURL = `data:image/${extension};base64,${resizedImage.toString( 'base64' )}` } @@ -41,7 +43,7 @@ async function nextImageLoader(content) { src: '/_next' + interpolatedName, height: imageSize.height, width: imageSize.width, - placeholder, + blurDataURL, }) this.emitFile(interpolatedName, content, null) diff --git a/packages/next/cli/next-lint.ts b/packages/next/cli/next-lint.ts index 02e4216008fb8..5c07725468732 100755 --- a/packages/next/cli/next-lint.ts +++ b/packages/next/cli/next-lint.ts @@ -5,23 +5,65 @@ import { resolve, join } from 'path' import chalk from 'chalk' import { cliCommand } from '../bin/next' +import { ESLINT_DEFAULT_DIRS } from '../lib/constants' import { runLintCheck } from '../lib/eslint/runLintCheck' import { printAndExit } from '../server/lib/utils' +const eslintOptions = (args: arg.Spec) => ({ + overrideConfigFile: args['--config'] || null, + extensions: args['--ext'] ?? ['.js', '.jsx', '.ts', '.tsx'], + resolvePluginsRelativeTo: args['--resolve-plugins-relative-to'] || null, + rulePaths: args['--rulesdir'] ?? [], + fix: args['--fix'] ?? false, + fixTypes: args['--fix-type'] ?? null, + ignorePath: args['--ignore-path'] || null, + ignore: !Boolean(args['--no-ignore']), + allowInlineConfig: !Boolean(args['--no-inline-config']), + reportUnusedDisableDirectives: + args['--report-unused-disable-directives'] || null, + cache: args['--cache'] ?? false, + cacheLocation: args['--cache-location'] || '.eslintcache', + cacheStrategy: args['--cache-strategy'] || 'metadata', + errorOnUnmatchedPattern: !Boolean(args['--no-error-on-unmatched-pattern']), +}) + const nextLint: cliCommand = (argv) => { const validArgs: arg.Spec = { // Types '--help': Boolean, + '--base-dir': String, '--dir': [String], // Aliases '-h': '--help', + '-b': '--base-dir', '-d': '--dir', } + const validEslintArgs: arg.Spec = { + // Types + '--config': String, + '--ext': [String], + '--resolve-plugins-relative-to': String, + '--rulesdir': [String], + '--fix': Boolean, + '--fix-type': [String], + '--ignore-path': String, + '--no-ignore': Boolean, + '--no-inline-config': Boolean, + '--report-unused-disable-directives': String, + '--cache': Boolean, + '--cache-location': String, + '--cache-strategy': String, + '--no-error-on-unmatched-pattern': Boolean, + + // Aliases + '-c': '--config', + } + let args: arg.Result try { - args = arg(validArgs, { argv }) + args = arg({ ...validArgs, ...validEslintArgs }, { argv }) } catch (error) { if (error.code === 'ARG_UNKNOWN_OPTION') { return printAndExit(error.message, 1) @@ -37,14 +79,41 @@ const nextLint: cliCommand = (argv) => { Usage $ next lint [options] - + represents the directory of the Next.js application. If no directory is provided, the current directory will be used. Options - -h - list this help - -d - set directory, or directories, to run ESLint (defaults to only 'pages') - `, + Basic configuration: + -h, --help List this help + -d, --dir Array Set directory, or directories, to run ESLint - default: 'pages', 'components', and 'lib' + -c, --config path::String Use this configuration file, overriding all other config options + --ext [String] Specify JavaScript file extensions - default: .js, .jsx, .ts, .tsx + --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default + + Specifying rules: + --rulesdir [path::String] Use additional rules from this directory + + Fixing problems: + --fix Automatically fix problems + --fix-type Array Specify the types of fixes to apply (problem, suggestion, layout) + + Ignoring files: + --ignore-path path::String Specify path of ignore file + --no-ignore Disable use of ignore files and patterns + + Inline configuration comments: + --no-inline-config Prevent comments from changing config or rules + --report-unused-disable-directives Adds reported errors for unused eslint-disable directives ("error" | "warn" | "off") + + Caching: + --cache Only check changed files - default: false + --cache-location path::String Path to the cache file or directory - default: .eslintcache + --cache-strategy String Strategy to use for detecting changed files - either: metadata or content - default: metadata + + Miscellaneous: + --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched - default: false + `, 0 ) } @@ -57,7 +126,7 @@ const nextLint: cliCommand = (argv) => { } const dirs: string[] = args['--dir'] - const lintDirs = (dirs ?? ['pages', 'components', 'lib']).reduce( + const lintDirs = (dirs ?? ESLINT_DEFAULT_DIRS).reduce( (res: string[], d: string) => { const currDir = join(baseDir, d) if (!existsSync(currDir)) return res @@ -67,7 +136,7 @@ const nextLint: cliCommand = (argv) => { [] ) - runLintCheck(baseDir, lintDirs) + runLintCheck(baseDir, lintDirs, false, eslintOptions(args)) .then((results) => { if (results) { console.log(results) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 46a4bac4165c6..1180493a86275 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -53,7 +53,7 @@ interface StaticImageData { src: string height: number width: number - placeholder?: string + blurDataURL?: string } interface StaticRequire { @@ -261,28 +261,28 @@ function defaultImageLoader(loaderProps: ImageLoaderProps) { // See https://stackoverflow.com/q/39777833/266535 for why we use this ref // handler instead of the img's onLoad attribute. -// 1500ms delay in removing placeholder is to prevent flash of white between -// image load and image render. function removePlaceholder( - element: HTMLImageElement | null, + img: HTMLImageElement | null, placeholder: PlaceholderValue ) { - if (placeholder === 'blur' && element) { - if (element.complete && !element.src.startsWith('data:')) { + if (placeholder === 'blur' && img) { + const handleLoad = () => { + if (!img.src.startsWith('data:')) { + const p = 'decode' in img ? img.decode() : Promise.resolve() + p.then(() => { + img.style.filter = 'none' + img.style.backgroundSize = 'none' + img.style.backgroundImage = 'none' + }) + } + } + if (img.complete) { // If the real image fails to load, this will still remove the placeholder. // This is the desired behavior for now, and will be revisited when error // handling is worked on for the image component itself. - setTimeout(() => { - element.style.backgroundImage = 'none' - }, 1500) + handleLoad() } else { - element.onload = () => { - if (!element.src.startsWith('data:')) { - setTimeout(() => { - element.style.backgroundImage = 'none' - }, 1500) - } - } + img.onload = handleLoad } } } @@ -325,9 +325,7 @@ export default function Image({ )}` ) } - if (staticImageData.placeholder) { - blurDataURL = staticImageData.placeholder - } + blurDataURL = blurDataURL || staticImageData.blurDataURL staticSrc = staticImageData.src if (!layout || layout !== 'fill') { height = height || staticImageData.height @@ -343,6 +341,10 @@ export default function Image({ } src = typeof src === 'string' ? src : staticSrc + const widthInt = getInt(width) + const heightInt = getInt(height) + const qualityInt = getInt(quality) + if (process.env.NODE_ENV !== 'production') { if (!src) { throw new Error( @@ -370,6 +372,27 @@ export default function Image({ `Image with src "${src}" has both "priority" and "loading='lazy'" properties. Only one should be used.` ) } + if (placeholder === 'blur') { + if ((widthInt || 0) * (heightInt || 0) < 1600) { + console.warn( + `Image with src "${src}" is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance.` + ) + } + if (!blurDataURL) { + const VALID_BLUR_EXT = ['jpeg', 'png', 'webp'] // should match next-image-loader + + throw new Error( + `Image with src "${src}" has "placeholder='blur'" property but is missing the "blurDataURL" property. + Possible solutions: + - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image + - Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join( + ',' + )} + - Remove the "placeholder" property, effectively no blur effect + Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url` + ) + } + } } let isLazy = !priority && (loading === 'lazy' || typeof loading === 'undefined') @@ -385,16 +408,6 @@ export default function Image({ }) const isVisible = !isLazy || isIntersected - const widthInt = getInt(width) - const heightInt = getInt(height) - const qualityInt = getInt(quality) - - const MIN_IMG_SIZE_FOR_PLACEHOLDER = 5000 - const tooSmallForBlurryPlaceholder = - widthInt && heightInt && widthInt * heightInt < MIN_IMG_SIZE_FOR_PLACEHOLDER - const shouldShowBlurryPlaceholder = - placeholder === 'blur' && !tooSmallForBlurryPlaceholder - let wrapperStyle: JSX.IntrinsicElements['div']['style'] | undefined let sizerStyle: JSX.IntrinsicElements['div']['style'] | undefined let sizerSvg: string | undefined @@ -421,8 +434,9 @@ export default function Image({ objectFit, objectPosition, - ...(shouldShowBlurryPlaceholder + ...(placeholder === 'blur' ? { + filter: 'blur(20px)', backgroundSize: 'cover', backgroundImage: `url("${blurDataURL}")`, } diff --git a/packages/next/lib/constants.ts b/packages/next/lib/constants.ts index 28636f4594104..8c56f65d5ceca 100644 --- a/packages/next/lib/constants.ts +++ b/packages/next/lib/constants.ts @@ -48,3 +48,12 @@ export const GSSP_COMPONENT_MEMBER_ERROR = `can not be attached to a page's comp export const NON_STANDARD_NODE_ENV = `You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against. Read more: https://nextjs.org/docs/messages/non-standard-node-env` export const SSG_FALLBACK_EXPORT_ERROR = `Pages with \`fallback\` enabled in \`getStaticPaths\` can not be exported. See more info here: https://nextjs.org/docs/messages/ssg-fallback-true-export` + +export const ESLINT_DEFAULT_DIRS = [ + 'pages', + 'components', + 'lib', + 'src/pages', + 'src/components', + 'src/lib', +] diff --git a/packages/next/lib/eslint/runLintCheck.ts b/packages/next/lib/eslint/runLintCheck.ts index aed9f452af043..b176881efe220 100644 --- a/packages/next/lib/eslint/runLintCheck.ts +++ b/packages/next/lib/eslint/runLintCheck.ts @@ -1,5 +1,6 @@ import { promises as fs } from 'fs' import chalk from 'chalk' +import path from 'path' import findUp from 'next/dist/compiled/find-up' import semver from 'next/dist/compiled/semver' @@ -7,7 +8,7 @@ import * as CommentJson from 'next/dist/compiled/comment-json' import { formatResults } from './customFormatter' import { writeDefaultConfig } from './writeDefaultConfig' -import { findPagesDir } from '../find-pages-dir' +import { existsSync, findPagesDir } from '../find-pages-dir' import { CompileError } from '../compile-error' import { hasNecessaryDependencies, @@ -21,16 +22,13 @@ type Config = { rules: { [key: string]: Array } } -const linteableFiles = (dir: string) => { - return `${dir}/**/*.{${['jsx', 'js', 'ts', 'tsx'].join(',')}}` -} - async function lint( deps: NecessaryDependencies, baseDir: string, lintDirs: string[], eslintrcFile: string | null, - pkgJsonPath: string | null + pkgJsonPath: string | null, + eslintOptions: any = null ): Promise { // Load ESLint after we're sure it exists: const mod = await import(deps.resolved) @@ -41,23 +39,23 @@ async function lint( const eslintVersion: string | undefined = mod?.CLIEngine?.version if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) { - Log.error( - `Your project has an older version of ESLint installed${ - eslintVersion ? ' (' + eslintVersion + ')' : '' - }. Please upgrade to ESLint version 7 or later` - ) - return null + return `${chalk.red( + 'error' + )} - Your project has an older version of ESLint installed${ + eslintVersion ? ' (' + eslintVersion + ')' : '' + }. Please upgrade to ESLint version 7 or later` } - Log.error( - `ESLint class not found. Please upgrade to ESLint version 7 or later` - ) - return null + return `${chalk.red( + 'error' + )} - ESLint class not found. Please upgrade to ESLint version 7 or later` } let options: any = { useEslintrc: true, baseConfig: {}, + extensions: ['.js', '.jsx', '.ts', '.tsx'], + ...eslintOptions, } let eslint = new ESLint(options) @@ -101,18 +99,21 @@ async function lint( eslint = new ESLint(options) } } + const results = await eslint.lintFiles(lintDirs) + if (options.fix) await ESLint.outputFixes(results) - const results = await eslint.lintFiles(lintDirs.map(linteableFiles)) if (ESLint.getErrorResults(results)?.length > 0) { throw new CompileError(await formatResults(baseDir, results)) } + return results?.length > 0 ? formatResults(baseDir, results) : null } export async function runLintCheck( baseDir: string, lintDirs: string[], - lintDuringBuild: boolean = false + lintDuringBuild: boolean = false, + eslintOptions: any = null ): Promise { try { // Find user's .eslintrc file @@ -158,10 +159,23 @@ export async function runLintCheck( ) // Write default ESLint config if none is present - await writeDefaultConfig(eslintrcFile, pkgJsonPath, packageJsonConfig) + // Check for /pages and src/pages is to make sure this happens in Next.js folder + if ( + existsSync(path.join(baseDir, 'pages')) || + existsSync(path.join(baseDir, 'src/pages')) + ) { + await writeDefaultConfig(eslintrcFile, pkgJsonPath, packageJsonConfig) + } // Run ESLint - return await lint(deps, baseDir, lintDirs, eslintrcFile, pkgJsonPath) + return await lint( + deps, + baseDir, + lintDirs, + eslintrcFile, + pkgJsonPath, + eslintOptions + ) } catch (err) { throw err } diff --git a/packages/next/lib/verifyAndLint.ts b/packages/next/lib/verifyAndLint.ts index 798f2b6ff9360..3ee163b1b0f0b 100644 --- a/packages/next/lib/verifyAndLint.ts +++ b/packages/next/lib/verifyAndLint.ts @@ -2,6 +2,7 @@ import chalk from 'chalk' import { Worker } from 'jest-worker' import { existsSync } from 'fs' import { join } from 'path' +import { ESLINT_DEFAULT_DIRS } from './constants' export async function verifyAndLint( dir: string, @@ -20,7 +21,7 @@ export async function verifyAndLint( lintWorkers.getStdout().pipe(process.stdout) lintWorkers.getStderr().pipe(process.stderr) - const lintDirs = (configLintDirs ?? ['pages', 'components', 'lib']).reduce( + const lintDirs = (configLintDirs ?? ESLINT_DEFAULT_DIRS).reduce( (res: string[], d: string) => { const currDir = join(dir, d) if (!existsSync(currDir)) return res diff --git a/packages/next/package.json b/packages/next/package.json index e5046972c53db..5557e7c83f906 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "10.2.4-canary.11", + "version": "10.2.4-canary.13", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -64,10 +64,10 @@ "dependencies": { "@babel/runtime": "7.12.5", "@hapi/accept": "5.0.2", - "@next/env": "10.2.4-canary.11", - "@next/polyfill-module": "10.2.4-canary.11", - "@next/react-dev-overlay": "10.2.4-canary.11", - "@next/react-refresh-utils": "10.2.4-canary.11", + "@next/env": "10.2.4-canary.13", + "@next/polyfill-module": "10.2.4-canary.13", + "@next/react-dev-overlay": "10.2.4-canary.13", + "@next/react-refresh-utils": "10.2.4-canary.13", "assert": "2.0.0", "ast-types": "0.13.2", "browserify-zlib": "0.2.0", @@ -151,7 +151,7 @@ "@babel/preset-typescript": "7.12.7", "@babel/traverse": "^7.12.10", "@babel/types": "7.12.12", - "@next/polyfill-nomodule": "10.2.4-canary.11", + "@next/polyfill-nomodule": "10.2.4-canary.13", "@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 d496ccb04257c..8fe5aca03898d 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.2.4-canary.11", + "version": "10.2.4-canary.13", "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 c4fee3c23fded..faf0ef6be3962 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.2.4-canary.11", + "version": "10.2.4-canary.13", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/test/integration/eslint/first-time-setup/.eslintrc b/test/integration/eslint/first-time-setup/.eslintrc index 15b1ed91acf66..c3796c8047dd0 100644 --- a/test/integration/eslint/first-time-setup/.eslintrc +++ b/test/integration/eslint/first-time-setup/.eslintrc @@ -1,3 +1,4 @@ { + "root": true, "extends": "next" } diff --git a/test/integration/image-component/default/pages/blurry-placeholder.js b/test/integration/image-component/default/pages/blurry-placeholder.js index 6d3dd34f031ab..4ebc925cbe94c 100644 --- a/test/integration/image-component/default/pages/blurry-placeholder.js +++ b/test/integration/image-component/default/pages/blurry-placeholder.js @@ -8,7 +8,7 @@ export default function Page() { { + return ( +
+ +
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/pages/invalid-placeholder-blur.js b/test/integration/image-component/default/pages/invalid-placeholder-blur.js new file mode 100644 index 0000000000000..2a02eb0478ce3 --- /dev/null +++ b/test/integration/image-component/default/pages/invalid-placeholder-blur.js @@ -0,0 +1,12 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+ +
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/pages/small-img-import.js b/test/integration/image-component/default/pages/small-img-import.js new file mode 100644 index 0000000000000..da06761146a77 --- /dev/null +++ b/test/integration/image-component/default/pages/small-img-import.js @@ -0,0 +1,13 @@ +import React from 'react' +import Image from 'next/image' +import Small from '../public/small.jpg' + +const Page = () => { + return ( +
+ +
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/pages/static.js b/test/integration/image-component/default/pages/static.js index b0ddba5fc0702..212481e82bb49 100644 --- a/test/integration/image-component/default/pages/static.js +++ b/test/integration/image-component/default/pages/static.js @@ -4,23 +4,13 @@ import Image from 'next/image' import testJPG from '../public/test.jpg' import testPNG from '../public/test.png' +import testWEBP from '../public/test.webp' import testSVG from '../public/test.svg' import testGIF from '../public/test.gif' import testBMP from '../public/test.bmp' import testICO from '../public/test.ico' -import testWEBP from '../public/test.webp' import TallImage from '../components/TallImage' -const testFiles = [ - testJPG, - testPNG, - testSVG, - testGIF, - testBMP, - testICO, - testWEBP, -] - const Page = () => { return (
@@ -46,9 +36,14 @@ const Page = () => { width="400" height="300" /> - {testFiles.map((f, i) => ( - - ))} +
+ + + + + + +
) } diff --git a/test/integration/image-component/default/public/small.jpg b/test/integration/image-component/default/public/small.jpg new file mode 100644 index 0000000000000..cb60a66dfd882 Binary files /dev/null and b/test/integration/image-component/default/public/small.jpg differ diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 6ff15e1108a0e..3f0f666ee52c6 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -484,6 +484,39 @@ function runTests(mode) { 'Failed to parse src "//assets.example.com/img.jpg" on `next/image`, protocol-relative URL (//) must be changed to an absolute URL (http:// or https://)' ) }) + + it('should show error when string src and placeholder=blur and blurDataURL is missing', async () => { + const browser = await webdriver(appPort, '/invalid-placeholder-blur') + + expect(await hasRedbox(browser)).toBe(true) + expect(await getRedboxHeader(browser)).toContain( + `Image with src "/test.png" has "placeholder='blur'" property but is missing the "blurDataURL" property.` + ) + }) + + it('should show error when static import and placeholder=blur and blurDataUrl is missing', async () => { + const browser = await webdriver( + appPort, + '/invalid-placeholder-blur-static' + ) + + expect(await hasRedbox(browser)).toBe(true) + expect(await getRedboxHeader(browser)).toMatch( + /Image with src "(.*)bmp" has "placeholder='blur'" property but is missing the "blurDataURL" property/ + ) + }) + + it('should warn when using a very small image with placeholder=blur', async () => { + const browser = await webdriver(appPort, '/small-img-import') + + const warnings = (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + expect(await hasRedbox(browser)).toBe(false) + expect(warnings).toMatch( + /Image with src (.*)jpg(.*) is smaller than 40x40. Consider removing(.*)/gm + ) + }) } it('should correctly ignore prose styles', async () => { diff --git a/test/integration/image-component/default/test/static.test.js b/test/integration/image-component/default/test/static.test.js index 9b814f93128e0..0e431822186a2 100644 --- a/test/integration/image-component/default/test/static.test.js +++ b/test/integration/image-component/default/test/static.test.js @@ -22,13 +22,12 @@ const indexPage = new File(join(appDir, 'pages/static.js')) const runTests = () => { it('Should allow an image with a static src to omit height and width', async () => { expect(await browser.elementById('basic-static')).toBeTruthy() - expect(await browser.elementById('format-test-0')).toBeTruthy() - expect(await browser.elementById('format-test-1')).toBeTruthy() - expect(await browser.elementById('format-test-2')).toBeTruthy() - expect(await browser.elementById('format-test-3')).toBeTruthy() - expect(await browser.elementById('format-test-4')).toBeTruthy() - expect(await browser.elementById('format-test-5')).toBeTruthy() - expect(await browser.elementById('format-test-6')).toBeTruthy() + expect(await browser.elementById('blur-png')).toBeTruthy() + expect(await browser.elementById('blur-jpg')).toBeTruthy() + expect(await browser.elementById('static-svg')).toBeTruthy() + expect(await browser.elementById('static-gif')).toBeTruthy() + expect(await browser.elementById('static-bmp')).toBeTruthy() + expect(await browser.elementById('static-ico')).toBeTruthy() }) it('Should automatically provide an image height and width', async () => { expect(html).toContain('width:400px;height:300px') @@ -39,12 +38,12 @@ const runTests = () => { }) it('Should add a blurry placeholder to statically imported jpg', async () => { expect(html).toContain( - `style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-image:url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAgACAMBIgACEQEDEQH/xAAUAAEAAAAAAAAAAAAAAAAAAAAH/9oACAEBAAAAADX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oACAECEAAAAH//xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDEAAAAH//xAAdEAABAgcAAAAAAAAAAAAAAAATEhUAAwUUIzLS/9oACAEBAAE/AB0ZlUac43GqMYuo/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwB//9k=")"` + `style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAgACAMBIgACEQEDEQH/xAAUAAEAAAAAAAAAAAAAAAAAAAAH/9oACAEBAAAAADX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oACAECEAAAAH//xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDEAAAAH//xAAdEAABAgcAAAAAAAAAAAAAAAATEhUAAwUUIzLS/9oACAEBAAE/AB0ZlUac43GqMYuo/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwB//9k=")"` ) }) it('Should add a blurry placeholder to statically imported png', async () => { expect(html).toContain( - `style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAQAAABuBnYAAAAATklEQVR42i2I0QmAMBQD869Q9K+IsxU6RkfoiA6T55VXDpJLJC9uUJIzcx+XFd2dXMbx8n+QpoeYDpgY66RaDA83jCUfVpK2pER1dcEUP+KfSBtXK+BpAAAAAElFTkSuQmCC")"` + `style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAQAAABuBnYAAAAATklEQVR42i2I0QmAMBQD869Q9K+IsxU6RkfoiA6T55VXDpJLJC9uUJIzcx+XFd2dXMbx8n+QpoeYDpgY66RaDA83jCUfVpK2pER1dcEUP+KfSBtXK+BpAAAAAElFTkSuQmCC")"` ) }) }